前言
出厂的设备有些客户喜欢设置锁屏密码,无奈记性不好,忘记密码后就只能恢复出厂或者重新刷机了,啊这客户肯定不接受的。
为了防止客户逼逼赖赖,我们就未雨绸缪,给它加个清除接口。
先说结论,系统锁屏密码数据库存储位于 /data/system/locksettings.db
经过测试 O 版本直接删除 locksettings.db 就已经达到要求
测试 Q、R 版本直接删除 locksettings.db 后,系统锁屏界面确实没了,但一直卡在安卓正在启动界面了,
无法进入桌面,进入设置中查看 Launcher3 应用丢了,目测此路不通。
那就曲线救国去研究 Settings 中是如何验证密码并取消密码的。
Settings 中各版本存储密码传递对象,已图案密码为例 ConfirmLockPattern.java
O、P 版本 密码 String intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, LockPatternUtils.patternToString(pattern));
Q 版本 密码 Byte[] intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, LockPatternUtils.patternToByteArray(pattern));
R 版本 密码 LockscreenCredential intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, pattern);
framework 往下加解密流程可参考这篇 AndroidQ 锁屏密码验证流程之GateKeeper解析
解决思路
一:找到 Settings 中与用户交互设置锁屏密码核心代码,将绘制图案或者密码字符保存起来(数据库、prop、ContentProvider等)
二:增加清除接口(AIDL、广播、ContentResolver监听等)
三:取出用户设置密码转换为 framework 所需要 LockscreenCredential 对象
四:移除密码
上代码
4.1、找到锁屏图案保存核心代码
vendor\mediatek\proprietary\packages\apps\MtkSettings\src\com\android\settings\password\ChooseLockPattern.java
protected LockPatternView.OnPatternListener mChooseNewLockPatternListener =
new LockPatternView.OnPatternListener() {
....
public void onPatternDetected(List<LockPatternView.Cell> pattern) {
if (mUiStage == Stage.NeedToConfirm || mUiStage == Stage.ConfirmWrong) {
if (mChosenPattern == null) throw new IllegalStateException(
"null chosen pattern in stage 'need to confirm");
try (LockscreenCredential confirmPattern =
LockscreenCredential.createPattern(pattern)) {
if (mChosenPattern.equals(confirmPattern)) {
//20200903 cczheng add save lockdata start
mScreenLockHelper.saveCurrentLockData(pattern);
//20200903 cczheng add save lockdata end
updateStage(Stage.ChoiceConfirmed);
} else {
updateStage(Stage.ConfirmWrong);
}
}
} else if (mUiStage == Stage.Introduction || mUiStage == Stage.ChoiceTooShort){
if (pattern.size() < LockPatternUtils.MIN_LOCK_PATTERN_SIZE) {
updateStage(Stage.ChoiceTooShort);
} else {
mChosenPattern = LockscreenCredential.createPattern(pattern);
updateStage(Stage.FirstChoiceValid);
}
} else {
throw new IllegalStateException("Unexpected stage " + mUiStage + " when "
+ "entering the pattern.");
}
}
mChosenPattern 第一次绘制图案密码,confirmPattern 第二次绘制图案密码,两次一致设置成功,
就地保存图案密码。图案密码九宫格每个点 Cell 对应一个 Row, 一个 Column,实际规则如下
(0,0) (0,1) (0,2)
(1,0) (1,1) (1,2)
(2,0) (2,1) (2,2)
最终画线将每一个 cell 添加到集合中。
实际图案密码对象 LockscreenCredential.createPattern(pattern);
4.2、找到PIN码和密码保存核心代码
vendor\mediatek\proprietary\packages\apps\MtkSettings\src\com\android\settings\password\ChooseLockPassword.java
public void handleNext() {
if (mSaveAndFinishWorker != null) return;
// TODO(b/120484642): This is a point of entry for passwords from the UI
final Editable passwordText = mPasswordEntry.getText();
if (TextUtils.isEmpty(passwordText)) {
return;
}
mChosenPassword = mIsAlphaMode ? LockscreenCredential.createPassword(passwordText)
: LockscreenCredential.createPin(passwordText);
if (mUiStage == Stage.Introduction) {
if (validatePassword(mChosenPassword)) {
mFirstPassword = mChosenPassword;
mPasswordEntry.setText("");
updateStage(Stage.NeedToConfirm);
} else {
mChosenPassword.zeroize();
}
} else if (mUiStage == Stage.NeedToConfirm) {
if (mChosenPassword.equals(mFirstPassword)) {
//20200903 cczheng add save lockdata start
mScreenLockHelper.saveCurrentLockData(mIsAlphaMode, passwordText.toString());
//20200903 cczheng add save lockdata end
startSaveAndFinish();
} else {
CharSequence tmp = mPasswordEntry.getText();
if (tmp != null) {
Selection.setSelection((Spannable) tmp, 0, tmp.length());
}
updateStage(Stage.ConfirmWrong);
mChosenPassword.zeroize();
}
}
}
mIsAlphaMode 用来区分是 PIN 码还是密码
实际密码对象 LockscreenCredential.createPassword(passwordText)
实际PIN码对象 LockscreenCredential.createPin(passwordText)
4.3、找到移除密码核心代码
设置完锁屏密码后再次进入锁屏选择页面,此时需要验证刚刚设置密码,验证成功后才能进入锁屏选择界面。
验证成功就获取到了 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, pattern);
这个很关键,一会需要用它来移除锁屏密码
vendor\mediatek\proprietary\packages\apps\MtkSettings\src\com\android\settings\password\ChooseLockGeneric.java
void updateUnlockMethodAndFinish(int quality, boolean disabled, boolean chooseLockSkipped) {
.....
if (quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
// Clearing of user biometrics when screen lock is cleared is done at
// LockSettingsService.removeBiometricsForUser().
if (mUserPassword != null) {
// No need to call setLockCredential if the user currently doesn't
// have a password
mChooseLockSettingsHelper.utils().setLockCredential(
LockscreenCredential.createNone(), mUserPassword, mUserId);
}
mChooseLockSettingsHelper.utils().setLockScreenDisabled(disabled, mUserId);
getActivity().setResult(Activity.RESULT_OK);
finish();
}
}
关键方法就两个
setLockCredential(新密码,旧密码,用户id)
setLockScreenDisabled(移除密码,用户id)
4.4、造数据移除密码
package com.android.settings.password;
import android.content.Context;
import android.util.Log;
import android.os.SystemProperties;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternView;
import com.android.internal.widget.LockPatternView.Cell;
import com.android.internal.widget.LockscreenCredential;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ScreenLockHelper{
private static final String TAG = "ScreenLockHelper";
//value startwith
//pt&xxxx LockPattern
//pn&xxxx LockPin
//ps&xxxx LockPassword
private final String KEY = "persist.android.screen.lock";
private final String HEAD_PAT = "pt&";
private final String HEAD_PIN = "pn&";
private final String HEAD_PWD = "ps&";
private Context mContext;
private LockPatternUtils mLockPatternUtils;
public ScreenLockHelper(Context context) {
mContext = context;
mLockPatternUtils = new LockPatternUtils(context);
}
public void saveCurrentLockData(boolean mIsPwd, String pwd) {
pwd = (mIsPwd ? HEAD_PWD : HEAD_PIN) + pwd;
SystemProperties.set(KEY, pwd);
}
public void saveCurrentLockData(List<LockPatternView.Cell> pattern) {
String pwd = HEAD_PAT;
StringBuilder builder = new StringBuilder();
builder.append(pwd);
for (int i=0; i<pattern.size(); i++) {
LockPatternView.Cell cell = pattern.get(i);
builder.append(cell.getRow());
builder.append("*");
builder.append(cell.getColumn());
if (i != pattern.size() - 1) {
builder.append("+");
}
Log.i(TAG,"cell="+cell.toString());
}
pwd = builder.toString();
SystemProperties.set(KEY, pwd);
}
public void clearScreenLock() {
LockscreenCredential emptyCredential = LockscreenCredential.createNone();
LockscreenCredential savedCredential;
String pwdValue = SystemProperties.get(KEY, "");
if (pwdValue.startsWith(HEAD_PIN)) {
savedCredential = LockscreenCredential.createPin(pwdValue.substring(3));
}else if (pwdValue.startsWith(HEAD_PWD)) {
savedCredential = LockscreenCredential.createPassword(pwdValue.substring(3));
}else if (pwdValue.startsWith(HEAD_PAT)) {
pwdValue = pwdValue.substring(3);
List<LockPatternView.Cell> pattern = new ArrayList<LockPatternView.Cell>();
String[] cellArray = pwdValue.split("\\+");
for (String cellStr : cellArray) {
String[] cell = cellStr.split("\\*");
pattern.add(Cell.of(Integer.parseInt(cell[0]), Integer.parseInt(cell[1])));
}
savedCredential = LockscreenCredential.createPattern(pattern);
}else{
savedCredential = LockscreenCredential.createNone();
}
mLockPatternUtils.setLockCredential(emptyCredential, savedCredential, 0);
mLockPatternUtils.setLockScreenDisabled(true, 0);
}
}