blob: f1d7da4ee629ccff2b89bab350740e2f0eab63fa [file] [log] [blame]
Amith Yamasani52c489c2012-03-28 11:42:42 -07001/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Jeff Sharkey7a96c392012-11-15 14:01:46 -080017package com.android.server;
Amith Yamasani52c489c2012-03-28 11:42:42 -070018
Adrian Roos230635e2015-01-07 20:50:29 +010019import android.app.admin.DevicePolicyManager;
Amith Yamasani072543f2015-02-13 11:09:45 -080020import android.app.backup.BackupManager;
Adrian Roosb5e47222015-08-14 15:53:06 -070021import android.app.trust.IStrongAuthTracker;
Robin Leef0246a82014-08-13 09:50:25 +010022import android.content.BroadcastReceiver;
Amith Yamasani52c489c2012-03-28 11:42:42 -070023import android.content.ContentResolver;
Amith Yamasani52c489c2012-03-28 11:42:42 -070024import android.content.Context;
Robin Leef0246a82014-08-13 09:50:25 +010025import android.content.Intent;
26import android.content.IntentFilter;
Jim Miller158fe192013-04-17 15:23:55 -070027import android.content.pm.PackageManager;
Jim Miller187ec582013-04-15 18:27:54 -070028import android.content.pm.UserInfo;
Adrian Roos261d5ab2014-10-29 14:42:38 +010029import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE;
Jim Miller187ec582013-04-15 18:27:54 -070030import static android.content.Context.USER_SERVICE;
Svetoslav Ganov6d2c0e52015-06-23 16:33:36 +000031import static android.Manifest.permission.READ_CONTACTS;
Adrian Roos873010d2015-08-25 15:59:00 -070032import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
33
Amith Yamasani52c489c2012-03-28 11:42:42 -070034import android.database.sqlite.SQLiteDatabase;
Amith Yamasani52c489c2012-03-28 11:42:42 -070035import android.os.Binder;
Paul Lawrence945490c2014-03-27 16:37:28 +000036import android.os.IBinder;
Amith Yamasani52c489c2012-03-28 11:42:42 -070037import android.os.RemoteException;
Paul Lawrence945490c2014-03-27 16:37:28 +000038import android.os.storage.IMountService;
39import android.os.ServiceManager;
Amith Yamasanid1645f82012-06-12 11:53:26 -070040import android.os.SystemProperties;
Dianne Hackbornf02b60a2012-08-16 10:48:27 -070041import android.os.UserHandle;
Jim Miller187ec582013-04-15 18:27:54 -070042import android.os.UserManager;
Amith Yamasani52c489c2012-03-28 11:42:42 -070043import android.provider.Settings;
44import android.provider.Settings.Secure;
Jim Miller187ec582013-04-15 18:27:54 -070045import android.provider.Settings.SettingNotFoundException;
Jim Millerde1af082013-09-11 14:58:26 -070046import android.security.KeyStore;
Andres Morales23974272015-05-14 22:42:26 -070047import android.service.gatekeeper.GateKeeperResponse;
Andres Morales8fa56652015-03-31 09:19:50 -070048import android.service.gatekeeper.IGateKeeperService;
Amith Yamasani52c489c2012-03-28 11:42:42 -070049import android.text.TextUtils;
50import android.util.Slog;
51
Amith Yamasani072543f2015-02-13 11:09:45 -080052import com.android.internal.util.ArrayUtils;
Jeff Sharkey7a96c392012-11-15 14:01:46 -080053import com.android.internal.widget.ILockSettings;
54import com.android.internal.widget.LockPatternUtils;
Andres Morales23974272015-05-14 22:42:26 -070055import com.android.internal.widget.VerifyCredentialResponse;
Andres Morales8fa56652015-03-31 09:19:50 -070056import com.android.server.LockSettingsStorage.CredentialHash;
Jeff Sharkey7a96c392012-11-15 14:01:46 -080057
Amith Yamasani52c489c2012-03-28 11:42:42 -070058import java.util.Arrays;
Jim Miller187ec582013-04-15 18:27:54 -070059import java.util.List;
Amith Yamasani52c489c2012-03-28 11:42:42 -070060
61/**
62 * Keeps the lock pattern/password data and related settings for each user.
63 * Used by LockPatternUtils. Needs to be a service because Settings app also needs
64 * to be able to save lockscreen information for secondary users.
65 * @hide
66 */
67public class LockSettingsService extends ILockSettings.Stub {
68
Adrian Roos261d5ab2014-10-29 14:42:38 +010069 private static final String PERMISSION = ACCESS_KEYGUARD_SECURE_STORAGE;
Adrian Roos4f788452014-05-22 20:45:59 +020070
Amith Yamasani52c489c2012-03-28 11:42:42 -070071 private static final String TAG = "LockSettingsService";
72
Amith Yamasani52c489c2012-03-28 11:42:42 -070073 private final Context mContext;
Adrian Roos261d5ab2014-10-29 14:42:38 +010074
75 private final LockSettingsStorage mStorage;
Adrian Roosb5e47222015-08-14 15:53:06 -070076 private final LockSettingsStrongAuth mStrongAuth = new LockSettingsStrongAuth();
Adrian Roos261d5ab2014-10-29 14:42:38 +010077
Jim Millerde1af082013-09-11 14:58:26 -070078 private LockPatternUtils mLockPatternUtils;
Paul Lawrence945490c2014-03-27 16:37:28 +000079 private boolean mFirstCallToVold;
Andres Morales8fa56652015-03-31 09:19:50 -070080 private IGateKeeperService mGateKeeperService;
Amith Yamasani52c489c2012-03-28 11:42:42 -070081
Andres Morales23974272015-05-14 22:42:26 -070082 private interface CredentialUtil {
83 void setCredential(String credential, String savedCredential, int userId)
84 throws RemoteException;
85 byte[] toHash(String credential, int userId);
Andres Morales59ef1262015-06-26 13:56:39 -070086 String adjustForKeystore(String credential);
Andres Morales23974272015-05-14 22:42:26 -070087 }
88
Amith Yamasani52c489c2012-03-28 11:42:42 -070089 public LockSettingsService(Context context) {
90 mContext = context;
91 // Open the database
Jim Millerde1af082013-09-11 14:58:26 -070092
93 mLockPatternUtils = new LockPatternUtils(context);
Paul Lawrence945490c2014-03-27 16:37:28 +000094 mFirstCallToVold = true;
Robin Leef0246a82014-08-13 09:50:25 +010095
96 IntentFilter filter = new IntentFilter();
97 filter.addAction(Intent.ACTION_USER_ADDED);
Adrian Roos3dcae682014-10-29 14:43:56 +010098 filter.addAction(Intent.ACTION_USER_STARTING);
Adrian Roosdb0f76e2015-01-07 22:19:38 +010099 filter.addAction(Intent.ACTION_USER_REMOVED);
Adrian Roosb5e47222015-08-14 15:53:06 -0700100 filter.addAction(Intent.ACTION_USER_PRESENT);
Robin Leef0246a82014-08-13 09:50:25 +0100101 mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100102
103 mStorage = new LockSettingsStorage(context, new LockSettingsStorage.Callback() {
104 @Override
105 public void initialize(SQLiteDatabase db) {
106 // Get the lockscreen default from a system property, if available
107 boolean lockScreenDisable = SystemProperties.getBoolean(
108 "ro.lockscreen.disable.default", false);
109 if (lockScreenDisable) {
110 mStorage.writeKeyValue(db, LockPatternUtils.DISABLE_LOCKSCREEN_KEY, "1", 0);
111 }
112 }
113 });
Amith Yamasani52c489c2012-03-28 11:42:42 -0700114 }
115
Robin Leef0246a82014-08-13 09:50:25 +0100116 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
117 @Override
118 public void onReceive(Context context, Intent intent) {
Robin Lee1096cf82014-09-01 16:52:47 +0100119 if (Intent.ACTION_USER_ADDED.equals(intent.getAction())) {
Chad Brubaker83ce0952015-05-12 13:00:02 -0700120 // Notify keystore that a new user was added.
Robin Leef0246a82014-08-13 09:50:25 +0100121 final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
Robin Lee49d810c2014-09-23 13:50:22 +0100122 final KeyStore ks = KeyStore.getInstance();
Robin Leef0246a82014-08-13 09:50:25 +0100123 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
124 final UserInfo parentInfo = um.getProfileParent(userHandle);
Chad Brubaker83ce0952015-05-12 13:00:02 -0700125 final int parentHandle = parentInfo != null ? parentInfo.id : -1;
126 ks.onUserAdded(userHandle, parentHandle);
Adrian Roos3dcae682014-10-29 14:43:56 +0100127 } else if (Intent.ACTION_USER_STARTING.equals(intent.getAction())) {
128 final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
129 mStorage.prefetchUser(userHandle);
Adrian Roosb5e47222015-08-14 15:53:06 -0700130 } else if (Intent.ACTION_USER_PRESENT.equals(intent.getAction())) {
131 mStrongAuth.reportUnlock(getSendingUserId());
Adrian Roosdb0f76e2015-01-07 22:19:38 +0100132 } else if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
133 final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
134 if (userHandle > 0) {
135 removeUser(userHandle);
136 }
Robin Leef0246a82014-08-13 09:50:25 +0100137 }
138 }
139 };
140
Amith Yamasani52c489c2012-03-28 11:42:42 -0700141 public void systemReady() {
142 migrateOldData();
Andres Morales301ea442015-04-17 09:15:47 -0700143 try {
144 getGateKeeperService();
145 } catch (RemoteException e) {
146 Slog.e(TAG, "Failure retrieving IGateKeeperService", e);
147 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100148 mStorage.prefetchUser(UserHandle.USER_OWNER);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700149 }
150
151 private void migrateOldData() {
152 try {
Jim Miller187ec582013-04-15 18:27:54 -0700153 // These Settings moved before multi-user was enabled, so we only have to do it for the
154 // root user.
155 if (getString("migrated", null, 0) == null) {
156 final ContentResolver cr = mContext.getContentResolver();
157 for (String validSetting : VALID_SETTINGS) {
158 String value = Settings.Secure.getString(cr, validSetting);
159 if (value != null) {
160 setString(validSetting, value, 0);
161 }
162 }
163 // No need to move the password / pattern files. They're already in the right place.
164 setString("migrated", "true", 0);
165 Slog.i(TAG, "Migrated lock settings to new location");
Amith Yamasani52c489c2012-03-28 11:42:42 -0700166 }
167
Jim Miller187ec582013-04-15 18:27:54 -0700168 // These Settings changed after multi-user was enabled, hence need to be moved per user.
169 if (getString("migrated_user_specific", null, 0) == null) {
170 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
171 final ContentResolver cr = mContext.getContentResolver();
172 List<UserInfo> users = um.getUsers();
173 for (int user = 0; user < users.size(); user++) {
Jim Miller2d8ecf9d2013-04-22 17:17:03 -0700174 // Migrate owner info
175 final int userId = users.get(user).id;
176 final String OWNER_INFO = Secure.LOCK_SCREEN_OWNER_INFO;
177 String ownerInfo = Settings.Secure.getStringForUser(cr, OWNER_INFO, userId);
178 if (ownerInfo != null) {
179 setString(OWNER_INFO, ownerInfo, userId);
180 Settings.Secure.putStringForUser(cr, ownerInfo, "", userId);
181 }
Jim Miller187ec582013-04-15 18:27:54 -0700182
Jim Miller2d8ecf9d2013-04-22 17:17:03 -0700183 // Migrate owner info enabled. Note there was a bug where older platforms only
184 // stored this value if the checkbox was toggled at least once. The code detects
185 // this case by handling the exception.
186 final String OWNER_INFO_ENABLED = Secure.LOCK_SCREEN_OWNER_INFO_ENABLED;
187 boolean enabled;
188 try {
189 int ivalue = Settings.Secure.getIntForUser(cr, OWNER_INFO_ENABLED, userId);
190 enabled = ivalue != 0;
191 setLong(OWNER_INFO_ENABLED, enabled ? 1 : 0, userId);
192 } catch (SettingNotFoundException e) {
193 // Setting was never stored. Store it if the string is not empty.
194 if (!TextUtils.isEmpty(ownerInfo)) {
195 setLong(OWNER_INFO_ENABLED, 1, userId);
Jim Miller187ec582013-04-15 18:27:54 -0700196 }
197 }
Jim Miller2d8ecf9d2013-04-22 17:17:03 -0700198 Settings.Secure.putIntForUser(cr, OWNER_INFO_ENABLED, 0, userId);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700199 }
Jim Miller187ec582013-04-15 18:27:54 -0700200 // No need to move the password / pattern files. They're already in the right place.
201 setString("migrated_user_specific", "true", 0);
202 Slog.i(TAG, "Migrated per-user lock settings to new location");
Amith Yamasani52c489c2012-03-28 11:42:42 -0700203 }
Adrian Roos230635e2015-01-07 20:50:29 +0100204
205 // Migrates biometric weak such that the fallback mechanism becomes the primary.
206 if (getString("migrated_biometric_weak", null, 0) == null) {
207 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
208 List<UserInfo> users = um.getUsers();
209 for (int i = 0; i < users.size(); i++) {
210 int userId = users.get(i).id;
211 long type = getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
212 DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
213 userId);
214 long alternateType = getLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
215 DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
216 userId);
217 if (type == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK) {
218 setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
219 alternateType,
220 userId);
221 }
222 setLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
223 DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
224 userId);
225 }
226 setString("migrated_biometric_weak", "true", 0);
227 Slog.i(TAG, "Migrated biometric weak to use the fallback instead");
228 }
Adrian Roos43830582015-04-21 16:04:43 -0700229
230 // Migrates lockscreen.disabled. Prior to M, the flag was ignored when more than one
231 // user was present on the system, so if we're upgrading to M and there is more than one
232 // user we disable the flag to remain consistent.
233 if (getString("migrated_lockscreen_disabled", null, 0) == null) {
234 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
235
236 final List<UserInfo> users = um.getUsers();
237 final int userCount = users.size();
238 int switchableUsers = 0;
239 for (int i = 0; i < userCount; i++) {
240 if (users.get(i).supportsSwitchTo()) {
241 switchableUsers++;
242 }
243 }
244
245 if (switchableUsers > 1) {
246 for (int i = 0; i < userCount; i++) {
247 int id = users.get(i).id;
248
249 if (getBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id)) {
250 setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id);
251 }
252 }
253 }
254
255 setString("migrated_lockscreen_disabled", "true", 0);
256 Slog.i(TAG, "Migrated lockscreen disabled flag");
257 }
Amith Yamasani52c489c2012-03-28 11:42:42 -0700258 } catch (RemoteException re) {
Jim Miller187ec582013-04-15 18:27:54 -0700259 Slog.e(TAG, "Unable to migrate old data", re);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700260 }
261 }
262
Jim Miller5ecd8112013-01-09 18:50:26 -0800263 private final void checkWritePermission(int userId) {
Jim Miller505329b2013-11-08 13:25:36 -0800264 mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsWrite");
Amith Yamasani52c489c2012-03-28 11:42:42 -0700265 }
266
Jim Miller5ecd8112013-01-09 18:50:26 -0800267 private final void checkPasswordReadPermission(int userId) {
Jim Miller505329b2013-11-08 13:25:36 -0800268 mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsRead");
Amith Yamasani52c489c2012-03-28 11:42:42 -0700269 }
270
Jim Miller158fe192013-04-17 15:23:55 -0700271 private final void checkReadPermission(String requestedKey, int userId) {
Amith Yamasani52c489c2012-03-28 11:42:42 -0700272 final int callingUid = Binder.getCallingUid();
Adrian Roos001b00d2015-02-24 17:08:48 +0100273
Svetoslav Ganov6d2c0e52015-06-23 16:33:36 +0000274 for (int i = 0; i < READ_CONTACTS_PROTECTED_SETTINGS.length; i++) {
275 String key = READ_CONTACTS_PROTECTED_SETTINGS[i];
276 if (key.equals(requestedKey) && mContext.checkCallingOrSelfPermission(READ_CONTACTS)
Jim Miller158fe192013-04-17 15:23:55 -0700277 != PackageManager.PERMISSION_GRANTED) {
278 throw new SecurityException("uid=" + callingUid
Svetoslav Ganov6d2c0e52015-06-23 16:33:36 +0000279 + " needs permission " + READ_CONTACTS + " to read "
Jim Miller158fe192013-04-17 15:23:55 -0700280 + requestedKey + " for user " + userId);
281 }
Amith Yamasani52c489c2012-03-28 11:42:42 -0700282 }
Adrian Roos001b00d2015-02-24 17:08:48 +0100283
284 for (int i = 0; i < READ_PASSWORD_PROTECTED_SETTINGS.length; i++) {
285 String key = READ_PASSWORD_PROTECTED_SETTINGS[i];
286 if (key.equals(requestedKey) && mContext.checkCallingOrSelfPermission(PERMISSION)
287 != PackageManager.PERMISSION_GRANTED) {
288 throw new SecurityException("uid=" + callingUid
289 + " needs permission " + PERMISSION + " to read "
290 + requestedKey + " for user " + userId);
291 }
292 }
Amith Yamasani52c489c2012-03-28 11:42:42 -0700293 }
294
295 @Override
296 public void setBoolean(String key, boolean value, int userId) throws RemoteException {
297 checkWritePermission(userId);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100298 setStringUnchecked(key, userId, value ? "1" : "0");
Amith Yamasani52c489c2012-03-28 11:42:42 -0700299 }
300
301 @Override
302 public void setLong(String key, long value, int userId) throws RemoteException {
303 checkWritePermission(userId);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100304 setStringUnchecked(key, userId, Long.toString(value));
Amith Yamasani52c489c2012-03-28 11:42:42 -0700305 }
306
307 @Override
308 public void setString(String key, String value, int userId) throws RemoteException {
309 checkWritePermission(userId);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100310 setStringUnchecked(key, userId, value);
311 }
Amith Yamasani52c489c2012-03-28 11:42:42 -0700312
Adrian Roos261d5ab2014-10-29 14:42:38 +0100313 private void setStringUnchecked(String key, int userId, String value) {
314 mStorage.writeKeyValue(key, value, userId);
Amith Yamasani072543f2015-02-13 11:09:45 -0800315 if (ArrayUtils.contains(SETTINGS_TO_BACKUP, key)) {
316 BackupManager.dataChanged("com.android.providers.settings");
317 }
Amith Yamasani52c489c2012-03-28 11:42:42 -0700318 }
319
320 @Override
321 public boolean getBoolean(String key, boolean defaultValue, int userId) throws RemoteException {
Jim Miller158fe192013-04-17 15:23:55 -0700322 checkReadPermission(key, userId);
Adrian Roos9dd16eb2015-01-08 16:20:49 +0100323 String value = getStringUnchecked(key, null, userId);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700324 return TextUtils.isEmpty(value) ?
325 defaultValue : (value.equals("1") || value.equals("true"));
326 }
327
328 @Override
329 public long getLong(String key, long defaultValue, int userId) throws RemoteException {
Jim Miller158fe192013-04-17 15:23:55 -0700330 checkReadPermission(key, userId);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700331
Adrian Roos9dd16eb2015-01-08 16:20:49 +0100332 String value = getStringUnchecked(key, null, userId);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700333 return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value);
334 }
335
336 @Override
337 public String getString(String key, String defaultValue, int userId) throws RemoteException {
Jim Miller158fe192013-04-17 15:23:55 -0700338 checkReadPermission(key, userId);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700339
Adrian Roos9dd16eb2015-01-08 16:20:49 +0100340 return getStringUnchecked(key, defaultValue, userId);
341 }
342
343 public String getStringUnchecked(String key, String defaultValue, int userId) {
344 if (Settings.Secure.LOCK_PATTERN_ENABLED.equals(key)) {
Adrian Roos7811d9f2015-07-27 15:10:13 -0700345 long ident = Binder.clearCallingIdentity();
346 try {
347 return mLockPatternUtils.isLockPatternEnabled(userId) ? "1" : "0";
348 } finally {
349 Binder.restoreCallingIdentity(ident);
350 }
Adrian Roos9dd16eb2015-01-08 16:20:49 +0100351 }
352
Adrian Roos261d5ab2014-10-29 14:42:38 +0100353 return mStorage.readKeyValue(key, defaultValue, userId);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700354 }
355
Adrian Roos4f788452014-05-22 20:45:59 +0200356 @Override
Amith Yamasani52c489c2012-03-28 11:42:42 -0700357 public boolean havePassword(int userId) throws RemoteException {
358 // Do we need a permissions check here?
359
Adrian Roos261d5ab2014-10-29 14:42:38 +0100360 return mStorage.hasPassword(userId);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700361 }
362
363 @Override
364 public boolean havePattern(int userId) throws RemoteException {
365 // Do we need a permissions check here?
366
Adrian Roos261d5ab2014-10-29 14:42:38 +0100367 return mStorage.hasPattern(userId);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700368 }
369
Chad Brubakera91a8502015-05-07 10:02:22 -0700370 private void setKeystorePassword(String password, int userHandle) {
Robin Leef0246a82014-08-13 09:50:25 +0100371 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
372 final KeyStore ks = KeyStore.getInstance();
373
374 final List<UserInfo> profiles = um.getProfiles(userHandle);
Robin Leef0246a82014-08-13 09:50:25 +0100375 for (UserInfo pi : profiles) {
Chad Brubakera91a8502015-05-07 10:02:22 -0700376 ks.onUserPasswordChanged(pi.id, password);
377 }
378 }
379
380 private void unlockKeystore(String password, int userHandle) {
381 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
382 final KeyStore ks = KeyStore.getInstance();
383
384 final List<UserInfo> profiles = um.getProfiles(userHandle);
385 for (UserInfo pi : profiles) {
386 ks.unlock(pi.id, password);
Jim Millerde1af082013-09-11 14:58:26 -0700387 }
388 }
389
Amith Yamasani52c489c2012-03-28 11:42:42 -0700390
Andres Morales8fa56652015-03-31 09:19:50 -0700391 private byte[] getCurrentHandle(int userId) {
392 CredentialHash credential;
393 byte[] currentHandle;
Jim Millerde1af082013-09-11 14:58:26 -0700394
Andres Morales8fa56652015-03-31 09:19:50 -0700395 int currentHandleType = mStorage.getStoredCredentialType(userId);
396 switch (currentHandleType) {
397 case CredentialHash.TYPE_PATTERN:
398 credential = mStorage.readPatternHash(userId);
399 currentHandle = credential != null
400 ? credential.hash
401 : null;
402 break;
403 case CredentialHash.TYPE_PASSWORD:
404 credential = mStorage.readPasswordHash(userId);
405 currentHandle = credential != null
406 ? credential.hash
407 : null;
408 break;
409 case CredentialHash.TYPE_NONE:
410 default:
411 currentHandle = null;
412 break;
413 }
414
415 // sanity check
416 if (currentHandleType != CredentialHash.TYPE_NONE && currentHandle == null) {
417 Slog.e(TAG, "Stored handle type [" + currentHandleType + "] but no handle available");
418 }
419
420 return currentHandle;
Amith Yamasani52c489c2012-03-28 11:42:42 -0700421 }
422
Andres Morales8fa56652015-03-31 09:19:50 -0700423
Amith Yamasani52c489c2012-03-28 11:42:42 -0700424 @Override
Andres Morales8fa56652015-03-31 09:19:50 -0700425 public void setLockPattern(String pattern, String savedCredential, int userId)
426 throws RemoteException {
427 byte[] currentHandle = getCurrentHandle(userId);
428
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700429 if (pattern == null) {
Andres Moralescfb61602015-04-16 16:31:15 -0700430 getGateKeeperService().clearSecureUserId(userId);
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700431 mStorage.writePatternHash(null, userId);
Chad Brubakera91a8502015-05-07 10:02:22 -0700432 setKeystorePassword(null, userId);
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700433 return;
434 }
435
Andres Morales8fa56652015-03-31 09:19:50 -0700436 if (currentHandle == null) {
437 if (savedCredential != null) {
438 Slog.w(TAG, "Saved credential provided, but none stored");
439 }
440 savedCredential = null;
441 }
442
443 byte[] enrolledHandle = enrollCredential(currentHandle, savedCredential, pattern, userId);
444 if (enrolledHandle != null) {
445 mStorage.writePatternHash(enrolledHandle, userId);
446 } else {
447 Slog.e(TAG, "Failed to enroll pattern");
448 }
449 }
450
451
452 @Override
453 public void setLockPassword(String password, String savedCredential, int userId)
454 throws RemoteException {
Andres Morales8fa56652015-03-31 09:19:50 -0700455 byte[] currentHandle = getCurrentHandle(userId);
456
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700457 if (password == null) {
Andres Moralescfb61602015-04-16 16:31:15 -0700458 getGateKeeperService().clearSecureUserId(userId);
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700459 mStorage.writePasswordHash(null, userId);
Chad Brubakera91a8502015-05-07 10:02:22 -0700460 setKeystorePassword(null, userId);
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700461 return;
462 }
463
Andres Morales8fa56652015-03-31 09:19:50 -0700464 if (currentHandle == null) {
465 if (savedCredential != null) {
466 Slog.w(TAG, "Saved credential provided, but none stored");
467 }
468 savedCredential = null;
469 }
470
471 byte[] enrolledHandle = enrollCredential(currentHandle, savedCredential, password, userId);
472 if (enrolledHandle != null) {
473 mStorage.writePasswordHash(enrolledHandle, userId);
474 } else {
475 Slog.e(TAG, "Failed to enroll password");
476 }
477 }
478
479 private byte[] enrollCredential(byte[] enrolledHandle,
480 String enrolledCredential, String toEnroll, int userId)
481 throws RemoteException {
Jim Millerde1af082013-09-11 14:58:26 -0700482 checkWritePermission(userId);
Andres Morales8fa56652015-03-31 09:19:50 -0700483 byte[] enrolledCredentialBytes = enrolledCredential == null
484 ? null
485 : enrolledCredential.getBytes();
486 byte[] toEnrollBytes = toEnroll == null
487 ? null
488 : toEnroll.getBytes();
Andres Morales23974272015-05-14 22:42:26 -0700489 GateKeeperResponse response = getGateKeeperService().enroll(userId, enrolledHandle,
490 enrolledCredentialBytes, toEnrollBytes);
Jim Millerde1af082013-09-11 14:58:26 -0700491
Andres Morales23974272015-05-14 22:42:26 -0700492 if (response == null) {
493 return null;
Andres Morales8fa56652015-03-31 09:19:50 -0700494 }
Jim Millerde1af082013-09-11 14:58:26 -0700495
Andres Morales23974272015-05-14 22:42:26 -0700496 byte[] hash = response.getPayload();
497 if (hash != null) {
498 setKeystorePassword(toEnroll, userId);
499 } else {
500 // Should not happen
501 Slog.e(TAG, "Throttled while enrolling a password");
502 }
Andres Morales8fa56652015-03-31 09:19:50 -0700503 return hash;
Jim Millerde1af082013-09-11 14:58:26 -0700504 }
505
506 @Override
Andres Morales23974272015-05-14 22:42:26 -0700507 public VerifyCredentialResponse checkPattern(String pattern, int userId) throws RemoteException {
508 return doVerifyPattern(pattern, false, 0, userId);
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700509 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100510
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700511 @Override
Andres Morales23974272015-05-14 22:42:26 -0700512 public VerifyCredentialResponse verifyPattern(String pattern, long challenge, int userId)
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700513 throws RemoteException {
Andres Morales23974272015-05-14 22:42:26 -0700514 return doVerifyPattern(pattern, true, challenge, userId);
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700515 }
516
Andres Moralese40bad82015-05-28 14:21:36 -0700517 private VerifyCredentialResponse doVerifyPattern(String pattern, boolean hasChallenge,
518 long challenge, int userId) throws RemoteException {
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700519 checkPasswordReadPermission(userId);
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700520 CredentialHash storedHash = mStorage.readPatternHash(userId);
Andres Moralese40bad82015-05-28 14:21:36 -0700521 boolean shouldReEnrollBaseZero = storedHash != null && storedHash.isBaseZeroPattern;
522
523 String patternToVerify;
524 if (shouldReEnrollBaseZero) {
525 patternToVerify = LockPatternUtils.patternStringToBaseZero(pattern);
526 } else {
527 patternToVerify = pattern;
528 }
529
530 VerifyCredentialResponse response = verifyCredential(userId, storedHash, patternToVerify,
531 hasChallenge, challenge,
Andres Morales23974272015-05-14 22:42:26 -0700532 new CredentialUtil() {
533 @Override
534 public void setCredential(String pattern, String oldPattern, int userId)
535 throws RemoteException {
536 setLockPattern(pattern, oldPattern, userId);
537 }
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700538
Andres Morales23974272015-05-14 22:42:26 -0700539 @Override
540 public byte[] toHash(String pattern, int userId) {
Andres Moralese40bad82015-05-28 14:21:36 -0700541 return LockPatternUtils.patternToHash(
542 LockPatternUtils.stringToPattern(pattern));
Andres Morales23974272015-05-14 22:42:26 -0700543 }
Andres Morales59ef1262015-06-26 13:56:39 -0700544
545 @Override
546 public String adjustForKeystore(String pattern) {
547 return LockPatternUtils.patternStringToBaseZero(pattern);
548 }
Andres Morales23974272015-05-14 22:42:26 -0700549 }
550 );
Andres Moralese40bad82015-05-28 14:21:36 -0700551
552 if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK
553 && shouldReEnrollBaseZero) {
554 setLockPattern(pattern, patternToVerify, userId);
555 }
556
557 return response;
558
Amith Yamasani52c489c2012-03-28 11:42:42 -0700559 }
560
561 @Override
Andres Morales23974272015-05-14 22:42:26 -0700562 public VerifyCredentialResponse checkPassword(String password, int userId)
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700563 throws RemoteException {
Andres Morales23974272015-05-14 22:42:26 -0700564 return doVerifyPassword(password, false, 0, userId);
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700565 }
566
Andres Morales23974272015-05-14 22:42:26 -0700567 @Override
568 public VerifyCredentialResponse verifyPassword(String password, long challenge, int userId)
569 throws RemoteException {
570 return doVerifyPassword(password, true, challenge, userId);
571 }
572
573 private VerifyCredentialResponse doVerifyPassword(String password, boolean hasChallenge,
574 long challenge, int userId) throws RemoteException {
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700575 checkPasswordReadPermission(userId);
Andres Morales8fa56652015-03-31 09:19:50 -0700576 CredentialHash storedHash = mStorage.readPasswordHash(userId);
Andres Morales23974272015-05-14 22:42:26 -0700577 return verifyCredential(userId, storedHash, password, hasChallenge, challenge,
578 new CredentialUtil() {
579 @Override
580 public void setCredential(String password, String oldPassword, int userId)
581 throws RemoteException {
582 setLockPassword(password, oldPassword, userId);
583 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100584
Andres Morales23974272015-05-14 22:42:26 -0700585 @Override
586 public byte[] toHash(String password, int userId) {
587 return mLockPatternUtils.passwordToHash(password, userId);
588 }
Andres Morales59ef1262015-06-26 13:56:39 -0700589
590 @Override
591 public String adjustForKeystore(String password) {
592 return password;
593 }
Andres Morales23974272015-05-14 22:42:26 -0700594 }
595 );
596 }
597
598 private VerifyCredentialResponse verifyCredential(int userId, CredentialHash storedHash,
599 String credential, boolean hasChallenge, long challenge, CredentialUtil credentialUtil)
600 throws RemoteException {
601 if ((storedHash == null || storedHash.hash.length == 0) && TextUtils.isEmpty(credential)) {
602 // don't need to pass empty credentials to GateKeeper
603 return VerifyCredentialResponse.OK;
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700604 }
605
Andres Morales23974272015-05-14 22:42:26 -0700606 if (TextUtils.isEmpty(credential)) {
607 return VerifyCredentialResponse.ERROR;
Amith Yamasani52c489c2012-03-28 11:42:42 -0700608 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100609
Andres Morales8fa56652015-03-31 09:19:50 -0700610 if (storedHash.version == CredentialHash.VERSION_LEGACY) {
Andres Morales23974272015-05-14 22:42:26 -0700611 byte[] hash = credentialUtil.toHash(credential, userId);
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700612 if (Arrays.equals(hash, storedHash.hash)) {
Andres Morales59ef1262015-06-26 13:56:39 -0700613 unlockKeystore(credentialUtil.adjustForKeystore(credential), userId);
Andres Morales23974272015-05-14 22:42:26 -0700614 // migrate credential to GateKeeper
615 credentialUtil.setCredential(credential, null, userId);
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700616 if (!hasChallenge) {
Andres Morales23974272015-05-14 22:42:26 -0700617 return VerifyCredentialResponse.OK;
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700618 }
619 // Fall through to get the auth token. Technically this should never happen,
Andres Morales23974272015-05-14 22:42:26 -0700620 // as a user that had a legacy credential would have to unlock their device
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700621 // before getting to a flow with a challenge, but supporting for consistency.
622 } else {
Andres Morales23974272015-05-14 22:42:26 -0700623 return VerifyCredentialResponse.ERROR;
Andres Morales8fa56652015-03-31 09:19:50 -0700624 }
Andres Morales8fa56652015-03-31 09:19:50 -0700625 }
626
Andres Morales23974272015-05-14 22:42:26 -0700627 VerifyCredentialResponse response;
628 boolean shouldReEnroll = false;;
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700629 if (hasChallenge) {
Andres Morales23974272015-05-14 22:42:26 -0700630 byte[] token = null;
631 GateKeeperResponse gateKeeperResponse = getGateKeeperService()
632 .verifyChallenge(userId, challenge, storedHash.hash, credential.getBytes());
633 int responseCode = gateKeeperResponse.getResponseCode();
634 if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
635 response = new VerifyCredentialResponse(gateKeeperResponse.getTimeout());
636 } else if (responseCode == GateKeeperResponse.RESPONSE_OK) {
637 token = gateKeeperResponse.getPayload();
638 if (token == null) {
639 // something's wrong if there's no payload with a challenge
640 Slog.e(TAG, "verifyChallenge response had no associated payload");
641 response = VerifyCredentialResponse.ERROR;
642 } else {
643 shouldReEnroll = gateKeeperResponse.getShouldReEnroll();
644 response = new VerifyCredentialResponse(token);
645 }
646 } else {
647 response = VerifyCredentialResponse.ERROR;
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700648 }
Andres Morales23974272015-05-14 22:42:26 -0700649 } else {
650 GateKeeperResponse gateKeeperResponse = getGateKeeperService().verify(
651 userId, storedHash.hash, credential.getBytes());
652 int responseCode = gateKeeperResponse.getResponseCode();
653 if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
654 response = new VerifyCredentialResponse(gateKeeperResponse.getTimeout());
655 } else if (responseCode == GateKeeperResponse.RESPONSE_OK) {
656 shouldReEnroll = gateKeeperResponse.getShouldReEnroll();
657 response = VerifyCredentialResponse.OK;
658 } else {
659 response = VerifyCredentialResponse.ERROR;
660 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100661 }
Andres Morales8fa56652015-03-31 09:19:50 -0700662
Andres Morales23974272015-05-14 22:42:26 -0700663 if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
664 // credential has matched
665 unlockKeystore(credential, userId);
666 if (shouldReEnroll) {
667 credentialUtil.setCredential(credential, credential, userId);
668 }
Adrian Roos873010d2015-08-25 15:59:00 -0700669 } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
670 if (response.getTimeout() > 0) {
671 requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId);
672 }
Andres Morales23974272015-05-14 22:42:26 -0700673 }
Amith Yamasani52c489c2012-03-28 11:42:42 -0700674
Andres Morales23974272015-05-14 22:42:26 -0700675 return response;
676 }
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700677
Amith Yamasani52c489c2012-03-28 11:42:42 -0700678 @Override
Adrian Roos261d5ab2014-10-29 14:42:38 +0100679 public boolean checkVoldPassword(int userId) throws RemoteException {
Paul Lawrence945490c2014-03-27 16:37:28 +0000680 if (!mFirstCallToVold) {
681 return false;
682 }
683 mFirstCallToVold = false;
684
685 checkPasswordReadPermission(userId);
686
687 // There's no guarantee that this will safely connect, but if it fails
688 // we will simply show the lock screen when we shouldn't, so relatively
689 // benign. There is an outside chance something nasty would happen if
690 // this service restarted before vold stales out the password in this
691 // case. The nastiness is limited to not showing the lock screen when
692 // we should, within the first minute of decrypting the phone if this
693 // service can't connect to vold, it restarts, and then the new instance
694 // does successfully connect.
695 final IMountService service = getMountService();
696 String password = service.getPassword();
697 service.clearPassword();
698 if (password == null) {
699 return false;
700 }
701
702 try {
Adrian Roos9dd16eb2015-01-08 16:20:49 +0100703 if (mLockPatternUtils.isLockPatternEnabled(userId)) {
Andres Morales23974272015-05-14 22:42:26 -0700704 if (checkPattern(password, userId).getResponseCode()
705 == GateKeeperResponse.RESPONSE_OK) {
Paul Lawrence945490c2014-03-27 16:37:28 +0000706 return true;
707 }
708 }
709 } catch (Exception e) {
710 }
711
712 try {
Adrian Roos9dd16eb2015-01-08 16:20:49 +0100713 if (mLockPatternUtils.isLockPasswordEnabled(userId)) {
Andres Morales23974272015-05-14 22:42:26 -0700714 if (checkPassword(password, userId).getResponseCode()
715 == GateKeeperResponse.RESPONSE_OK) {
Paul Lawrence945490c2014-03-27 16:37:28 +0000716 return true;
717 }
718 }
719 } catch (Exception e) {
720 }
721
722 return false;
723 }
724
Adrian Roosdb0f76e2015-01-07 22:19:38 +0100725 private void removeUser(int userId) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100726 mStorage.removeUser(userId);
Adrian Roosb5e47222015-08-14 15:53:06 -0700727 mStrongAuth.removeUser(userId);
Robin Lee49d810c2014-09-23 13:50:22 +0100728
729 final KeyStore ks = KeyStore.getInstance();
Chad Brubaker83ce0952015-05-12 13:00:02 -0700730 ks.onUserRemoved(userId);
Andres Morales070fe632015-06-24 10:37:10 -0700731
732 try {
733 final IGateKeeperService gk = getGateKeeperService();
734 if (gk != null) {
735 gk.clearSecureUserId(userId);
736 }
737 } catch (RemoteException ex) {
738 Slog.w(TAG, "unable to clear GK secure user id");
739 }
Amith Yamasani52c489c2012-03-28 11:42:42 -0700740 }
741
Adrian Roosb5e47222015-08-14 15:53:06 -0700742 @Override
743 public void registerStrongAuthTracker(IStrongAuthTracker tracker) {
744 checkPasswordReadPermission(UserHandle.USER_ALL);
745 mStrongAuth.registerStrongAuthTracker(tracker);
746 }
747
748 @Override
749 public void unregisterStrongAuthTracker(IStrongAuthTracker tracker) {
750 checkPasswordReadPermission(UserHandle.USER_ALL);
751 mStrongAuth.unregisterStrongAuthTracker(tracker);
752 }
753
754 @Override
755 public void requireStrongAuth(int strongAuthReason, int userId) {
756 checkWritePermission(userId);
757 mStrongAuth.requireStrongAuth(strongAuthReason, userId);
758 }
759
Amith Yamasani52c489c2012-03-28 11:42:42 -0700760 private static final String[] VALID_SETTINGS = new String[] {
761 LockPatternUtils.LOCKOUT_PERMANENT_KEY,
762 LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE,
763 LockPatternUtils.PATTERN_EVER_CHOSEN_KEY,
764 LockPatternUtils.PASSWORD_TYPE_KEY,
765 LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
766 LockPatternUtils.LOCK_PASSWORD_SALT_KEY,
767 LockPatternUtils.DISABLE_LOCKSCREEN_KEY,
768 LockPatternUtils.LOCKSCREEN_OPTIONS,
769 LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK,
770 LockPatternUtils.BIOMETRIC_WEAK_EVER_CHOSEN_KEY,
771 LockPatternUtils.LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS,
772 LockPatternUtils.PASSWORD_HISTORY_KEY,
773 Secure.LOCK_PATTERN_ENABLED,
774 Secure.LOCK_BIOMETRIC_WEAK_FLAGS,
775 Secure.LOCK_PATTERN_VISIBLE,
776 Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED
Jim Miller187ec582013-04-15 18:27:54 -0700777 };
778
Svetoslav Ganov6d2c0e52015-06-23 16:33:36 +0000779 // Reading these settings needs the contacts permission
780 private static final String[] READ_CONTACTS_PROTECTED_SETTINGS = new String[] {
Jim Miller187ec582013-04-15 18:27:54 -0700781 Secure.LOCK_SCREEN_OWNER_INFO_ENABLED,
782 Secure.LOCK_SCREEN_OWNER_INFO
783 };
Paul Lawrence945490c2014-03-27 16:37:28 +0000784
Adrian Roos001b00d2015-02-24 17:08:48 +0100785 // Reading these settings needs the same permission as checking the password
786 private static final String[] READ_PASSWORD_PROTECTED_SETTINGS = new String[] {
787 LockPatternUtils.LOCK_PASSWORD_SALT_KEY,
788 LockPatternUtils.PASSWORD_HISTORY_KEY,
Adrian Roos855fa302015-04-02 16:01:12 +0200789 LockPatternUtils.PASSWORD_TYPE_KEY,
Adrian Roos001b00d2015-02-24 17:08:48 +0100790 };
791
Amith Yamasani072543f2015-02-13 11:09:45 -0800792 private static final String[] SETTINGS_TO_BACKUP = new String[] {
793 Secure.LOCK_SCREEN_OWNER_INFO_ENABLED,
794 Secure.LOCK_SCREEN_OWNER_INFO
795 };
796
Paul Lawrence945490c2014-03-27 16:37:28 +0000797 private IMountService getMountService() {
798 final IBinder service = ServiceManager.getService("mount");
799 if (service != null) {
800 return IMountService.Stub.asInterface(service);
801 }
802 return null;
803 }
Andres Morales8fa56652015-03-31 09:19:50 -0700804
Andres Morales301ea442015-04-17 09:15:47 -0700805 private class GateKeeperDiedRecipient implements IBinder.DeathRecipient {
806 @Override
807 public void binderDied() {
808 mGateKeeperService.asBinder().unlinkToDeath(this, 0);
809 mGateKeeperService = null;
810 }
811 }
812
813 private synchronized IGateKeeperService getGateKeeperService()
814 throws RemoteException {
Andres Morales8fa56652015-03-31 09:19:50 -0700815 if (mGateKeeperService != null) {
816 return mGateKeeperService;
817 }
818
819 final IBinder service =
820 ServiceManager.getService("android.service.gatekeeper.IGateKeeperService");
821 if (service != null) {
Andres Morales301ea442015-04-17 09:15:47 -0700822 service.linkToDeath(new GateKeeperDiedRecipient(), 0);
Andres Morales8fa56652015-03-31 09:19:50 -0700823 mGateKeeperService = IGateKeeperService.Stub.asInterface(service);
824 return mGateKeeperService;
825 }
826
827 Slog.e(TAG, "Unable to acquire GateKeeperService");
828 return null;
829 }
Amith Yamasani52c489c2012-03-28 11:42:42 -0700830}