summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Rubin Xu <rubinxu@google.com> 2017-01-25 18:38:28 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2017-01-25 18:38:31 +0000
commite7beedff6e02cf11bce748d1aa73c848c53bbccd (patch)
treebfe940d884f23d89993694d3ab61e03128a0bc6b
parentb3e3100489051ba0ccd286e896605ec5beaf135d (diff)
parent0cbc19e4a66f7db51596b57ca91afc6f5b27f3b4 (diff)
Merge "Add unit tests for LockSettingsService"
-rw-r--r--core/java/android/service/gatekeeper/GateKeeperResponse.java48
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java4
-rw-r--r--services/core/java/com/android/server/LockSettingsService.java164
-rw-r--r--services/core/java/com/android/server/LockSettingsShellCommand.java3
-rw-r--r--services/core/java/com/android/server/LockSettingsStorage.java19
-rw-r--r--services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java138
-rw-r--r--services/tests/servicestests/src/com/android/server/LockSettingsServiceTestable.java116
-rw-r--r--services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java228
-rw-r--r--services/tests/servicestests/src/com/android/server/LockSettingsStorageTestable.java49
-rw-r--r--services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java53
-rw-r--r--services/tests/servicestests/src/com/android/server/MockGateKeeperService.java185
-rw-r--r--services/tests/servicestests/src/com/android/server/MockLockSettingsContext.java52
-rw-r--r--services/tests/servicestests/src/com/android/server/MockStorageManager.java475
13 files changed, 1420 insertions, 114 deletions
diff --git a/core/java/android/service/gatekeeper/GateKeeperResponse.java b/core/java/android/service/gatekeeper/GateKeeperResponse.java
index a512957d6040..287dc76a9b01 100644
--- a/core/java/android/service/gatekeeper/GateKeeperResponse.java
+++ b/core/java/android/service/gatekeeper/GateKeeperResponse.java
@@ -19,6 +19,8 @@ package android.service.gatekeeper;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.annotations.VisibleForTesting;
+
/**
* Response object for a GateKeeper verification request.
* @hide
@@ -35,12 +37,28 @@ public final class GateKeeperResponse implements Parcelable {
private byte[] mPayload;
private boolean mShouldReEnroll;
+ /** Default constructor for response with generic response code **/
private GateKeeperResponse(int responseCode) {
mResponseCode = responseCode;
}
- private GateKeeperResponse(int responseCode, int timeout) {
- mResponseCode = responseCode;
+ @VisibleForTesting
+ public static GateKeeperResponse createGenericResponse(int responseCode) {
+ return new GateKeeperResponse(responseCode);
+ }
+
+ private static GateKeeperResponse createRetryResponse(int timeout) {
+ GateKeeperResponse response = new GateKeeperResponse(RESPONSE_RETRY);
+ response.mTimeout = timeout;
+ return response;
+ }
+
+ @VisibleForTesting
+ public static GateKeeperResponse createOkResponse(byte[] payload, boolean shouldReEnroll) {
+ GateKeeperResponse response = new GateKeeperResponse(RESPONSE_OK);
+ response.mPayload = payload;
+ response.mShouldReEnroll = shouldReEnroll;
+ return response;
}
@Override
@@ -53,17 +71,20 @@ public final class GateKeeperResponse implements Parcelable {
@Override
public GateKeeperResponse createFromParcel(Parcel source) {
int responseCode = source.readInt();
- GateKeeperResponse response = new GateKeeperResponse(responseCode);
+ final GateKeeperResponse response;
if (responseCode == RESPONSE_RETRY) {
- response.setTimeout(source.readInt());
+ response = createRetryResponse(source.readInt());
} else if (responseCode == RESPONSE_OK) {
- response.setShouldReEnroll(source.readInt() == 1);
+ final boolean shouldReEnroll = source.readInt() == 1;
+ byte[] payload = null;
int size = source.readInt();
if (size > 0) {
- byte[] payload = new byte[size];
+ payload = new byte[size];
source.readByteArray(payload);
- response.setPayload(payload);
}
+ response = createOkResponse(payload, shouldReEnroll);
+ } else {
+ response = createGenericResponse(responseCode);
}
return response;
}
@@ -104,17 +125,4 @@ public final class GateKeeperResponse implements Parcelable {
public int getResponseCode() {
return mResponseCode;
}
-
- private void setTimeout(int timeout) {
- mTimeout = timeout;
- }
-
- private void setShouldReEnroll(boolean shouldReEnroll) {
- mShouldReEnroll = shouldReEnroll;
- }
-
- private void setPayload(byte[] payload) {
- mPayload = payload;
- }
-
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index ece5540443ce..58fb145db556 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -43,6 +43,7 @@ import android.text.TextUtils;
import android.util.Log;
import android.util.SparseIntArray;
+import com.android.internal.annotations.VisibleForTesting;
import com.google.android.collect.Lists;
import libcore.util.HexEncoding;
@@ -239,7 +240,8 @@ public class LockPatternUtils {
mHandler = looper != null ? new Handler(looper) : null;
}
- private ILockSettings getLockSettings() {
+ @VisibleForTesting
+ public ILockSettings getLockSettings() {
if (mLockSettingsService == null) {
ILockSettings service = ILockSettings.Stub.asInterface(
ServiceManager.getService("lock_settings"));
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index 2d40e8eebdb5..8ef34dce438b 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -23,6 +23,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR
import android.annotation.UserIdInt;
import android.app.ActivityManager;
+import android.app.IActivityManager;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
@@ -70,6 +71,7 @@ import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.ICheckCredentialProgressCallback;
import com.android.internal.widget.ILockSettings;
@@ -123,20 +125,23 @@ public class LockSettingsService extends ILockSettings.Stub {
private final Object mSeparateChallengeLock = new Object();
+ private final Injector mInjector;
private final Context mContext;
private final Handler mHandler;
- private final LockSettingsStorage mStorage;
+ @VisibleForTesting
+ protected final LockSettingsStorage mStorage;
private final LockSettingsStrongAuth mStrongAuth;
private final SynchronizedStrongAuthTracker mStrongAuthTracker;
- private LockPatternUtils mLockPatternUtils;
- private boolean mFirstCallToVold;
- private IGateKeeperService mGateKeeperService;
- private NotificationManager mNotificationManager;
- private UserManager mUserManager;
+ private final LockPatternUtils mLockPatternUtils;
+ private final NotificationManager mNotificationManager;
+ private final UserManager mUserManager;
+ private final IActivityManager mActivityManager;
- private final KeyStore mKeyStore = KeyStore.getInstance();
+ private final KeyStore mKeyStore;
+ private boolean mFirstCallToVold;
+ protected IGateKeeperService mGateKeeperService;
/**
* The UIDs that are used for system credential storage in keystore.
*/
@@ -177,7 +182,9 @@ public class LockSettingsService extends ILockSettings.Stub {
}
}
- private class SynchronizedStrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
+ @VisibleForTesting
+ protected static class SynchronizedStrongAuthTracker
+ extends LockPatternUtils.StrongAuthTracker {
public SynchronizedStrongAuthTracker(Context context) {
super(context);
}
@@ -196,8 +203,8 @@ public class LockSettingsService extends ILockSettings.Stub {
}
}
- void register() {
- mStrongAuth.registerStrongAuthTracker(this.mStub);
+ void register(LockSettingsStrongAuth strongAuth) {
+ strongAuth.registerStrongAuthTracker(this.mStub);
}
}
@@ -211,7 +218,7 @@ public class LockSettingsService extends ILockSettings.Stub {
public void tieManagedProfileLockIfNecessary(int managedUserId, String managedUserPassword) {
if (DEBUG) Slog.v(TAG, "Check child profile lock for user: " + managedUserId);
// Only for managed profile
- if (!UserManager.get(mContext).getUserInfo(managedUserId).isManagedProfile()) {
+ if (!mUserManager.getUserInfo(managedUserId).isManagedProfile()) {
return;
}
// Do not tie managed profile when work challenge is enabled
@@ -258,38 +265,103 @@ public class LockSettingsService extends ILockSettings.Stub {
}
}
+ static class Injector {
+
+ protected Context mContext;
+
+ public Injector(Context context) {
+ mContext = context;
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ public Handler getHandler() {
+ return new Handler();
+ }
+
+ public LockSettingsStorage getStorage() {
+ final LockSettingsStorage storage = new LockSettingsStorage(mContext);
+ storage.setDatabaseOnCreateCallback(new LockSettingsStorage.Callback() {
+ @Override
+ public void initialize(SQLiteDatabase db) {
+ // Get the lockscreen default from a system property, if available
+ boolean lockScreenDisable = SystemProperties.getBoolean(
+ "ro.lockscreen.disable.default", false);
+ if (lockScreenDisable) {
+ storage.writeKeyValue(db, LockPatternUtils.DISABLE_LOCKSCREEN_KEY, "1", 0);
+ }
+ }
+ });
+ return storage;
+ }
+
+ public LockSettingsStrongAuth getStrongAuth() {
+ return new LockSettingsStrongAuth(mContext);
+ }
+
+ public SynchronizedStrongAuthTracker getStrongAuthTracker() {
+ return new SynchronizedStrongAuthTracker(mContext);
+ }
+
+ public IActivityManager getActivityManager() {
+ return ActivityManager.getService();
+ }
+
+ public LockPatternUtils getLockPatternUtils() {
+ return new LockPatternUtils(mContext);
+ }
+
+ public NotificationManager getNotificationManager() {
+ return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ }
+
+ public UserManager getUserManager() {
+ return (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ }
+
+ public KeyStore getKeyStore() {
+ return KeyStore.getInstance();
+ }
+
+ public IStorageManager getStorageManager() {
+ final IBinder service = ServiceManager.getService("mount");
+ if (service != null) {
+ return IStorageManager.Stub.asInterface(service);
+ }
+ return null;
+ }
+ }
+
public LockSettingsService(Context context) {
- mContext = context;
- mHandler = new Handler();
- mStrongAuth = new LockSettingsStrongAuth(context);
- // Open the database
+ this(new Injector(context));
+ }
- mLockPatternUtils = new LockPatternUtils(context);
+ @VisibleForTesting
+ protected LockSettingsService(Injector injector) {
+ mInjector = injector;
+ mContext = injector.getContext();
+ mKeyStore = injector.getKeyStore();
+ mHandler = injector.getHandler();
+ mStrongAuth = injector.getStrongAuth();
+ mActivityManager = injector.getActivityManager();
+
+ mLockPatternUtils = injector.getLockPatternUtils();
mFirstCallToVold = true;
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_USER_ADDED);
filter.addAction(Intent.ACTION_USER_STARTING);
filter.addAction(Intent.ACTION_USER_REMOVED);
- mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null);
-
- mStorage = new LockSettingsStorage(context, new LockSettingsStorage.Callback() {
- @Override
- public void initialize(SQLiteDatabase db) {
- // Get the lockscreen default from a system property, if available
- boolean lockScreenDisable = SystemProperties.getBoolean(
- "ro.lockscreen.disable.default", false);
- if (lockScreenDisable) {
- mStorage.writeKeyValue(db, LockPatternUtils.DISABLE_LOCKSCREEN_KEY, "1", 0);
- }
- }
- });
- mNotificationManager = (NotificationManager)
- mContext.getSystemService(Context.NOTIFICATION_SERVICE);
- mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- mStrongAuthTracker = new SynchronizedStrongAuthTracker(mContext);
- mStrongAuthTracker.register();
+ injector.getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter,
+ null, null);
+ mStorage = injector.getStorage();
+ mNotificationManager = injector.getNotificationManager();
+ mUserManager = injector.getUserManager();
+ mStrongAuthTracker = injector.getStrongAuthTracker();
+ mStrongAuthTracker.register(mStrongAuth);
}
/**
@@ -748,7 +820,8 @@ public class LockSettingsService extends ILockSettings.Stub {
ks.unlock(userHandle, password);
}
- private String getDecryptedPasswordForTiedProfile(int userId)
+ @VisibleForTesting
+ protected String getDecryptedPasswordForTiedProfile(int userId)
throws KeyStoreException, UnrecoverableKeyException,
NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException,
@@ -814,7 +887,7 @@ public class LockSettingsService extends ILockSettings.Stub {
};
try {
- ActivityManager.getService().unlockUser(userId, token, secret, listener);
+ mActivityManager.unlockUser(userId, token, secret, listener);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
@@ -961,7 +1034,8 @@ public class LockSettingsService extends ILockSettings.Stub {
}
}
- private void tieProfileLockToParent(int userId, String password) {
+ @VisibleForTesting
+ protected void tieProfileLockToParent(int userId, String password) {
if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + userId);
byte[] randomLockSeed = password.getBytes(StandardCharsets.UTF_8);
byte[] encryptionResult;
@@ -1085,8 +1159,8 @@ public class LockSettingsService extends ILockSettings.Stub {
private void addUserKeyAuth(int userId, byte[] token, byte[] secret)
throws RemoteException {
- final UserInfo userInfo = UserManager.get(mContext).getUserInfo(userId);
- final IStorageManager storageManager = getStorageManager();
+ final UserInfo userInfo = mUserManager.getUserInfo(userId);
+ final IStorageManager storageManager = mInjector.getStorageManager();
final long callingId = Binder.clearCallingIdentity();
try {
storageManager.addUserKeyAuth(userId, userInfo.serialNumber, token, secret);
@@ -1097,7 +1171,7 @@ public class LockSettingsService extends ILockSettings.Stub {
private void fixateNewestUserKeyAuth(int userId)
throws RemoteException {
- final IStorageManager storageManager = getStorageManager();
+ final IStorageManager storageManager = mInjector.getStorageManager();
final long callingId = Binder.clearCallingIdentity();
try {
storageManager.fixateNewestUserKeyAuth(userId);
@@ -1396,7 +1470,7 @@ public class LockSettingsService extends ILockSettings.Stub {
// we should, within the first minute of decrypting the phone if this
// service can't connect to vold, it restarts, and then the new instance
// does successfully connect.
- final IStorageManager service = getStorageManager();
+ final IStorageManager service = mInjector.getStorageManager();
String password;
long identity = Binder.clearCallingIdentity();
try {
@@ -1561,14 +1635,6 @@ public class LockSettingsService extends ILockSettings.Stub {
Secure.LOCK_SCREEN_OWNER_INFO
};
- private IStorageManager getStorageManager() {
- final IBinder service = ServiceManager.getService("mount");
- if (service != null) {
- return IStorageManager.Stub.asInterface(service);
- }
- return null;
- }
-
private class GateKeeperDiedRecipient implements IBinder.DeathRecipient {
@Override
public void binderDied() {
diff --git a/services/core/java/com/android/server/LockSettingsShellCommand.java b/services/core/java/com/android/server/LockSettingsShellCommand.java
index f72663aed140..e1312519480f 100644
--- a/services/core/java/com/android/server/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/LockSettingsShellCommand.java
@@ -77,7 +77,8 @@ class LockSettingsShellCommand extends ShellCommand {
}
return 0;
} catch (Exception e) {
- getErrPrintWriter().println("Error while executing command: " + e);
+ getErrPrintWriter().println("Error while executing command: " + cmd);
+ e.printStackTrace(getErrPrintWriter());
return -1;
}
}
diff --git a/services/core/java/com/android/server/LockSettingsStorage.java b/services/core/java/com/android/server/LockSettingsStorage.java
index 3d973a04c212..c858036eb024 100644
--- a/services/core/java/com/android/server/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/LockSettingsStorage.java
@@ -119,9 +119,13 @@ class LockSettingsStorage {
boolean isBaseZeroPattern;
}
- public LockSettingsStorage(Context context, Callback callback) {
+ public LockSettingsStorage(Context context) {
mContext = context;
- mOpenHelper = new DatabaseHelper(context, callback);
+ mOpenHelper = new DatabaseHelper(context);
+ }
+
+ public void setDatabaseOnCreateCallback(Callback callback) {
+ mOpenHelper.setCallback(callback);
}
public void writeKeyValue(String key, String value, int userId) {
@@ -472,11 +476,14 @@ class LockSettingsStorage {
private static final int DATABASE_VERSION = 2;
- private final Callback mCallback;
+ private Callback mCallback;
- public DatabaseHelper(Context context, Callback callback) {
+ public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
setWriteAheadLoggingEnabled(true);
+ }
+
+ public void setCallback(Callback callback) {
mCallback = callback;
}
@@ -492,7 +499,9 @@ class LockSettingsStorage {
@Override
public void onCreate(SQLiteDatabase db) {
createTable(db);
- mCallback.initialize(db);
+ if (mCallback != null) {
+ mCallback.initialize(db);
+ }
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java
new file mode 100644
index 000000000000..c89d35c1d84e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.IActivityManager;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.IProgressListener;
+import android.os.RemoteException;
+import android.os.UserManager;
+import android.os.storage.IStorageManager;
+import android.security.KeyStore;
+import android.service.gatekeeper.GateKeeperResponse;
+import android.service.gatekeeper.IGateKeeperService;
+import android.test.AndroidTestCase;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.VerifyCredentialResponse;
+import com.android.server.LockSettingsService.SynchronizedStrongAuthTracker;
+import com.android.server.LockSettingsStorage.CredentialHash;
+import com.android.server.MockGateKeeperService.AuthToken;
+import com.android.server.MockGateKeeperService.VerifyHandle;
+
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.File;
+import java.util.Arrays;
+
+
+public class BaseLockSettingsServiceTests extends AndroidTestCase {
+ protected static final int PRIMARY_USER_ID = 0;
+ protected static final int MANAGED_PROFILE_USER_ID = 12;
+ protected static final int SECONDARY_USER_ID = 20;
+
+ private static final UserInfo PRIMARY_USER_INFO = new UserInfo(PRIMARY_USER_ID, null, null,
+ UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
+ private static final UserInfo MANAGED_PROFILE_INFO = new UserInfo(MANAGED_PROFILE_USER_ID, null,
+ null, UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_MANAGED_PROFILE);
+ private static final UserInfo SECONDARY_USER_INFO = new UserInfo(SECONDARY_USER_ID, null, null,
+ UserInfo.FLAG_INITIALIZED);
+
+ LockSettingsService mService;
+
+ MockLockSettingsContext mContext;
+ LockSettingsStorageTestable mStorage;
+
+ LockPatternUtils mLockPatternUtils;
+ MockGateKeeperService mGateKeeperService;
+ NotificationManager mNotificationManager;
+ UserManager mUserManager;
+ MockStorageManager mStorageManager;
+ IActivityManager mActivityManager;
+
+ KeyStore mKeyStore;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mLockPatternUtils = mock(LockPatternUtils.class);
+ mGateKeeperService = new MockGateKeeperService();
+ mNotificationManager = mock(NotificationManager.class);
+ mUserManager = mock(UserManager.class);
+ mStorageManager = new MockStorageManager();
+ mActivityManager = mock(IActivityManager.class);
+ mContext = new MockLockSettingsContext(getContext(), mUserManager, mNotificationManager);
+ mStorage = new LockSettingsStorageTestable(mContext,
+ new File(getContext().getFilesDir(), "locksettings"));
+ File storageDir = mStorage.mStorageDir;
+ if (storageDir.exists()) {
+ FileUtils.deleteContents(storageDir);
+ } else {
+ storageDir.mkdirs();
+ }
+
+ mService = new LockSettingsServiceTestable(mContext, mLockPatternUtils,
+ mStorage, mGateKeeperService, mKeyStore, mStorageManager, mActivityManager);
+ when(mUserManager.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(PRIMARY_USER_INFO);
+ when(mUserManager.getProfiles(eq(PRIMARY_USER_ID))).thenReturn(Arrays.asList(
+ new UserInfo[] {PRIMARY_USER_INFO, MANAGED_PROFILE_INFO}));
+ when(mUserManager.getUserInfo(eq(MANAGED_PROFILE_USER_ID))).thenReturn(
+ MANAGED_PROFILE_INFO);
+ when(mUserManager.getProfileParent(eq(MANAGED_PROFILE_USER_ID))).thenReturn(
+ PRIMARY_USER_INFO);
+ when(mUserManager.getUserInfo(eq(SECONDARY_USER_ID))).thenReturn(SECONDARY_USER_INFO);
+
+ when(mActivityManager.unlockUser(anyInt(), any(), any(), any())).thenAnswer(
+ new Answer<Boolean>() {
+ @Override
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ Object[] args = invocation.getArguments();
+ mStorageManager.unlockUser((int)args[0], (byte[])args[2],
+ (IProgressListener) args[3]);
+ return true;
+ }
+ });
+
+ when(mLockPatternUtils.getLockSettings()).thenReturn(mService);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ mStorage.closeDatabase();
+ File db = getContext().getDatabasePath("locksettings.db");
+ assertTrue(!db.exists() || db.delete());
+
+ File storageDir = mStorage.mStorageDir;
+ assertTrue(FileUtils.deleteContents(storageDir));
+ }
+}
+
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTestable.java
new file mode 100644
index 000000000000..613ec0be0919
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTestable.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server;
+
+import static org.mockito.Mockito.mock;
+
+import android.app.IActivityManager;
+import android.content.Context;
+import android.os.Handler;
+import android.os.storage.IStorageManager;
+import android.security.KeyStore;
+import android.service.gatekeeper.IGateKeeperService;
+
+import com.android.internal.widget.LockPatternUtils;
+
+import java.io.FileNotFoundException;
+
+public class LockSettingsServiceTestable extends LockSettingsService {
+
+ private static class MockInjector extends LockSettingsService.Injector {
+
+ private LockSettingsStorage mLockSettingsStorage;
+ private KeyStore mKeyStore;
+ private IActivityManager mActivityManager;
+ private LockPatternUtils mLockPatternUtils;
+ private IStorageManager mStorageManager;
+
+ public MockInjector(Context context, LockSettingsStorage storage, KeyStore keyStore,
+ IActivityManager activityManager, LockPatternUtils lockPatternUtils,
+ IStorageManager storageManager) {
+ super(context);
+ mLockSettingsStorage = storage;
+ mKeyStore = keyStore;
+ mActivityManager = activityManager;
+ mLockPatternUtils = lockPatternUtils;
+ mStorageManager = storageManager;
+ }
+
+ @Override
+ public Handler getHandler() {
+ return mock(Handler.class);
+ }
+
+ @Override
+ public LockSettingsStorage getStorage() {
+ return mLockSettingsStorage;
+ }
+
+ @Override
+ public LockSettingsStrongAuth getStrongAuth() {
+ return mock(LockSettingsStrongAuth.class);
+ }
+
+ @Override
+ public SynchronizedStrongAuthTracker getStrongAuthTracker() {
+ return mock(SynchronizedStrongAuthTracker.class);
+ }
+
+ @Override
+ public IActivityManager getActivityManager() {
+ return mActivityManager;
+ }
+
+ @Override
+ public LockPatternUtils getLockPatternUtils() {
+ return mLockPatternUtils;
+ }
+
+ @Override
+ public KeyStore getKeyStore() {
+ return mKeyStore;
+ }
+
+ @Override
+ public IStorageManager getStorageManager() {
+ return mStorageManager;
+ }
+ }
+
+ protected LockSettingsServiceTestable(Context context, LockPatternUtils lockPatternUtils,
+ LockSettingsStorage storage, IGateKeeperService gatekeeper, KeyStore keystore,
+ IStorageManager storageManager, IActivityManager mActivityManager) {
+ super(new MockInjector(context, storage, keystore, mActivityManager, lockPatternUtils,
+ storageManager));
+ mGateKeeperService = gatekeeper;
+ }
+
+ @Override
+ protected void tieProfileLockToParent(int userId, String password) {
+ mStorage.writeChildProfileLock(userId, password.getBytes());
+ }
+
+ @Override
+ protected String getDecryptedPasswordForTiedProfile(int userId) throws FileNotFoundException {
+ byte[] storedData = mStorage.readChildProfileLock(userId);
+ if (storedData == null) {
+ throw new FileNotFoundException("Child profile lock file not found");
+ }
+ return new String(storedData);
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java
new file mode 100644
index 000000000000..4c2e17172e76
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server;
+
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+
+import android.os.RemoteException;
+import android.service.gatekeeper.GateKeeperResponse;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.VerifyCredentialResponse;
+import com.android.server.LockSettingsStorage.CredentialHash;
+import com.android.server.MockGateKeeperService.VerifyHandle;
+
+/**
+ * runtest frameworks-services -c com.android.server.LockSettingsServiceTests
+ */
+public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void testCreatePasswordPrimaryUser() throws RemoteException {
+ testCreateCredential(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD);
+ }
+
+ public void testCreatePatternPrimaryUser() throws RemoteException {
+ testCreateCredential(PRIMARY_USER_ID, "123456789", CREDENTIAL_TYPE_PATTERN);
+ }
+
+ public void testChangePasswordPrimaryUser() throws RemoteException {
+ testChangeCredentials(PRIMARY_USER_ID, "78963214", CREDENTIAL_TYPE_PATTERN,
+ "asdfghjk", CREDENTIAL_TYPE_PASSWORD);
+ }
+
+ public void testChangePatternPrimaryUser() throws RemoteException {
+ testChangeCredentials(PRIMARY_USER_ID, "!£$%^&*(())", CREDENTIAL_TYPE_PASSWORD,
+ "1596321", CREDENTIAL_TYPE_PATTERN);
+ }
+
+ public void testChangePasswordFailPrimaryUser() throws RemoteException {
+ final long sid = 1234;
+ final String FAILED_MESSAGE = "Failed to enroll password";
+ initializeStorageWithCredential(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD, sid);
+
+ try {
+ mService.setLockCredential("newpwd", CREDENTIAL_TYPE_PASSWORD, "badpwd",
+ PRIMARY_USER_ID);
+ fail("Did not fail when enrolling using incorrect credential");
+ } catch (RemoteException expected) {
+ assertTrue(expected.getMessage().equals(FAILED_MESSAGE));
+ }
+ try {
+ mService.setLockCredential("newpwd", CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+ fail("Did not fail when enrolling using incorrect credential");
+ } catch (RemoteException expected) {
+ assertTrue(expected.getMessage().equals(FAILED_MESSAGE));
+ }
+ assertVerifyCredentials(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD, sid);
+ }
+
+ public void testClearPasswordPrimaryUser() throws RemoteException {
+ final String PASSWORD = "password";
+ initializeStorageWithCredential(PRIMARY_USER_ID, PASSWORD, CREDENTIAL_TYPE_PASSWORD, 1234);
+ mService.setLockCredential(null, CREDENTIAL_TYPE_NONE, PASSWORD, PRIMARY_USER_ID);
+ assertFalse(mService.havePassword(PRIMARY_USER_ID));
+ assertFalse(mService.havePattern(PRIMARY_USER_ID));
+ assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ }
+
+ public void testManagedProfileUnifiedChallenge() throws RemoteException {
+ final String UnifiedPassword = "testManagedProfileUnifiedChallenge-pwd";
+ mService.setLockCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+ PRIMARY_USER_ID);
+ mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
+ final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+ final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
+ assertTrue(primarySid != 0);
+ assertTrue(profileSid != 0);
+ assertTrue(profileSid != primarySid);
+
+ // clear auth token and wait for verify challenge from primary user to re-generate it.
+ mGateKeeperService.clearAuthToken(MANAGED_PROFILE_USER_ID);
+ // verify credential
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+ UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+ .getResponseCode());
+
+ // Verify that we have a new auth token for the profile
+ assertNotNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
+ assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+
+ /* Currently in LockSettingsService.setLockCredential, unlockUser() is called with the new
+ * credential as part of verifyCredential() before the new credential is committed in
+ * StorageManager. So we relax the check in our mock StorageManager to allow that.
+ */
+ mStorageManager.setIgnoreBadUnlock(true);
+ // Change primary password and verify that profile SID remains
+ mService.setLockCredential("pwd", LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+ UnifiedPassword, PRIMARY_USER_ID);
+ mStorageManager.setIgnoreBadUnlock(false);
+ assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+ }
+
+ public void testManagedProfileSeparateChallenge() throws RemoteException {
+ final String primaryPassword = "testManagedProfileSeparateChallenge-primary";
+ final String profilePassword = "testManagedProfileSeparateChallenge-profile";
+ mService.setLockCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+ PRIMARY_USER_ID);
+ /* Currently in LockSettingsService.setLockCredential, unlockUser() is called with the new
+ * credential as part of verifyCredential() before the new credential is committed in
+ * StorageManager. So we relax the check in our mock StorageManager to allow that.
+ */
+ mStorageManager.setIgnoreBadUnlock(true);
+ mService.setLockCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+ MANAGED_PROFILE_USER_ID);
+ mStorageManager.setIgnoreBadUnlock(false);
+
+ final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+ final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
+ assertTrue(primarySid != 0);
+ assertTrue(profileSid != 0);
+ assertTrue(profileSid != primarySid);
+
+ // clear auth token and make sure verify challenge from primary user does not regenerate it.
+ mGateKeeperService.clearAuthToken(MANAGED_PROFILE_USER_ID);
+ // verify primary credential
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+ primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+ .getResponseCode());
+ assertNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
+
+ // verify profile credential
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+ profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
+ MANAGED_PROFILE_USER_ID).getResponseCode());
+ assertNotNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
+ assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+
+ // Change primary credential and make sure we don't affect profile
+ mStorageManager.setIgnoreBadUnlock(true);
+ mService.setLockCredential("pwd", LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+ primaryPassword, PRIMARY_USER_ID);
+ mStorageManager.setIgnoreBadUnlock(false);
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+ profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
+ MANAGED_PROFILE_USER_ID).getResponseCode());
+ assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+ }
+
+ private void testCreateCredential(int userId, String credential, int type)
+ throws RemoteException {
+ mService.setLockCredential(credential, type, null, userId);
+ assertVerifyCredentials(userId, credential, type, -1);
+ }
+
+ private void testChangeCredentials(int userId, String newCredential, int newType,
+ String oldCredential, int oldType) throws RemoteException {
+ final long sid = 1234;
+ initializeStorageWithCredential(userId, oldCredential, oldType, sid);
+ mService.setLockCredential(newCredential, newType, oldCredential, userId);
+ assertVerifyCredentials(userId, newCredential, newType, sid);
+ }
+
+ private void assertVerifyCredentials(int userId, String credential, int type, long sid)
+ throws RemoteException{
+ final long challenge = 54321;
+ VerifyCredentialResponse response = mService.verifyCredential(credential, type, challenge,
+ userId);
+
+ assertEquals(GateKeeperResponse.RESPONSE_OK, response.getResponseCode());
+ if (sid != -1) assertEquals(sid, mGateKeeperService.getSecureUserId(userId));
+ final int incorrectType;
+ if (type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) {
+ assertTrue(mService.havePassword(userId));
+ assertFalse(mService.havePattern(userId));
+ incorrectType = LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+ } else if (type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN){
+ assertFalse(mService.havePassword(userId));
+ assertTrue(mService.havePattern(userId));
+ incorrectType = LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+ } else {
+ assertFalse(mService.havePassword(userId));
+ assertFalse(mService.havePassword(userId));
+ incorrectType = LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+ }
+ // check for bad type
+ assertEquals(GateKeeperResponse.RESPONSE_ERROR, mService.verifyCredential(credential,
+ incorrectType, challenge, userId).getResponseCode());
+ // check for bad credential
+ assertEquals(GateKeeperResponse.RESPONSE_ERROR, mService.verifyCredential("0" + credential,
+ type, challenge, userId).getResponseCode());
+ }
+
+ private void initializeStorageWithCredential(int userId, String credential, int type, long sid) {
+ byte[] oldHash = new VerifyHandle(credential.getBytes(), sid).toBytes();
+ if (type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) {
+ mStorage.writeCredentialHash(CredentialHash.create(oldHash,
+ LockPatternUtils.CREDENTIAL_TYPE_PASSWORD), userId);
+ } else {
+ mStorage.writeCredentialHash(CredentialHash.create(oldHash,
+ LockPatternUtils.CREDENTIAL_TYPE_PATTERN), userId);
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTestable.java
new file mode 100644
index 000000000000..e81b02f071a8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTestable.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server;
+
+import android.content.Context;
+
+import java.io.File;
+
+public class LockSettingsStorageTestable extends LockSettingsStorage {
+
+ public File mStorageDir;
+
+ public LockSettingsStorageTestable(Context context, File storageDir) {
+ super(context);
+ mStorageDir = storageDir;
+ }
+
+ @Override
+ String getLockPatternFilename(int userId) {
+ return new File(mStorageDir,
+ super.getLockPatternFilename(userId).replace('/', '-')).getAbsolutePath();
+ }
+
+ @Override
+ String getLockPasswordFilename(int userId) {
+ return new File(mStorageDir,
+ super.getLockPasswordFilename(userId).replace('/', '-')).getAbsolutePath();
+ }
+
+ @Override
+ String getChildProfileLockFile(int userId) {
+ return new File(mStorageDir,
+ super.getChildProfileLockFile(userId).replace('/', '-')).getAbsolutePath();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java
index 9d521533d773..d110feaab3c9 100644
--- a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java
@@ -20,6 +20,7 @@ import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import android.app.NotificationManager;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.UserInfo;
@@ -60,46 +61,22 @@ public class LockSettingsStorageTests extends AndroidTestCase {
assertTrue(FileUtils.deleteContents(mStorageDir));
assertTrue(!mDb.exists() || mDb.delete());
- final Context ctx = getContext();
final UserManager mockUserManager = mock(UserManager.class);
// User 2 is a profile of user 1.
when(mockUserManager.getProfileParent(eq(2))).thenReturn(new UserInfo(1, "name", 0));
// User 3 is a profile of user 0.
when(mockUserManager.getProfileParent(eq(3))).thenReturn(new UserInfo(0, "name", 0));
- setContext(new ContextWrapper(ctx) {
- @Override
- public Object getSystemService(String name) {
- if (USER_SERVICE.equals(name)) {
- return mockUserManager;
- }
- return super.getSystemService(name);
- }
- });
-
- mStorage = new LockSettingsStorage(getContext(), new LockSettingsStorage.Callback() {
- @Override
- public void initialize(SQLiteDatabase db) {
- mStorage.writeKeyValue(db, "initializedKey", "initialValue", 0);
- }
- }) {
- @Override
- String getLockPatternFilename(int userId) {
- return new File(mStorageDir,
- super.getLockPatternFilename(userId).replace('/', '-')).getAbsolutePath();
- }
- @Override
- String getLockPasswordFilename(int userId) {
- return new File(mStorageDir,
- super.getLockPasswordFilename(userId).replace('/', '-')).getAbsolutePath();
- }
-
- @Override
- String getChildProfileLockFile(int userId) {
- return new File(mStorageDir,
- super.getChildProfileLockFile(userId).replace('/', '-')).getAbsolutePath();
- }
- };
+ MockLockSettingsContext context = new MockLockSettingsContext(getContext(), mockUserManager,
+ mock(NotificationManager.class));
+ mStorage = new LockSettingsStorageTestable(context,
+ new File(getContext().getFilesDir(), "locksettings"));
+ mStorage.setDatabaseOnCreateCallback(new LockSettingsStorage.Callback() {
+ @Override
+ public void initialize(SQLiteDatabase db) {
+ mStorage.writeKeyValue(db, "initializedKey", "initialValue", 0);
+ }
+ });
}
@Override
@@ -323,7 +300,7 @@ public class LockSettingsStorageTests extends AndroidTestCase {
}
public void testFileLocation_Owner() {
- LockSettingsStorage storage = new LockSettingsStorage(getContext(), null);
+ LockSettingsStorage storage = new LockSettingsStorage(getContext());
assertEquals("/data/system/gesture.key", storage.getLegacyLockPatternFilename(0));
assertEquals("/data/system/password.key", storage.getLegacyLockPasswordFilename(0));
@@ -332,21 +309,21 @@ public class LockSettingsStorageTests extends AndroidTestCase {
}
public void testFileLocation_SecondaryUser() {
- LockSettingsStorage storage = new LockSettingsStorage(getContext(), null);
+ LockSettingsStorage storage = new LockSettingsStorage(getContext());
assertEquals("/data/system/users/1/gatekeeper.pattern.key", storage.getLockPatternFilename(1));
assertEquals("/data/system/users/1/gatekeeper.password.key", storage.getLockPasswordFilename(1));
}
public void testFileLocation_ProfileToSecondary() {
- LockSettingsStorage storage = new LockSettingsStorage(getContext(), null);
+ LockSettingsStorage storage = new LockSettingsStorage(getContext());
assertEquals("/data/system/users/2/gatekeeper.pattern.key", storage.getLockPatternFilename(2));
assertEquals("/data/system/users/2/gatekeeper.password.key", storage.getLockPasswordFilename(2));
}
public void testFileLocation_ProfileToOwner() {
- LockSettingsStorage storage = new LockSettingsStorage(getContext(), null);
+ LockSettingsStorage storage = new LockSettingsStorage(getContext());
assertEquals("/data/system/users/3/gatekeeper.pattern.key", storage.getLockPatternFilename(3));
assertEquals("/data/system/users/3/gatekeeper.password.key", storage.getLockPasswordFilename(3));
diff --git a/services/tests/servicestests/src/com/android/server/MockGateKeeperService.java b/services/tests/servicestests/src/com/android/server/MockGateKeeperService.java
new file mode 100644
index 000000000000..15983cada9c2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/MockGateKeeperService.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.service.gatekeeper.GateKeeperResponse;
+import android.service.gatekeeper.IGateKeeperService;
+import android.util.ArrayMap;
+
+import junit.framework.AssertionFailedError;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Random;
+
+public class MockGateKeeperService implements IGateKeeperService {
+ static class VerifyHandle {
+ public byte[] password;
+ public long sid;
+
+ public VerifyHandle(byte[] password, long sid) {
+ this.password = password;
+ this.sid = sid;
+ }
+
+ public VerifyHandle(byte[] handle) {
+ ByteBuffer buffer = ByteBuffer.allocate(handle.length);
+ buffer.put(handle, 0, handle.length);
+ buffer.flip();
+ int version = buffer.get();
+ sid = buffer.getLong();
+ password = new byte[buffer.remaining()];
+ buffer.get(password);
+ }
+
+ public byte[] toBytes() {
+ ByteBuffer buffer = ByteBuffer.allocate(1 + Long.BYTES + password.length);
+ buffer.put((byte)0);
+ buffer.putLong(sid);
+ buffer.put(password);
+ return buffer.array();
+ }
+ }
+
+ static class AuthToken {
+ public long challenge;
+ public long sid;
+
+ public AuthToken(long challenge, long sid) {
+ this.challenge = challenge;
+ this.sid = sid;
+ }
+
+ public AuthToken(byte[] handle) {
+ ByteBuffer buffer = ByteBuffer.allocate(handle.length);
+ buffer.put(handle, 0, handle.length);
+ buffer.flip();
+ int version = buffer.get();
+ challenge = buffer.getLong();
+ sid = buffer.getLong();
+ }
+
+ public byte[] toBytes() {
+ ByteBuffer buffer = ByteBuffer.allocate(1 + Long.BYTES + Long.BYTES);
+ buffer.put((byte)0);
+ buffer.putLong(challenge);
+ buffer.putLong(sid);
+ return buffer.array();
+ }
+ }
+
+ private ArrayMap<Integer, Long> sidMap = new ArrayMap<>();
+ private ArrayMap<Integer, AuthToken> authTokenMap = new ArrayMap<>();
+
+ private ArrayMap<Integer, byte[]> handleMap = new ArrayMap<>();
+
+ @Override
+ public GateKeeperResponse enroll(int uid, byte[] currentPasswordHandle, byte[] currentPassword,
+ byte[] desiredPassword) throws android.os.RemoteException {
+
+ if (currentPasswordHandle != null) {
+ VerifyHandle handle = new VerifyHandle(currentPasswordHandle);
+ if (Arrays.equals(currentPassword, handle.password)) {
+ // Trusted enroll
+ VerifyHandle newHandle = new VerifyHandle(desiredPassword, handle.sid);
+ refreshSid(uid, handle.sid, false);
+ handleMap.put(uid, newHandle.toBytes());
+ return GateKeeperResponse.createOkResponse(newHandle.toBytes(), false);
+ } else {
+ return null;
+ }
+ } else {
+ // Untrusted enroll
+ long newSid = new Random().nextLong();
+ VerifyHandle newHandle = new VerifyHandle(desiredPassword, newSid);
+ refreshSid(uid, newSid, true);
+ handleMap.put(uid, newHandle.toBytes());
+ return GateKeeperResponse.createOkResponse(newHandle.toBytes(), false);
+ }
+ }
+
+ @Override
+ public GateKeeperResponse verify(int uid, byte[] enrolledPasswordHandle,
+ byte[] providedPassword) throws android.os.RemoteException {
+ return verifyChallenge(uid, 0, enrolledPasswordHandle, providedPassword);
+ }
+
+ @Override
+ public GateKeeperResponse verifyChallenge(int uid, long challenge,
+ byte[] enrolledPasswordHandle, byte[] providedPassword) throws RemoteException {
+
+ VerifyHandle handle = new VerifyHandle(enrolledPasswordHandle);
+ if (Arrays.equals(handle.password, providedPassword)) {
+ byte[] knownHandle = handleMap.get(uid);
+ if (knownHandle != null) {
+ if (!Arrays.equals(knownHandle, enrolledPasswordHandle)) {
+ throw new AssertionFailedError("Got correct but obsolete handle");
+ }
+ }
+ refreshSid(uid, handle.sid, false);
+ AuthToken token = new AuthToken(challenge, handle.sid);
+ refreshAuthToken(uid, token);
+ return GateKeeperResponse.createOkResponse(token.toBytes(), false);
+ } else {
+ return GateKeeperResponse.createGenericResponse(GateKeeperResponse.RESPONSE_ERROR);
+ }
+ }
+
+ private void refreshAuthToken(int uid, AuthToken token) {
+ authTokenMap.put(uid, token);
+ }
+
+ public AuthToken getAuthToken(int uid) {
+ return authTokenMap.get(uid);
+ }
+
+ public void clearAuthToken(int uid) {
+ authTokenMap.remove(uid);
+ }
+
+ @Override
+ public IBinder asBinder() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clearSecureUserId(int userId) throws RemoteException {
+ sidMap.remove(userId);
+ }
+
+ @Override
+ public long getSecureUserId(int userId) throws RemoteException {
+ if (sidMap.containsKey(userId)) {
+ return sidMap.get(userId);
+ } else {
+ return 0L;
+ }
+ }
+
+ private void refreshSid(int uid, long sid, boolean force) {
+ if (!sidMap.containsKey(uid) || force) {
+ sidMap.put(uid, sid);
+ } else{
+ if (sidMap.get(uid) != sid) {
+ throw new AssertionFailedError("Inconsistent SID");
+ }
+ }
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/MockLockSettingsContext.java b/services/tests/servicestests/src/com/android/server/MockLockSettingsContext.java
new file mode 100644
index 000000000000..b63936fdffdf
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/MockLockSettingsContext.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server;
+
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.os.UserManager;
+
+public class MockLockSettingsContext extends ContextWrapper {
+
+ private UserManager mUserManager;
+ private NotificationManager mNotificationManager;
+
+ public MockLockSettingsContext(Context base, UserManager userManager,
+ NotificationManager notificationManager) {
+ super(base);
+ mUserManager = userManager;
+ mNotificationManager = notificationManager;
+ }
+
+ @Override
+ public Object getSystemService(String name) {
+ if (USER_SERVICE.equals(name)) {
+ return mUserManager;
+ } else if (NOTIFICATION_SERVICE.equals(name)) {
+ return mNotificationManager;
+ } else {
+ throw new RuntimeException("System service not mocked: " + name);
+ }
+ }
+
+ @Override
+ public void enforceCallingOrSelfPermission(String permission, String message) {
+ // Skip permission checks for unit tests.
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/MockStorageManager.java b/services/tests/servicestests/src/com/android/server/MockStorageManager.java
new file mode 100644
index 000000000000..931651603610
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/MockStorageManager.java
@@ -0,0 +1,475 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server;
+
+import android.content.pm.IPackageMoveObserver;
+import android.os.IBinder;
+import android.os.IProgressListener;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.storage.DiskInfo;
+import android.os.storage.IObbActionListener;
+import android.os.storage.IStorageEventListener;
+import android.os.storage.IStorageManager;
+import android.os.storage.IStorageShutdownObserver;
+import android.os.storage.StorageVolume;
+import android.os.storage.VolumeInfo;
+import android.os.storage.VolumeRecord;
+import android.util.ArrayMap;
+import android.util.Pair;
+
+import junit.framework.AssertionFailedError;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class MockStorageManager implements IStorageManager {
+
+ private ArrayMap<Integer, ArrayList<Pair<byte[], byte[]>>> mAuth = new ArrayMap<>();
+ private boolean mIgnoreBadUnlock;
+
+ @Override
+ public void addUserKeyAuth(int userId, int serialNumber, byte[] token, byte[] secret)
+ throws RemoteException {
+ getUserAuth(userId).add(new Pair<>(token, secret));
+ }
+
+ @Override
+ public void fixateNewestUserKeyAuth(int userId) throws RemoteException {
+ ArrayList<Pair<byte[], byte[]>> auths = mAuth.get(userId);
+ Pair<byte[], byte[]> latest = auths.get(auths.size() - 1);
+ auths.clear();
+ auths.add(latest);
+ }
+
+ private ArrayList<Pair<byte[], byte[]>> getUserAuth(int userId) {
+ if (!mAuth.containsKey(userId)) {
+ ArrayList<Pair<byte[], byte[]>> auths = new ArrayList<Pair<byte[], byte[]>>();
+ auths.add(new Pair(null, null));
+ mAuth.put(userId, auths);
+ }
+ return mAuth.get(userId);
+ }
+
+ public byte[] getUserUnlockToken(int userId) {
+ ArrayList<Pair<byte[], byte[]>> auths = getUserAuth(userId);
+ if (auths.size() != 1) {
+ throw new AssertionFailedError("More than one secret exists");
+ }
+ return auths.get(0).second;
+ }
+
+ public void unlockUser(int userId, byte[] secret, IProgressListener listener)
+ throws RemoteException {
+ listener.onStarted(userId, null);
+ listener.onFinished(userId, null);
+ ArrayList<Pair<byte[], byte[]>> auths = getUserAuth(userId);
+ if (secret != null) {
+ if (auths.size() > 1) {
+ throw new AssertionFailedError("More than one secret exists");
+ }
+ Pair<byte[], byte[]> auth = auths.get(0);
+ if ((!mIgnoreBadUnlock) && auth.second != null && !Arrays.equals(secret, auth.second)) {
+ throw new AssertionFailedError("Invalid secret to unlock user");
+ }
+ } else {
+ if (auths != null && auths.size() > 0) {
+ throw new AssertionFailedError("Cannot unlock encrypted user with empty token");
+ }
+ }
+ }
+
+ public void setIgnoreBadUnlock(boolean ignore) {
+ mIgnoreBadUnlock = ignore;
+ }
+
+ @Override
+ public IBinder asBinder() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void registerListener(IStorageEventListener listener) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void unregisterListener(IStorageEventListener listener) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isUsbMassStorageConnected() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setUsbMassStorageEnabled(boolean enable) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isUsbMassStorageEnabled() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int mountVolume(String mountPoint) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void unmountVolume(String mountPoint, boolean force, boolean removeEncryption)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public int formatVolume(String mountPoint) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int[] getStorageUsers(String path) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getVolumeState(String mountPoint) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int createSecureContainer(String id, int sizeMb, String fstype, String key, int ownerUid,
+ boolean external) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int finalizeSecureContainer(String id) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int destroySecureContainer(String id, boolean force) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int mountSecureContainer(String id, String key, int ownerUid, boolean readOnly)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int unmountSecureContainer(String id, boolean force) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isSecureContainerMounted(String id) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int renameSecureContainer(String oldId, String newId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getSecureContainerPath(String id) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String[] getSecureContainerList() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void shutdown(IStorageShutdownObserver observer) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void finishMediaUpdate() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void mountObb(String rawPath, String canonicalPath, String key, IObbActionListener token,
+ int nonce) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void unmountObb(String rawPath, boolean force, IObbActionListener token, int nonce)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isObbMounted(String rawPath) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getMountedObbPath(String rawPath) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isExternalStorageEmulated() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int decryptStorage(String password) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int encryptStorage(int type, String password) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int changeEncryptionPassword(int type, String password) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public StorageVolume[] getVolumeList(int uid, String packageName, int flags)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getSecureContainerFilesystemPath(String cid) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getEncryptionState() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int verifyEncryptionPassword(String password) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int fixPermissionsSecureContainer(String id, int gid, String filename)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int mkdirs(String callingPkg, String path) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getPasswordType() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getPassword() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clearPassword() throws RemoteException {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public void setField(String field, String contents) throws RemoteException {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public String getField(String field) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int resizeSecureContainer(String id, int sizeMb, String key) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long lastMaintenance() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void runMaintenance() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void waitForAsecScan() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public DiskInfo[] getDisks() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public VolumeInfo[] getVolumes(int flags) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public VolumeRecord[] getVolumeRecords(int flags) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void mount(String volId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void unmount(String volId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void format(String volId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void partitionPublic(String diskId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void partitionPrivate(String diskId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void partitionMixed(String diskId, int ratio) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setVolumeNickname(String fsUuid, String nickname) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setVolumeUserFlags(String fsUuid, int flags, int mask) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void forgetVolume(String fsUuid) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void forgetAllVolumes() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getPrimaryStorageUuid() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long benchmark(String volId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setDebugFlags(int flags, int mask) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void createUserKey(int userId, int serialNumber, boolean ephemeral)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void destroyUserKey(int userId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void unlockUserKey(int userId, int serialNumber, byte[] token, byte[] secret)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void lockUserKey(int userId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isUserKeyUnlocked(int userId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void destroyUserStorage(String volumeUuid, int userId, int flags)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isConvertibleToFBE() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ParcelFileDescriptor mountAppFuse(String name) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void fstrim(int flags) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+}