Merge changes Ief1cac19,Ibc9236a1 into rvc-dev
* changes: Resume-on-Reboot: add storage for armed status LockSettingsService: move database storage
This commit is contained in:
@@ -50,7 +50,6 @@ import android.app.admin.DevicePolicyManager;
|
||||
import android.app.admin.DevicePolicyManagerInternal;
|
||||
import android.app.admin.DeviceStateCache;
|
||||
import android.app.admin.PasswordMetrics;
|
||||
import android.app.backup.BackupManager;
|
||||
import android.app.trust.IStrongAuthTracker;
|
||||
import android.app.trust.TrustManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
@@ -112,10 +111,8 @@ import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
|
||||
import com.android.internal.notification.SystemNotificationChannels;
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.internal.util.DumpUtils;
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.internal.util.Preconditions;
|
||||
import com.android.internal.widget.ICheckCredentialProgressCallback;
|
||||
import com.android.internal.widget.ILockSettings;
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
@@ -701,7 +698,7 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
// Serial number is never reusued, so we can use it as a distinguisher for user Id reuse.
|
||||
int serialNumber = mUserManager.getUserSerialNumber(userId);
|
||||
|
||||
int storedSerialNumber = getIntUnchecked(USER_SERIAL_NUMBER_KEY, -1, userId);
|
||||
int storedSerialNumber = mStorage.getInt(USER_SERIAL_NUMBER_KEY, -1, userId);
|
||||
if (storedSerialNumber != serialNumber) {
|
||||
// If LockSettingsStorage does not have a copy of the serial number, it could be either
|
||||
// this is a user created before the serial number recording logic is introduced, or
|
||||
@@ -710,7 +707,7 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
if (storedSerialNumber != -1) {
|
||||
removeUser(userId, /* unknownUser */ true);
|
||||
}
|
||||
setIntUnchecked(USER_SERIAL_NUMBER_KEY, serialNumber, userId);
|
||||
mStorage.setInt(USER_SERIAL_NUMBER_KEY, serialNumber, userId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1069,7 +1066,7 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
|
||||
private boolean getSeparateProfileChallengeEnabledInternal(int userId) {
|
||||
synchronized (mSeparateChallengeLock) {
|
||||
return getBooleanUnchecked(SEPARATE_PROFILE_CHALLENGE_KEY, false, userId);
|
||||
return mStorage.getBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, false, userId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1122,94 +1119,49 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
@Override
|
||||
public void setBoolean(String key, boolean value, int userId) {
|
||||
checkWritePermission(userId);
|
||||
setStringUnchecked(key, userId, value ? "1" : "0");
|
||||
mStorage.setBoolean(key, value, userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLong(String key, long value, int userId) {
|
||||
checkWritePermission(userId);
|
||||
setLongUnchecked(key, value, userId);
|
||||
}
|
||||
|
||||
private void setLongUnchecked(String key, long value, int userId) {
|
||||
setStringUnchecked(key, userId, Long.toString(value));
|
||||
}
|
||||
|
||||
private void setIntUnchecked(String key, int value, int userId) {
|
||||
setStringUnchecked(key, userId, Integer.toString(value));
|
||||
mStorage.setLong(key, value, userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setString(String key, String value, int userId) {
|
||||
checkWritePermission(userId);
|
||||
setStringUnchecked(key, userId, value);
|
||||
}
|
||||
|
||||
private void setStringUnchecked(String key, int userId, String value) {
|
||||
Preconditions.checkArgument(userId != USER_FRP, "cannot store lock settings for FRP user");
|
||||
|
||||
mStorage.writeKeyValue(key, value, userId);
|
||||
if (ArrayUtils.contains(SETTINGS_TO_BACKUP, key)) {
|
||||
BackupManager.dataChanged("com.android.providers.settings");
|
||||
}
|
||||
mStorage.setString(key, value, userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBoolean(String key, boolean defaultValue, int userId) {
|
||||
checkReadPermission(key, userId);
|
||||
return getBooleanUnchecked(key, defaultValue, userId);
|
||||
}
|
||||
|
||||
private boolean getBooleanUnchecked(String key, boolean defaultValue, int userId) {
|
||||
String value = getStringUnchecked(key, null, userId);
|
||||
return TextUtils.isEmpty(value) ?
|
||||
defaultValue : (value.equals("1") || value.equals("true"));
|
||||
if (Settings.Secure.LOCK_PATTERN_ENABLED.equals(key)) {
|
||||
return getCredentialTypeInternal(userId) == CREDENTIAL_TYPE_PATTERN;
|
||||
}
|
||||
return mStorage.getBoolean(key, defaultValue, userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(String key, long defaultValue, int userId) {
|
||||
checkReadPermission(key, userId);
|
||||
return getLongUnchecked(key, defaultValue, userId);
|
||||
}
|
||||
|
||||
private long getLongUnchecked(String key, long defaultValue, int userId) {
|
||||
String value = getStringUnchecked(key, null, userId);
|
||||
return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value);
|
||||
}
|
||||
|
||||
private int getIntUnchecked(String key, int defaultValue, int userId) {
|
||||
String value = getStringUnchecked(key, null, userId);
|
||||
return TextUtils.isEmpty(value) ? defaultValue : Integer.parseInt(value);
|
||||
return mStorage.getLong(key, defaultValue, userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(String key, String defaultValue, int userId) {
|
||||
checkReadPermission(key, userId);
|
||||
return getStringUnchecked(key, defaultValue, userId);
|
||||
}
|
||||
|
||||
private String getStringUnchecked(String key, String defaultValue, int userId) {
|
||||
if (Settings.Secure.LOCK_PATTERN_ENABLED.equals(key)) {
|
||||
return getCredentialTypeInternal(userId) == CREDENTIAL_TYPE_PATTERN ? "1" : "0";
|
||||
}
|
||||
if (userId == USER_FRP) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (LockPatternUtils.LEGACY_LOCK_PATTERN_ENABLED.equals(key)) {
|
||||
key = Settings.Secure.LOCK_PATTERN_ENABLED;
|
||||
}
|
||||
|
||||
return mStorage.readKeyValue(key, defaultValue, userId);
|
||||
return mStorage.getString(key, defaultValue, userId);
|
||||
}
|
||||
|
||||
private void setKeyguardStoredQuality(int quality, int userId) {
|
||||
if (DEBUG) Slog.d(TAG, "setKeyguardStoredQuality: user=" + userId + " quality=" + quality);
|
||||
setLongUnchecked(LockPatternUtils.PASSWORD_TYPE_KEY, quality, userId);
|
||||
mStorage.setLong(LockPatternUtils.PASSWORD_TYPE_KEY, quality, userId);
|
||||
}
|
||||
|
||||
private int getKeyguardStoredQuality(int userId) {
|
||||
return (int) getLongUnchecked(LockPatternUtils.PASSWORD_TYPE_KEY,
|
||||
return (int) mStorage.getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
|
||||
DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userId);
|
||||
}
|
||||
|
||||
@@ -2493,13 +2445,6 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
SEPARATE_PROFILE_CHALLENGE_KEY
|
||||
};
|
||||
|
||||
private static final String[] SETTINGS_TO_BACKUP = new String[] {
|
||||
Secure.LOCK_SCREEN_OWNER_INFO_ENABLED,
|
||||
Secure.LOCK_SCREEN_OWNER_INFO,
|
||||
Secure.LOCK_PATTERN_VISIBLE,
|
||||
LockPatternUtils.LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS
|
||||
};
|
||||
|
||||
private class GateKeeperDiedRecipient implements IBinder.DeathRecipient {
|
||||
@Override
|
||||
public void binderDied() {
|
||||
|
||||
@@ -18,8 +18,12 @@ package com.android.server.locksettings;
|
||||
|
||||
import static android.content.Context.USER_SERVICE;
|
||||
|
||||
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
|
||||
import static com.android.internal.widget.LockPatternUtils.USER_FRP;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.app.backup.BackupManager;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.pm.UserInfo;
|
||||
@@ -29,6 +33,8 @@ import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.os.Environment;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Slog;
|
||||
|
||||
@@ -87,6 +93,13 @@ class LockSettingsStorage {
|
||||
|
||||
private static final Object DEFAULT = new Object();
|
||||
|
||||
private static final String[] SETTINGS_TO_BACKUP = new String[] {
|
||||
Settings.Secure.LOCK_SCREEN_OWNER_INFO_ENABLED,
|
||||
Settings.Secure.LOCK_SCREEN_OWNER_INFO,
|
||||
Settings.Secure.LOCK_PATTERN_VISIBLE,
|
||||
LockPatternUtils.LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS
|
||||
};
|
||||
|
||||
private final DatabaseHelper mOpenHelper;
|
||||
private final Context mContext;
|
||||
private final Cache mCache = new Cache();
|
||||
@@ -136,10 +149,12 @@ class LockSettingsStorage {
|
||||
mOpenHelper.setCallback(callback);
|
||||
}
|
||||
|
||||
@VisibleForTesting(visibility = PACKAGE)
|
||||
public void writeKeyValue(String key, String value, int userId) {
|
||||
writeKeyValue(mOpenHelper.getWritableDatabase(), key, value, userId);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void writeKeyValue(SQLiteDatabase db, String key, String value, int userId) {
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(COLUMN_KEY, key);
|
||||
@@ -159,6 +174,7 @@ class LockSettingsStorage {
|
||||
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public String readKeyValue(String key, String defaultValue, int userId) {
|
||||
int version;
|
||||
synchronized (mCache) {
|
||||
@@ -184,6 +200,28 @@ class LockSettingsStorage {
|
||||
return result == DEFAULT ? defaultValue : (String) result;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void removeKey(String key, int userId) {
|
||||
removeKey(mOpenHelper.getWritableDatabase(), key, userId);
|
||||
}
|
||||
|
||||
private void removeKey(SQLiteDatabase db, String key, int userId) {
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(COLUMN_KEY, key);
|
||||
cv.put(COLUMN_USERID, userId);
|
||||
|
||||
db.beginTransaction();
|
||||
try {
|
||||
db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?",
|
||||
new String[] {key, Integer.toString(userId)});
|
||||
db.setTransactionSuccessful();
|
||||
mCache.removeKey(key, userId);
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void prefetchUser(int userId) {
|
||||
int version;
|
||||
synchronized (mCache) {
|
||||
@@ -537,6 +575,55 @@ class LockSettingsStorage {
|
||||
}
|
||||
}
|
||||
|
||||
public void setBoolean(String key, boolean value, int userId) {
|
||||
setString(key, value ? "1" : "0", userId);
|
||||
}
|
||||
|
||||
public void setLong(String key, long value, int userId) {
|
||||
setString(key, Long.toString(value), userId);
|
||||
}
|
||||
|
||||
public void setInt(String key, int value, int userId) {
|
||||
setString(key, Integer.toString(value), userId);
|
||||
}
|
||||
|
||||
public void setString(String key, String value, int userId) {
|
||||
Preconditions.checkArgument(userId != USER_FRP, "cannot store lock settings for FRP user");
|
||||
|
||||
writeKeyValue(key, value, userId);
|
||||
if (ArrayUtils.contains(SETTINGS_TO_BACKUP, key)) {
|
||||
BackupManager.dataChanged("com.android.providers.settings");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getBoolean(String key, boolean defaultValue, int userId) {
|
||||
String value = getString(key, null, userId);
|
||||
return TextUtils.isEmpty(value)
|
||||
? defaultValue : (value.equals("1") || value.equals("true"));
|
||||
}
|
||||
|
||||
public long getLong(String key, long defaultValue, int userId) {
|
||||
String value = getString(key, null, userId);
|
||||
return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value);
|
||||
}
|
||||
|
||||
public int getInt(String key, int defaultValue, int userId) {
|
||||
String value = getString(key, null, userId);
|
||||
return TextUtils.isEmpty(value) ? defaultValue : Integer.parseInt(value);
|
||||
}
|
||||
|
||||
public String getString(String key, String defaultValue, int userId) {
|
||||
if (userId == USER_FRP) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (LockPatternUtils.LEGACY_LOCK_PATTERN_ENABLED.equals(key)) {
|
||||
key = Settings.Secure.LOCK_PATTERN_ENABLED;
|
||||
}
|
||||
|
||||
return readKeyValue(key, defaultValue, userId);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void closeDatabase() {
|
||||
mOpenHelper.close();
|
||||
@@ -764,6 +851,10 @@ class LockSettingsStorage {
|
||||
putIfUnchanged(CacheKey.TYPE_KEY_VALUE, key, value, userId, version);
|
||||
}
|
||||
|
||||
void removeKey(String key, int userId) {
|
||||
remove(CacheKey.TYPE_KEY_VALUE, key, userId);
|
||||
}
|
||||
|
||||
byte[] peekFile(String fileName) {
|
||||
return copyOf((byte[]) peek(CacheKey.TYPE_FILE, fileName, -1 /* userId */));
|
||||
}
|
||||
@@ -788,6 +879,9 @@ class LockSettingsStorage {
|
||||
return contains(CacheKey.TYPE_FETCHED, "", userId);
|
||||
}
|
||||
|
||||
private synchronized void remove(int type, String key, int userId) {
|
||||
mCache.remove(mCacheKey.set(type, key, userId));
|
||||
}
|
||||
|
||||
private synchronized void put(int type, String key, Object value, int userId) {
|
||||
// Create a new CachKey here because it may be saved in the map if the key is absent.
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.server.locksettings;
|
||||
|
||||
import static android.os.UserHandle.USER_SYSTEM;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.content.Context;
|
||||
@@ -24,6 +26,7 @@ import android.hardware.rebootescrow.IRebootEscrow;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.UserManager;
|
||||
import android.provider.Settings;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
@@ -39,6 +42,26 @@ import java.util.NoSuchElementException;
|
||||
class RebootEscrowManager {
|
||||
private static final String TAG = "RebootEscrowManager";
|
||||
|
||||
/**
|
||||
* Used in the database storage to indicate the boot count at which the reboot escrow was
|
||||
* previously armed.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static final String REBOOT_ESCROW_ARMED_KEY = "reboot_escrow_armed_count";
|
||||
|
||||
/**
|
||||
* Number of boots until we consider the escrow data to be stale for the purposes of metrics.
|
||||
* <p>
|
||||
* If the delta between the current boot number and the boot number stored when the mechanism
|
||||
* was armed is under this number and the escrow mechanism fails, we report it as a failure of
|
||||
* the mechanism.
|
||||
* <p>
|
||||
* If the delta over this number and escrow fails, we will not report the metric as failed
|
||||
* since there most likely was some other issue if the device rebooted several times before
|
||||
* getting to the escrow restore code.
|
||||
*/
|
||||
private static final int BOOT_COUNT_TOLERANCE = 5;
|
||||
|
||||
/**
|
||||
* Used to track when the reboot escrow is wanted. Should stay true once escrow is requested
|
||||
* unless clearRebootEscrow is called. This will allow all the active users to be unlocked
|
||||
@@ -74,6 +97,7 @@ class RebootEscrowManager {
|
||||
|
||||
interface Callbacks {
|
||||
boolean isUserSecure(int userId);
|
||||
|
||||
void onRebootEscrowRestored(byte spVersion, byte[] syntheticPassword, int userId);
|
||||
}
|
||||
|
||||
@@ -92,7 +116,8 @@ class RebootEscrowManager {
|
||||
return (UserManager) mContext.getSystemService(Context.USER_SERVICE);
|
||||
}
|
||||
|
||||
public @Nullable IRebootEscrow getRebootEscrow() {
|
||||
@Nullable
|
||||
public IRebootEscrow getRebootEscrow() {
|
||||
try {
|
||||
return IRebootEscrow.Stub.asInterface(ServiceManager.getService(
|
||||
"android.hardware.rebootescrow.IRebootEscrow/default"));
|
||||
@@ -101,6 +126,15 @@ class RebootEscrowManager {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getBootCount() {
|
||||
return Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.BOOT_COUNT,
|
||||
0);
|
||||
}
|
||||
|
||||
public void reportMetric(boolean success) {
|
||||
FrameworkStatsLog.write(FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, success);
|
||||
}
|
||||
}
|
||||
|
||||
RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage) {
|
||||
@@ -135,7 +169,7 @@ class RebootEscrowManager {
|
||||
for (UserInfo user : users) {
|
||||
mStorage.removeRebootEscrow(user.id);
|
||||
}
|
||||
FrameworkStatsLog.write(FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, false);
|
||||
onEscrowRestoreComplete(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -143,8 +177,19 @@ class RebootEscrowManager {
|
||||
for (UserInfo user : rebootEscrowUsers) {
|
||||
allUsersUnlocked &= restoreRebootEscrowForUser(user.id, escrowKey);
|
||||
}
|
||||
FrameworkStatsLog.write(FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED,
|
||||
allUsersUnlocked);
|
||||
onEscrowRestoreComplete(allUsersUnlocked);
|
||||
}
|
||||
|
||||
private void onEscrowRestoreComplete(boolean success) {
|
||||
int previousBootCount = mStorage.getInt(REBOOT_ESCROW_ARMED_KEY, 0, USER_SYSTEM);
|
||||
mStorage.removeKey(REBOOT_ESCROW_ARMED_KEY, USER_SYSTEM);
|
||||
|
||||
int bootCountDelta = mInjector.getBootCount() - previousBootCount;
|
||||
if (bootCountDelta > BOOT_COUNT_TOLERANCE) {
|
||||
return;
|
||||
}
|
||||
|
||||
mInjector.reportMetric(success);
|
||||
}
|
||||
|
||||
private RebootEscrowKey getAndClearRebootEscrowKey() {
|
||||
@@ -267,6 +312,8 @@ class RebootEscrowManager {
|
||||
return;
|
||||
}
|
||||
|
||||
mStorage.removeKey(REBOOT_ESCROW_ARMED_KEY, USER_SYSTEM);
|
||||
|
||||
try {
|
||||
rebootEscrow.storeKey(new byte[32]);
|
||||
} catch (RemoteException e) {
|
||||
@@ -308,6 +355,11 @@ class RebootEscrowManager {
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Failed escrow secret to RebootEscrow HAL", e);
|
||||
}
|
||||
|
||||
if (armedRebootEscrow) {
|
||||
mStorage.setInt(REBOOT_ESCROW_ARMED_KEY, mInjector.getBootCount(), USER_SYSTEM);
|
||||
}
|
||||
|
||||
return armedRebootEscrow;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,12 +19,16 @@ package com.android.server.locksettings;
|
||||
import static android.content.pm.UserInfo.FLAG_FULL;
|
||||
import static android.content.pm.UserInfo.FLAG_PRIMARY;
|
||||
import static android.content.pm.UserInfo.FLAG_PROFILE;
|
||||
import static android.os.UserHandle.USER_SYSTEM;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.clearInvocations;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
@@ -32,8 +36,10 @@ import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.testng.Assert.assertNull;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.hardware.rebootescrow.IRebootEscrow;
|
||||
import android.os.RemoteException;
|
||||
@@ -49,6 +55,7 @@ import com.android.internal.widget.RebootEscrowListener;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
@@ -76,16 +83,26 @@ public class RebootEscrowManagerTests {
|
||||
|
||||
LockSettingsStorageTestable mStorage;
|
||||
|
||||
private MockableRebootEscrowInjected mInjected;
|
||||
private RebootEscrowManager mService;
|
||||
|
||||
public interface MockableRebootEscrowInjected {
|
||||
int getBootCount();
|
||||
|
||||
void reportMetric(boolean success);
|
||||
}
|
||||
|
||||
static class MockInjector extends RebootEscrowManager.Injector {
|
||||
private final IRebootEscrow mRebootEscrow;
|
||||
private final UserManager mUserManager;
|
||||
private final MockableRebootEscrowInjected mInjected;
|
||||
|
||||
MockInjector(Context context, UserManager userManager, IRebootEscrow rebootEscrow) {
|
||||
MockInjector(Context context, UserManager userManager, IRebootEscrow rebootEscrow,
|
||||
MockableRebootEscrowInjected injected) {
|
||||
super(context);
|
||||
mRebootEscrow = rebootEscrow;
|
||||
mUserManager = userManager;
|
||||
mInjected = injected;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -97,11 +114,21 @@ public class RebootEscrowManagerTests {
|
||||
public IRebootEscrow getRebootEscrow() {
|
||||
return mRebootEscrow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBootCount() {
|
||||
return mInjected.getBootCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportMetric(boolean success) {
|
||||
mInjected.reportMetric(success);
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp_baseServices() throws Exception {
|
||||
mContext = mock(Context.class);
|
||||
mContext = new ContextWrapper(InstrumentationRegistry.getContext());
|
||||
mUserManager = mock(UserManager.class);
|
||||
mCallbacks = mock(RebootEscrowManager.Callbacks.class);
|
||||
mRebootEscrow = mock(IRebootEscrow.class);
|
||||
@@ -119,8 +146,9 @@ public class RebootEscrowManagerTests {
|
||||
when(mCallbacks.isUserSecure(WORK_PROFILE_USER_ID)).thenReturn(true);
|
||||
when(mCallbacks.isUserSecure(NONSECURE_SECONDARY_USER_ID)).thenReturn(false);
|
||||
when(mCallbacks.isUserSecure(SECURE_SECONDARY_USER_ID)).thenReturn(true);
|
||||
mService = new RebootEscrowManager(new MockInjector(mContext, mUserManager, mRebootEscrow),
|
||||
mCallbacks, mStorage);
|
||||
mInjected = mock(MockableRebootEscrowInjected.class);
|
||||
mService = new RebootEscrowManager(new MockInjector(mContext, mUserManager, mRebootEscrow,
|
||||
mInjected), mCallbacks, mStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -160,7 +188,11 @@ public class RebootEscrowManagerTests {
|
||||
verify(mockListener).onPreparedForReboot(eq(true));
|
||||
verify(mRebootEscrow, never()).storeKey(any());
|
||||
|
||||
assertNull(
|
||||
mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_ARMED_KEY, null, USER_SYSTEM));
|
||||
assertTrue(mService.armRebootEscrowIfNeeded());
|
||||
assertNotNull(
|
||||
mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_ARMED_KEY, null, USER_SYSTEM));
|
||||
verify(mRebootEscrow).storeKey(any());
|
||||
|
||||
assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
|
||||
@@ -180,7 +212,15 @@ public class RebootEscrowManagerTests {
|
||||
FAKE_AUTH_TOKEN);
|
||||
verify(mRebootEscrow, never()).storeKey(any());
|
||||
|
||||
assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
|
||||
assertTrue(mStorage.hasRebootEscrow(SECURE_SECONDARY_USER_ID));
|
||||
assertFalse(mStorage.hasRebootEscrow(NONSECURE_SECONDARY_USER_ID));
|
||||
|
||||
assertNull(
|
||||
mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_ARMED_KEY, null, USER_SYSTEM));
|
||||
assertTrue(mService.armRebootEscrowIfNeeded());
|
||||
assertNotNull(
|
||||
mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_ARMED_KEY, null, USER_SYSTEM));
|
||||
verify(mRebootEscrow, times(1)).storeKey(any());
|
||||
|
||||
assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
|
||||
@@ -200,4 +240,105 @@ public class RebootEscrowManagerTests {
|
||||
assertFalse(mService.armRebootEscrowIfNeeded());
|
||||
verifyNoMoreInteractions(mRebootEscrow);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadRebootEscrowDataIfAvailable_NothingAvailable_Success() throws Exception {
|
||||
mService.loadRebootEscrowDataIfAvailable();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadRebootEscrowDataIfAvailable_Success() throws Exception {
|
||||
when(mInjected.getBootCount()).thenReturn(0);
|
||||
|
||||
RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
|
||||
mService.setRebootEscrowListener(mockListener);
|
||||
mService.prepareRebootEscrow();
|
||||
|
||||
clearInvocations(mRebootEscrow);
|
||||
mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
|
||||
verify(mockListener).onPreparedForReboot(eq(true));
|
||||
|
||||
verify(mRebootEscrow, never()).storeKey(any());
|
||||
|
||||
ArgumentCaptor<byte[]> keyByteCaptor = ArgumentCaptor.forClass(byte[].class);
|
||||
assertTrue(mService.armRebootEscrowIfNeeded());
|
||||
verify(mRebootEscrow).storeKey(keyByteCaptor.capture());
|
||||
|
||||
assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
|
||||
assertFalse(mStorage.hasRebootEscrow(NONSECURE_SECONDARY_USER_ID));
|
||||
|
||||
// pretend reboot happens here
|
||||
|
||||
when(mInjected.getBootCount()).thenReturn(1);
|
||||
ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
|
||||
doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture());
|
||||
when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue());
|
||||
|
||||
mService.loadRebootEscrowDataIfAvailable();
|
||||
verify(mRebootEscrow).retrieveKey();
|
||||
assertTrue(metricsSuccessCaptor.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadRebootEscrowDataIfAvailable_TooManyBootsInBetween_NoMetrics() throws Exception {
|
||||
when(mInjected.getBootCount()).thenReturn(0);
|
||||
|
||||
RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
|
||||
mService.setRebootEscrowListener(mockListener);
|
||||
mService.prepareRebootEscrow();
|
||||
|
||||
clearInvocations(mRebootEscrow);
|
||||
mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
|
||||
verify(mockListener).onPreparedForReboot(eq(true));
|
||||
|
||||
verify(mRebootEscrow, never()).storeKey(any());
|
||||
|
||||
ArgumentCaptor<byte[]> keyByteCaptor = ArgumentCaptor.forClass(byte[].class);
|
||||
assertTrue(mService.armRebootEscrowIfNeeded());
|
||||
verify(mRebootEscrow).storeKey(keyByteCaptor.capture());
|
||||
|
||||
assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
|
||||
assertFalse(mStorage.hasRebootEscrow(NONSECURE_SECONDARY_USER_ID));
|
||||
|
||||
// pretend reboot happens here
|
||||
|
||||
when(mInjected.getBootCount()).thenReturn(10);
|
||||
when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue());
|
||||
|
||||
mService.loadRebootEscrowDataIfAvailable();
|
||||
verify(mRebootEscrow).retrieveKey();
|
||||
verify(mInjected, never()).reportMetric(anyBoolean());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void loadRebootEscrowDataIfAvailable_RestoreUnsuccessful_Failure() throws Exception {
|
||||
when(mInjected.getBootCount()).thenReturn(0);
|
||||
|
||||
RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
|
||||
mService.setRebootEscrowListener(mockListener);
|
||||
mService.prepareRebootEscrow();
|
||||
|
||||
clearInvocations(mRebootEscrow);
|
||||
mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
|
||||
verify(mockListener).onPreparedForReboot(eq(true));
|
||||
|
||||
verify(mRebootEscrow, never()).storeKey(any());
|
||||
|
||||
assertTrue(mService.armRebootEscrowIfNeeded());
|
||||
verify(mRebootEscrow).storeKey(any());
|
||||
|
||||
assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
|
||||
assertFalse(mStorage.hasRebootEscrow(NONSECURE_SECONDARY_USER_ID));
|
||||
|
||||
// pretend reboot happens here.
|
||||
|
||||
when(mInjected.getBootCount()).thenReturn(1);
|
||||
ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
|
||||
doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture());
|
||||
when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> new byte[32]);
|
||||
mService.loadRebootEscrowDataIfAvailable();
|
||||
verify(mRebootEscrow).retrieveKey();
|
||||
assertFalse(metricsSuccessCaptor.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user