前言
出厂的设备有些客户喜欢设置锁屏密码,无奈记性不好,忘记密码后就只能恢复出厂或者重新刷机了,啊这客户肯定不接受的。
为了防止客户逼逼赖赖,我们就未雨绸缪,给它加个清除接口。
先说结论,系统锁屏密码数据库存储位于 /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); } }