blob: da8152861edf9dcfa367065884a8f1c6a74ad4c8 [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 }
Xiaohui Chen7c696362015-09-16 09:56:14 -0700148 // TODO: maybe skip this for split system user mode.
149 mStorage.prefetchUser(UserHandle.USER_SYSTEM);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700150 }
151
152 private void migrateOldData() {
153 try {
Jim Miller187ec582013-04-15 18:27:54 -0700154 // These Settings moved before multi-user was enabled, so we only have to do it for the
155 // root user.
156 if (getString("migrated", null, 0) == null) {
157 final ContentResolver cr = mContext.getContentResolver();
158 for (String validSetting : VALID_SETTINGS) {
159 String value = Settings.Secure.getString(cr, validSetting);
160 if (value != null) {
161 setString(validSetting, value, 0);
162 }
163 }
164 // No need to move the password / pattern files. They're already in the right place.
165 setString("migrated", "true", 0);
166 Slog.i(TAG, "Migrated lock settings to new location");
Amith Yamasani52c489c2012-03-28 11:42:42 -0700167 }
168
Jim Miller187ec582013-04-15 18:27:54 -0700169 // These Settings changed after multi-user was enabled, hence need to be moved per user.
170 if (getString("migrated_user_specific", null, 0) == null) {
171 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
172 final ContentResolver cr = mContext.getContentResolver();
173 List<UserInfo> users = um.getUsers();
174 for (int user = 0; user < users.size(); user++) {
Jim Miller2d8ecf9d2013-04-22 17:17:03 -0700175 // Migrate owner info
176 final int userId = users.get(user).id;
177 final String OWNER_INFO = Secure.LOCK_SCREEN_OWNER_INFO;
178 String ownerInfo = Settings.Secure.getStringForUser(cr, OWNER_INFO, userId);
179 if (ownerInfo != null) {
180 setString(OWNER_INFO, ownerInfo, userId);
181 Settings.Secure.putStringForUser(cr, ownerInfo, "", userId);
182 }
Jim Miller187ec582013-04-15 18:27:54 -0700183
Jim Miller2d8ecf9d2013-04-22 17:17:03 -0700184 // Migrate owner info enabled. Note there was a bug where older platforms only
185 // stored this value if the checkbox was toggled at least once. The code detects
186 // this case by handling the exception.
187 final String OWNER_INFO_ENABLED = Secure.LOCK_SCREEN_OWNER_INFO_ENABLED;
188 boolean enabled;
189 try {
190 int ivalue = Settings.Secure.getIntForUser(cr, OWNER_INFO_ENABLED, userId);
191 enabled = ivalue != 0;
192 setLong(OWNER_INFO_ENABLED, enabled ? 1 : 0, userId);
193 } catch (SettingNotFoundException e) {
194 // Setting was never stored. Store it if the string is not empty.
195 if (!TextUtils.isEmpty(ownerInfo)) {
196 setLong(OWNER_INFO_ENABLED, 1, userId);
Jim Miller187ec582013-04-15 18:27:54 -0700197 }
198 }
Jim Miller2d8ecf9d2013-04-22 17:17:03 -0700199 Settings.Secure.putIntForUser(cr, OWNER_INFO_ENABLED, 0, userId);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700200 }
Jim Miller187ec582013-04-15 18:27:54 -0700201 // No need to move the password / pattern files. They're already in the right place.
202 setString("migrated_user_specific", "true", 0);
203 Slog.i(TAG, "Migrated per-user lock settings to new location");
Amith Yamasani52c489c2012-03-28 11:42:42 -0700204 }
Adrian Roos230635e2015-01-07 20:50:29 +0100205
206 // Migrates biometric weak such that the fallback mechanism becomes the primary.
207 if (getString("migrated_biometric_weak", null, 0) == null) {
208 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
209 List<UserInfo> users = um.getUsers();
210 for (int i = 0; i < users.size(); i++) {
211 int userId = users.get(i).id;
212 long type = getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
213 DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
214 userId);
215 long alternateType = getLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
216 DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
217 userId);
218 if (type == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK) {
219 setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
220 alternateType,
221 userId);
222 }
223 setLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
224 DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
225 userId);
226 }
227 setString("migrated_biometric_weak", "true", 0);
228 Slog.i(TAG, "Migrated biometric weak to use the fallback instead");
229 }
Adrian Roos43830582015-04-21 16:04:43 -0700230
231 // Migrates lockscreen.disabled. Prior to M, the flag was ignored when more than one
232 // user was present on the system, so if we're upgrading to M and there is more than one
233 // user we disable the flag to remain consistent.
234 if (getString("migrated_lockscreen_disabled", null, 0) == null) {
235 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
236
237 final List<UserInfo> users = um.getUsers();
238 final int userCount = users.size();
239 int switchableUsers = 0;
240 for (int i = 0; i < userCount; i++) {
241 if (users.get(i).supportsSwitchTo()) {
242 switchableUsers++;
243 }
244 }
245
246 if (switchableUsers > 1) {
247 for (int i = 0; i < userCount; i++) {
248 int id = users.get(i).id;
249
250 if (getBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id)) {
251 setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id);
252 }
253 }
254 }
255
256 setString("migrated_lockscreen_disabled", "true", 0);
257 Slog.i(TAG, "Migrated lockscreen disabled flag");
258 }
Amith Yamasani52c489c2012-03-28 11:42:42 -0700259 } catch (RemoteException re) {
Jim Miller187ec582013-04-15 18:27:54 -0700260 Slog.e(TAG, "Unable to migrate old data", re);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700261 }
262 }
263
Jim Miller5ecd8112013-01-09 18:50:26 -0800264 private final void checkWritePermission(int userId) {
Jim Miller505329b2013-11-08 13:25:36 -0800265 mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsWrite");
Amith Yamasani52c489c2012-03-28 11:42:42 -0700266 }
267
Jim Miller5ecd8112013-01-09 18:50:26 -0800268 private final void checkPasswordReadPermission(int userId) {
Jim Miller505329b2013-11-08 13:25:36 -0800269 mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsRead");
Amith Yamasani52c489c2012-03-28 11:42:42 -0700270 }
271
Jim Miller158fe192013-04-17 15:23:55 -0700272 private final void checkReadPermission(String requestedKey, int userId) {
Amith Yamasani52c489c2012-03-28 11:42:42 -0700273 final int callingUid = Binder.getCallingUid();
Adrian Roos001b00d2015-02-24 17:08:48 +0100274
Svetoslav Ganov6d2c0e52015-06-23 16:33:36 +0000275 for (int i = 0; i < READ_CONTACTS_PROTECTED_SETTINGS.length; i++) {
276 String key = READ_CONTACTS_PROTECTED_SETTINGS[i];
277 if (key.equals(requestedKey) && mContext.checkCallingOrSelfPermission(READ_CONTACTS)
Jim Miller158fe192013-04-17 15:23:55 -0700278 != PackageManager.PERMISSION_GRANTED) {
279 throw new SecurityException("uid=" + callingUid
Svetoslav Ganov6d2c0e52015-06-23 16:33:36 +0000280 + " needs permission " + READ_CONTACTS + " to read "
Jim Miller158fe192013-04-17 15:23:55 -0700281 + requestedKey + " for user " + userId);
282 }
Amith Yamasani52c489c2012-03-28 11:42:42 -0700283 }
Adrian Roos001b00d2015-02-24 17:08:48 +0100284
285 for (int i = 0; i < READ_PASSWORD_PROTECTED_SETTINGS.length; i++) {
286 String key = READ_PASSWORD_PROTECTED_SETTINGS[i];
287 if (key.equals(requestedKey) && mContext.checkCallingOrSelfPermission(PERMISSION)
288 != PackageManager.PERMISSION_GRANTED) {
289 throw new SecurityException("uid=" + callingUid
290 + " needs permission " + PERMISSION + " to read "
291 + requestedKey + " for user " + userId);
292 }
293 }
Amith Yamasani52c489c2012-03-28 11:42:42 -0700294 }
295
296 @Override
297 public void setBoolean(String key, boolean value, int userId) throws RemoteException {
298 checkWritePermission(userId);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100299 setStringUnchecked(key, userId, value ? "1" : "0");
Amith Yamasani52c489c2012-03-28 11:42:42 -0700300 }
301
302 @Override
303 public void setLong(String key, long value, int userId) throws RemoteException {
304 checkWritePermission(userId);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100305 setStringUnchecked(key, userId, Long.toString(value));
Amith Yamasani52c489c2012-03-28 11:42:42 -0700306 }
307
308 @Override
309 public void setString(String key, String value, int userId) throws RemoteException {
310 checkWritePermission(userId);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100311 setStringUnchecked(key, userId, value);
312 }
Amith Yamasani52c489c2012-03-28 11:42:42 -0700313
Adrian Roos261d5ab2014-10-29 14:42:38 +0100314 private void setStringUnchecked(String key, int userId, String value) {
315 mStorage.writeKeyValue(key, value, userId);
Amith Yamasani072543f2015-02-13 11:09:45 -0800316 if (ArrayUtils.contains(SETTINGS_TO_BACKUP, key)) {
317 BackupManager.dataChanged("com.android.providers.settings");
318 }
Amith Yamasani52c489c2012-03-28 11:42:42 -0700319 }
320
321 @Override
322 public boolean getBoolean(String key, boolean defaultValue, int userId) throws RemoteException {
Jim Miller158fe192013-04-17 15:23:55 -0700323 checkReadPermission(key, userId);
Adrian Roos9dd16eb2015-01-08 16:20:49 +0100324 String value = getStringUnchecked(key, null, userId);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700325 return TextUtils.isEmpty(value) ?
326 defaultValue : (value.equals("1") || value.equals("true"));
327 }
328
329 @Override
330 public long getLong(String key, long defaultValue, int userId) throws RemoteException {
Jim Miller158fe192013-04-17 15:23:55 -0700331 checkReadPermission(key, userId);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700332
Adrian Roos9dd16eb2015-01-08 16:20:49 +0100333 String value = getStringUnchecked(key, null, userId);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700334 return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value);
335 }
336
337 @Override
338 public String getString(String key, String defaultValue, int userId) throws RemoteException {
Jim Miller158fe192013-04-17 15:23:55 -0700339 checkReadPermission(key, userId);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700340
Adrian Roos9dd16eb2015-01-08 16:20:49 +0100341 return getStringUnchecked(key, defaultValue, userId);
342 }
343
344 public String getStringUnchecked(String key, String defaultValue, int userId) {
345 if (Settings.Secure.LOCK_PATTERN_ENABLED.equals(key)) {
Adrian Roos7811d9f2015-07-27 15:10:13 -0700346 long ident = Binder.clearCallingIdentity();
347 try {
348 return mLockPatternUtils.isLockPatternEnabled(userId) ? "1" : "0";
349 } finally {
350 Binder.restoreCallingIdentity(ident);
351 }
Adrian Roos9dd16eb2015-01-08 16:20:49 +0100352 }
353
Adrian Roos261d5ab2014-10-29 14:42:38 +0100354 return mStorage.readKeyValue(key, defaultValue, userId);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700355 }
356
Adrian Roos4f788452014-05-22 20:45:59 +0200357 @Override
Amith Yamasani52c489c2012-03-28 11:42:42 -0700358 public boolean havePassword(int userId) throws RemoteException {
359 // Do we need a permissions check here?
360
Adrian Roos261d5ab2014-10-29 14:42:38 +0100361 return mStorage.hasPassword(userId);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700362 }
363
364 @Override
365 public boolean havePattern(int userId) throws RemoteException {
366 // Do we need a permissions check here?
367
Adrian Roos261d5ab2014-10-29 14:42:38 +0100368 return mStorage.hasPattern(userId);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700369 }
370
Chad Brubakera91a8502015-05-07 10:02:22 -0700371 private void setKeystorePassword(String password, int userHandle) {
Robin Leef0246a82014-08-13 09:50:25 +0100372 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
373 final KeyStore ks = KeyStore.getInstance();
374
375 final List<UserInfo> profiles = um.getProfiles(userHandle);
Robin Leef0246a82014-08-13 09:50:25 +0100376 for (UserInfo pi : profiles) {
Chad Brubakera91a8502015-05-07 10:02:22 -0700377 ks.onUserPasswordChanged(pi.id, password);
378 }
379 }
380
381 private void unlockKeystore(String password, int userHandle) {
382 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
383 final KeyStore ks = KeyStore.getInstance();
384
385 final List<UserInfo> profiles = um.getProfiles(userHandle);
386 for (UserInfo pi : profiles) {
387 ks.unlock(pi.id, password);
Jim Millerde1af082013-09-11 14:58:26 -0700388 }
389 }
390
Amith Yamasani52c489c2012-03-28 11:42:42 -0700391
Andres Morales8fa56652015-03-31 09:19:50 -0700392 private byte[] getCurrentHandle(int userId) {
393 CredentialHash credential;
394 byte[] currentHandle;
Jim Millerde1af082013-09-11 14:58:26 -0700395
Andres Morales8fa56652015-03-31 09:19:50 -0700396 int currentHandleType = mStorage.getStoredCredentialType(userId);
397 switch (currentHandleType) {
398 case CredentialHash.TYPE_PATTERN:
399 credential = mStorage.readPatternHash(userId);
400 currentHandle = credential != null
401 ? credential.hash
402 : null;
403 break;
404 case CredentialHash.TYPE_PASSWORD:
405 credential = mStorage.readPasswordHash(userId);
406 currentHandle = credential != null
407 ? credential.hash
408 : null;
409 break;
410 case CredentialHash.TYPE_NONE:
411 default:
412 currentHandle = null;
413 break;
414 }
415
416 // sanity check
417 if (currentHandleType != CredentialHash.TYPE_NONE && currentHandle == null) {
418 Slog.e(TAG, "Stored handle type [" + currentHandleType + "] but no handle available");
419 }
420
421 return currentHandle;
Amith Yamasani52c489c2012-03-28 11:42:42 -0700422 }
423
Andres Morales8fa56652015-03-31 09:19:50 -0700424
Amith Yamasani52c489c2012-03-28 11:42:42 -0700425 @Override
Andres Morales8fa56652015-03-31 09:19:50 -0700426 public void setLockPattern(String pattern, String savedCredential, int userId)
427 throws RemoteException {
428 byte[] currentHandle = getCurrentHandle(userId);
429
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700430 if (pattern == null) {
Andres Moralescfb61602015-04-16 16:31:15 -0700431 getGateKeeperService().clearSecureUserId(userId);
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700432 mStorage.writePatternHash(null, userId);
Chad Brubakera91a8502015-05-07 10:02:22 -0700433 setKeystorePassword(null, userId);
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700434 return;
435 }
436
Andres Morales8fa56652015-03-31 09:19:50 -0700437 if (currentHandle == null) {
438 if (savedCredential != null) {
439 Slog.w(TAG, "Saved credential provided, but none stored");
440 }
441 savedCredential = null;
442 }
443
444 byte[] enrolledHandle = enrollCredential(currentHandle, savedCredential, pattern, userId);
445 if (enrolledHandle != null) {
446 mStorage.writePatternHash(enrolledHandle, userId);
447 } else {
Andres Morales2c4a5732015-07-09 16:11:00 -0700448 throw new RemoteException("Failed to enroll pattern");
Andres Morales8fa56652015-03-31 09:19:50 -0700449 }
450 }
451
452
453 @Override
454 public void setLockPassword(String password, String savedCredential, int userId)
455 throws RemoteException {
Andres Morales8fa56652015-03-31 09:19:50 -0700456 byte[] currentHandle = getCurrentHandle(userId);
457
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700458 if (password == null) {
Andres Moralescfb61602015-04-16 16:31:15 -0700459 getGateKeeperService().clearSecureUserId(userId);
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700460 mStorage.writePasswordHash(null, userId);
Chad Brubakera91a8502015-05-07 10:02:22 -0700461 setKeystorePassword(null, userId);
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700462 return;
463 }
464
Andres Morales8fa56652015-03-31 09:19:50 -0700465 if (currentHandle == null) {
466 if (savedCredential != null) {
467 Slog.w(TAG, "Saved credential provided, but none stored");
468 }
469 savedCredential = null;
470 }
471
472 byte[] enrolledHandle = enrollCredential(currentHandle, savedCredential, password, userId);
473 if (enrolledHandle != null) {
474 mStorage.writePasswordHash(enrolledHandle, userId);
475 } else {
Andres Morales2c4a5732015-07-09 16:11:00 -0700476 throw new RemoteException("Failed to enroll password");
Andres Morales8fa56652015-03-31 09:19:50 -0700477 }
478 }
479
480 private byte[] enrollCredential(byte[] enrolledHandle,
481 String enrolledCredential, String toEnroll, int userId)
482 throws RemoteException {
Jim Millerde1af082013-09-11 14:58:26 -0700483 checkWritePermission(userId);
Andres Morales8fa56652015-03-31 09:19:50 -0700484 byte[] enrolledCredentialBytes = enrolledCredential == null
485 ? null
486 : enrolledCredential.getBytes();
487 byte[] toEnrollBytes = toEnroll == null
488 ? null
489 : toEnroll.getBytes();
Andres Morales23974272015-05-14 22:42:26 -0700490 GateKeeperResponse response = getGateKeeperService().enroll(userId, enrolledHandle,
491 enrolledCredentialBytes, toEnrollBytes);
Jim Millerde1af082013-09-11 14:58:26 -0700492
Andres Morales23974272015-05-14 22:42:26 -0700493 if (response == null) {
494 return null;
Andres Morales8fa56652015-03-31 09:19:50 -0700495 }
Jim Millerde1af082013-09-11 14:58:26 -0700496
Andres Morales23974272015-05-14 22:42:26 -0700497 byte[] hash = response.getPayload();
498 if (hash != null) {
499 setKeystorePassword(toEnroll, userId);
500 } else {
501 // Should not happen
502 Slog.e(TAG, "Throttled while enrolling a password");
503 }
Andres Morales8fa56652015-03-31 09:19:50 -0700504 return hash;
Jim Millerde1af082013-09-11 14:58:26 -0700505 }
506
507 @Override
Andres Morales23974272015-05-14 22:42:26 -0700508 public VerifyCredentialResponse checkPattern(String pattern, int userId) throws RemoteException {
509 return doVerifyPattern(pattern, false, 0, userId);
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700510 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100511
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700512 @Override
Andres Morales23974272015-05-14 22:42:26 -0700513 public VerifyCredentialResponse verifyPattern(String pattern, long challenge, int userId)
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700514 throws RemoteException {
Andres Morales23974272015-05-14 22:42:26 -0700515 return doVerifyPattern(pattern, true, challenge, userId);
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700516 }
517
Andres Moralese40bad82015-05-28 14:21:36 -0700518 private VerifyCredentialResponse doVerifyPattern(String pattern, boolean hasChallenge,
519 long challenge, int userId) throws RemoteException {
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700520 checkPasswordReadPermission(userId);
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700521 CredentialHash storedHash = mStorage.readPatternHash(userId);
Andres Moralese40bad82015-05-28 14:21:36 -0700522 boolean shouldReEnrollBaseZero = storedHash != null && storedHash.isBaseZeroPattern;
523
524 String patternToVerify;
525 if (shouldReEnrollBaseZero) {
526 patternToVerify = LockPatternUtils.patternStringToBaseZero(pattern);
527 } else {
528 patternToVerify = pattern;
529 }
530
531 VerifyCredentialResponse response = verifyCredential(userId, storedHash, patternToVerify,
532 hasChallenge, challenge,
Andres Morales23974272015-05-14 22:42:26 -0700533 new CredentialUtil() {
534 @Override
535 public void setCredential(String pattern, String oldPattern, int userId)
536 throws RemoteException {
537 setLockPattern(pattern, oldPattern, userId);
538 }
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700539
Andres Morales23974272015-05-14 22:42:26 -0700540 @Override
541 public byte[] toHash(String pattern, int userId) {
Andres Moralese40bad82015-05-28 14:21:36 -0700542 return LockPatternUtils.patternToHash(
543 LockPatternUtils.stringToPattern(pattern));
Andres Morales23974272015-05-14 22:42:26 -0700544 }
Andres Morales59ef1262015-06-26 13:56:39 -0700545
546 @Override
547 public String adjustForKeystore(String pattern) {
548 return LockPatternUtils.patternStringToBaseZero(pattern);
549 }
Andres Morales23974272015-05-14 22:42:26 -0700550 }
551 );
Andres Moralese40bad82015-05-28 14:21:36 -0700552
553 if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK
554 && shouldReEnrollBaseZero) {
555 setLockPattern(pattern, patternToVerify, userId);
556 }
557
558 return response;
559
Amith Yamasani52c489c2012-03-28 11:42:42 -0700560 }
561
562 @Override
Andres Morales23974272015-05-14 22:42:26 -0700563 public VerifyCredentialResponse checkPassword(String password, int userId)
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700564 throws RemoteException {
Andres Morales23974272015-05-14 22:42:26 -0700565 return doVerifyPassword(password, false, 0, userId);
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700566 }
567
Andres Morales23974272015-05-14 22:42:26 -0700568 @Override
569 public VerifyCredentialResponse verifyPassword(String password, long challenge, int userId)
570 throws RemoteException {
571 return doVerifyPassword(password, true, challenge, userId);
572 }
573
574 private VerifyCredentialResponse doVerifyPassword(String password, boolean hasChallenge,
575 long challenge, int userId) throws RemoteException {
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700576 checkPasswordReadPermission(userId);
Andres Morales8fa56652015-03-31 09:19:50 -0700577 CredentialHash storedHash = mStorage.readPasswordHash(userId);
Andres Morales23974272015-05-14 22:42:26 -0700578 return verifyCredential(userId, storedHash, password, hasChallenge, challenge,
579 new CredentialUtil() {
580 @Override
581 public void setCredential(String password, String oldPassword, int userId)
582 throws RemoteException {
583 setLockPassword(password, oldPassword, userId);
584 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100585
Andres Morales23974272015-05-14 22:42:26 -0700586 @Override
587 public byte[] toHash(String password, int userId) {
588 return mLockPatternUtils.passwordToHash(password, userId);
589 }
Andres Morales59ef1262015-06-26 13:56:39 -0700590
591 @Override
592 public String adjustForKeystore(String password) {
593 return password;
594 }
Andres Morales23974272015-05-14 22:42:26 -0700595 }
596 );
597 }
598
599 private VerifyCredentialResponse verifyCredential(int userId, CredentialHash storedHash,
600 String credential, boolean hasChallenge, long challenge, CredentialUtil credentialUtil)
601 throws RemoteException {
602 if ((storedHash == null || storedHash.hash.length == 0) && TextUtils.isEmpty(credential)) {
603 // don't need to pass empty credentials to GateKeeper
604 return VerifyCredentialResponse.OK;
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700605 }
606
Andres Morales23974272015-05-14 22:42:26 -0700607 if (TextUtils.isEmpty(credential)) {
608 return VerifyCredentialResponse.ERROR;
Amith Yamasani52c489c2012-03-28 11:42:42 -0700609 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100610
Andres Morales8fa56652015-03-31 09:19:50 -0700611 if (storedHash.version == CredentialHash.VERSION_LEGACY) {
Andres Morales23974272015-05-14 22:42:26 -0700612 byte[] hash = credentialUtil.toHash(credential, userId);
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700613 if (Arrays.equals(hash, storedHash.hash)) {
Andres Morales59ef1262015-06-26 13:56:39 -0700614 unlockKeystore(credentialUtil.adjustForKeystore(credential), userId);
Andres Morales23974272015-05-14 22:42:26 -0700615 // migrate credential to GateKeeper
616 credentialUtil.setCredential(credential, null, userId);
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700617 if (!hasChallenge) {
Andres Morales23974272015-05-14 22:42:26 -0700618 return VerifyCredentialResponse.OK;
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700619 }
620 // Fall through to get the auth token. Technically this should never happen,
Andres Morales23974272015-05-14 22:42:26 -0700621 // as a user that had a legacy credential would have to unlock their device
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700622 // before getting to a flow with a challenge, but supporting for consistency.
623 } else {
Andres Morales23974272015-05-14 22:42:26 -0700624 return VerifyCredentialResponse.ERROR;
Andres Morales8fa56652015-03-31 09:19:50 -0700625 }
Andres Morales8fa56652015-03-31 09:19:50 -0700626 }
627
Andres Morales23974272015-05-14 22:42:26 -0700628 VerifyCredentialResponse response;
629 boolean shouldReEnroll = false;;
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700630 if (hasChallenge) {
Andres Morales23974272015-05-14 22:42:26 -0700631 byte[] token = null;
632 GateKeeperResponse gateKeeperResponse = getGateKeeperService()
633 .verifyChallenge(userId, challenge, storedHash.hash, credential.getBytes());
634 int responseCode = gateKeeperResponse.getResponseCode();
635 if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
636 response = new VerifyCredentialResponse(gateKeeperResponse.getTimeout());
637 } else if (responseCode == GateKeeperResponse.RESPONSE_OK) {
638 token = gateKeeperResponse.getPayload();
639 if (token == null) {
640 // something's wrong if there's no payload with a challenge
641 Slog.e(TAG, "verifyChallenge response had no associated payload");
642 response = VerifyCredentialResponse.ERROR;
643 } else {
644 shouldReEnroll = gateKeeperResponse.getShouldReEnroll();
645 response = new VerifyCredentialResponse(token);
646 }
647 } else {
648 response = VerifyCredentialResponse.ERROR;
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700649 }
Andres Morales23974272015-05-14 22:42:26 -0700650 } else {
651 GateKeeperResponse gateKeeperResponse = getGateKeeperService().verify(
652 userId, storedHash.hash, credential.getBytes());
653 int responseCode = gateKeeperResponse.getResponseCode();
654 if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
655 response = new VerifyCredentialResponse(gateKeeperResponse.getTimeout());
656 } else if (responseCode == GateKeeperResponse.RESPONSE_OK) {
657 shouldReEnroll = gateKeeperResponse.getShouldReEnroll();
658 response = VerifyCredentialResponse.OK;
659 } else {
660 response = VerifyCredentialResponse.ERROR;
661 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100662 }
Andres Morales8fa56652015-03-31 09:19:50 -0700663
Andres Morales23974272015-05-14 22:42:26 -0700664 if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
665 // credential has matched
666 unlockKeystore(credential, userId);
667 if (shouldReEnroll) {
668 credentialUtil.setCredential(credential, credential, userId);
669 }
Adrian Roos873010d2015-08-25 15:59:00 -0700670 } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
671 if (response.getTimeout() > 0) {
672 requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId);
673 }
Andres Morales23974272015-05-14 22:42:26 -0700674 }
Amith Yamasani52c489c2012-03-28 11:42:42 -0700675
Andres Morales23974272015-05-14 22:42:26 -0700676 return response;
677 }
Andres Moralesd9fc85a2015-04-09 19:14:42 -0700678
Amith Yamasani52c489c2012-03-28 11:42:42 -0700679 @Override
Adrian Roos261d5ab2014-10-29 14:42:38 +0100680 public boolean checkVoldPassword(int userId) throws RemoteException {
Paul Lawrence945490c2014-03-27 16:37:28 +0000681 if (!mFirstCallToVold) {
682 return false;
683 }
684 mFirstCallToVold = false;
685
686 checkPasswordReadPermission(userId);
687
688 // There's no guarantee that this will safely connect, but if it fails
689 // we will simply show the lock screen when we shouldn't, so relatively
690 // benign. There is an outside chance something nasty would happen if
691 // this service restarted before vold stales out the password in this
692 // case. The nastiness is limited to not showing the lock screen when
693 // we should, within the first minute of decrypting the phone if this
694 // service can't connect to vold, it restarts, and then the new instance
695 // does successfully connect.
696 final IMountService service = getMountService();
697 String password = service.getPassword();
698 service.clearPassword();
699 if (password == null) {
700 return false;
701 }
702
703 try {
Adrian Roos9dd16eb2015-01-08 16:20:49 +0100704 if (mLockPatternUtils.isLockPatternEnabled(userId)) {
Andres Morales23974272015-05-14 22:42:26 -0700705 if (checkPattern(password, userId).getResponseCode()
706 == GateKeeperResponse.RESPONSE_OK) {
Paul Lawrence945490c2014-03-27 16:37:28 +0000707 return true;
708 }
709 }
710 } catch (Exception e) {
711 }
712
713 try {
Adrian Roos9dd16eb2015-01-08 16:20:49 +0100714 if (mLockPatternUtils.isLockPasswordEnabled(userId)) {
Andres Morales23974272015-05-14 22:42:26 -0700715 if (checkPassword(password, userId).getResponseCode()
716 == GateKeeperResponse.RESPONSE_OK) {
Paul Lawrence945490c2014-03-27 16:37:28 +0000717 return true;
718 }
719 }
720 } catch (Exception e) {
721 }
722
723 return false;
724 }
725
Adrian Roosdb0f76e2015-01-07 22:19:38 +0100726 private void removeUser(int userId) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100727 mStorage.removeUser(userId);
Adrian Roosb5e47222015-08-14 15:53:06 -0700728 mStrongAuth.removeUser(userId);
Robin Lee49d810c2014-09-23 13:50:22 +0100729
730 final KeyStore ks = KeyStore.getInstance();
Chad Brubaker83ce0952015-05-12 13:00:02 -0700731 ks.onUserRemoved(userId);
Andres Morales070fe632015-06-24 10:37:10 -0700732
733 try {
734 final IGateKeeperService gk = getGateKeeperService();
735 if (gk != null) {
736 gk.clearSecureUserId(userId);
737 }
738 } catch (RemoteException ex) {
739 Slog.w(TAG, "unable to clear GK secure user id");
740 }
Amith Yamasani52c489c2012-03-28 11:42:42 -0700741 }
742
Adrian Roosb5e47222015-08-14 15:53:06 -0700743 @Override
744 public void registerStrongAuthTracker(IStrongAuthTracker tracker) {
745 checkPasswordReadPermission(UserHandle.USER_ALL);
746 mStrongAuth.registerStrongAuthTracker(tracker);
747 }
748
749 @Override
750 public void unregisterStrongAuthTracker(IStrongAuthTracker tracker) {
751 checkPasswordReadPermission(UserHandle.USER_ALL);
752 mStrongAuth.unregisterStrongAuthTracker(tracker);
753 }
754
755 @Override
756 public void requireStrongAuth(int strongAuthReason, int userId) {
757 checkWritePermission(userId);
758 mStrongAuth.requireStrongAuth(strongAuthReason, userId);
759 }
760
Amith Yamasani52c489c2012-03-28 11:42:42 -0700761 private static final String[] VALID_SETTINGS = new String[] {
762 LockPatternUtils.LOCKOUT_PERMANENT_KEY,
763 LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE,
764 LockPatternUtils.PATTERN_EVER_CHOSEN_KEY,
765 LockPatternUtils.PASSWORD_TYPE_KEY,
766 LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
767 LockPatternUtils.LOCK_PASSWORD_SALT_KEY,
768 LockPatternUtils.DISABLE_LOCKSCREEN_KEY,
769 LockPatternUtils.LOCKSCREEN_OPTIONS,
770 LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK,
771 LockPatternUtils.BIOMETRIC_WEAK_EVER_CHOSEN_KEY,
772 LockPatternUtils.LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS,
773 LockPatternUtils.PASSWORD_HISTORY_KEY,
774 Secure.LOCK_PATTERN_ENABLED,
775 Secure.LOCK_BIOMETRIC_WEAK_FLAGS,
776 Secure.LOCK_PATTERN_VISIBLE,
777 Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED
Jim Miller187ec582013-04-15 18:27:54 -0700778 };
779
Svetoslav Ganov6d2c0e52015-06-23 16:33:36 +0000780 // Reading these settings needs the contacts permission
781 private static final String[] READ_CONTACTS_PROTECTED_SETTINGS = new String[] {
Jim Miller187ec582013-04-15 18:27:54 -0700782 Secure.LOCK_SCREEN_OWNER_INFO_ENABLED,
783 Secure.LOCK_SCREEN_OWNER_INFO
784 };
Paul Lawrence945490c2014-03-27 16:37:28 +0000785
Adrian Roos001b00d2015-02-24 17:08:48 +0100786 // Reading these settings needs the same permission as checking the password
787 private static final String[] READ_PASSWORD_PROTECTED_SETTINGS = new String[] {
788 LockPatternUtils.LOCK_PASSWORD_SALT_KEY,
789 LockPatternUtils.PASSWORD_HISTORY_KEY,
Adrian Roos855fa302015-04-02 16:01:12 +0200790 LockPatternUtils.PASSWORD_TYPE_KEY,
Adrian Roos001b00d2015-02-24 17:08:48 +0100791 };
792
Amith Yamasani072543f2015-02-13 11:09:45 -0800793 private static final String[] SETTINGS_TO_BACKUP = new String[] {
794 Secure.LOCK_SCREEN_OWNER_INFO_ENABLED,
795 Secure.LOCK_SCREEN_OWNER_INFO
796 };
797
Paul Lawrence945490c2014-03-27 16:37:28 +0000798 private IMountService getMountService() {
799 final IBinder service = ServiceManager.getService("mount");
800 if (service != null) {
801 return IMountService.Stub.asInterface(service);
802 }
803 return null;
804 }
Andres Morales8fa56652015-03-31 09:19:50 -0700805
Andres Morales301ea442015-04-17 09:15:47 -0700806 private class GateKeeperDiedRecipient implements IBinder.DeathRecipient {
807 @Override
808 public void binderDied() {
809 mGateKeeperService.asBinder().unlinkToDeath(this, 0);
810 mGateKeeperService = null;
811 }
812 }
813
814 private synchronized IGateKeeperService getGateKeeperService()
815 throws RemoteException {
Andres Morales8fa56652015-03-31 09:19:50 -0700816 if (mGateKeeperService != null) {
817 return mGateKeeperService;
818 }
819
820 final IBinder service =
821 ServiceManager.getService("android.service.gatekeeper.IGateKeeperService");
822 if (service != null) {
Andres Morales301ea442015-04-17 09:15:47 -0700823 service.linkToDeath(new GateKeeperDiedRecipient(), 0);
Andres Morales8fa56652015-03-31 09:19:50 -0700824 mGateKeeperService = IGateKeeperService.Stub.asInterface(service);
825 return mGateKeeperService;
826 }
827
828 Slog.e(TAG, "Unable to acquire GateKeeperService");
829 return null;
830 }
Amith Yamasani52c489c2012-03-28 11:42:42 -0700831}