LockSettingsService.java revision 493e3e7e6523fd94cc1acae3e45935a1227d58c3
1/* 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 17package com.android.server; 18 19import android.content.ContentResolver; 20import android.content.ContentValues; 21import android.content.Context; 22import android.content.pm.PackageManager; 23import android.content.pm.UserInfo; 24 25import static android.content.Context.USER_SERVICE; 26import static android.Manifest.permission.READ_PROFILE; 27import android.database.Cursor; 28import android.database.sqlite.SQLiteDatabase; 29import android.database.sqlite.SQLiteOpenHelper; 30import android.database.sqlite.SQLiteStatement; 31import android.os.Binder; 32import android.os.Environment; 33import android.os.IBinder; 34import android.os.RemoteException; 35import android.os.storage.IMountService; 36import android.os.ServiceManager; 37import android.os.storage.StorageManager; 38import android.os.SystemProperties; 39import android.os.UserHandle; 40import android.os.UserManager; 41import android.provider.Settings; 42import android.provider.Settings.Secure; 43import android.provider.Settings.SettingNotFoundException; 44import android.security.KeyStore; 45import android.text.TextUtils; 46import android.util.Log; 47import android.util.Slog; 48 49import com.android.internal.widget.ILockSettings; 50import com.android.internal.widget.LockPatternUtils; 51 52import java.io.File; 53import java.io.FileNotFoundException; 54import java.io.IOException; 55import java.io.RandomAccessFile; 56import java.util.Arrays; 57import java.util.List; 58 59/** 60 * Keeps the lock pattern/password data and related settings for each user. 61 * Used by LockPatternUtils. Needs to be a service because Settings app also needs 62 * to be able to save lockscreen information for secondary users. 63 * @hide 64 */ 65public class LockSettingsService extends ILockSettings.Stub { 66 67 private static final String PERMISSION = "android.permission.ACCESS_KEYGUARD_SECURE_STORAGE"; 68 private final DatabaseHelper mOpenHelper; 69 private static final String TAG = "LockSettingsService"; 70 71 private static final String TABLE = "locksettings"; 72 private static final String COLUMN_KEY = "name"; 73 private static final String COLUMN_USERID = "user"; 74 private static final String COLUMN_VALUE = "value"; 75 76 private static final String[] COLUMNS_FOR_QUERY = { 77 COLUMN_VALUE 78 }; 79 80 private static final String SYSTEM_DIRECTORY = "/system/"; 81 private static final String LOCK_PATTERN_FILE = "gesture.key"; 82 private static final String LOCK_PASSWORD_FILE = "password.key"; 83 84 private final Context mContext; 85 private LockPatternUtils mLockPatternUtils; 86 private boolean mFirstCallToVold; 87 88 public LockSettingsService(Context context) { 89 mContext = context; 90 // Open the database 91 mOpenHelper = new DatabaseHelper(mContext); 92 93 mLockPatternUtils = new LockPatternUtils(context); 94 mFirstCallToVold = true; 95 } 96 97 public void systemReady() { 98 migrateOldData(); 99 } 100 101 private void migrateOldData() { 102 try { 103 // These Settings moved before multi-user was enabled, so we only have to do it for the 104 // root user. 105 if (getString("migrated", null, 0) == null) { 106 final ContentResolver cr = mContext.getContentResolver(); 107 for (String validSetting : VALID_SETTINGS) { 108 String value = Settings.Secure.getString(cr, validSetting); 109 if (value != null) { 110 setString(validSetting, value, 0); 111 } 112 } 113 // No need to move the password / pattern files. They're already in the right place. 114 setString("migrated", "true", 0); 115 Slog.i(TAG, "Migrated lock settings to new location"); 116 } 117 118 // These Settings changed after multi-user was enabled, hence need to be moved per user. 119 if (getString("migrated_user_specific", null, 0) == null) { 120 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE); 121 final ContentResolver cr = mContext.getContentResolver(); 122 List<UserInfo> users = um.getUsers(); 123 for (int user = 0; user < users.size(); user++) { 124 // Migrate owner info 125 final int userId = users.get(user).id; 126 final String OWNER_INFO = Secure.LOCK_SCREEN_OWNER_INFO; 127 String ownerInfo = Settings.Secure.getStringForUser(cr, OWNER_INFO, userId); 128 if (ownerInfo != null) { 129 setString(OWNER_INFO, ownerInfo, userId); 130 Settings.Secure.putStringForUser(cr, ownerInfo, "", userId); 131 } 132 133 // Migrate owner info enabled. Note there was a bug where older platforms only 134 // stored this value if the checkbox was toggled at least once. The code detects 135 // this case by handling the exception. 136 final String OWNER_INFO_ENABLED = Secure.LOCK_SCREEN_OWNER_INFO_ENABLED; 137 boolean enabled; 138 try { 139 int ivalue = Settings.Secure.getIntForUser(cr, OWNER_INFO_ENABLED, userId); 140 enabled = ivalue != 0; 141 setLong(OWNER_INFO_ENABLED, enabled ? 1 : 0, userId); 142 } catch (SettingNotFoundException e) { 143 // Setting was never stored. Store it if the string is not empty. 144 if (!TextUtils.isEmpty(ownerInfo)) { 145 setLong(OWNER_INFO_ENABLED, 1, userId); 146 } 147 } 148 Settings.Secure.putIntForUser(cr, OWNER_INFO_ENABLED, 0, userId); 149 } 150 // No need to move the password / pattern files. They're already in the right place. 151 setString("migrated_user_specific", "true", 0); 152 Slog.i(TAG, "Migrated per-user lock settings to new location"); 153 } 154 } catch (RemoteException re) { 155 Slog.e(TAG, "Unable to migrate old data", re); 156 } 157 } 158 159 private final void checkWritePermission(int userId) { 160 mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsWrite"); 161 } 162 163 private final void checkPasswordReadPermission(int userId) { 164 mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsRead"); 165 } 166 167 private final void checkReadPermission(String requestedKey, int userId) { 168 final int callingUid = Binder.getCallingUid(); 169 for (int i = 0; i < READ_PROFILE_PROTECTED_SETTINGS.length; i++) { 170 String key = READ_PROFILE_PROTECTED_SETTINGS[i]; 171 if (key.equals(requestedKey) && mContext.checkCallingOrSelfPermission(READ_PROFILE) 172 != PackageManager.PERMISSION_GRANTED) { 173 throw new SecurityException("uid=" + callingUid 174 + " needs permission " + READ_PROFILE + " to read " 175 + requestedKey + " for user " + userId); 176 } 177 } 178 } 179 180 @Override 181 public void setBoolean(String key, boolean value, int userId) throws RemoteException { 182 checkWritePermission(userId); 183 184 writeToDb(key, value ? "1" : "0", userId); 185 } 186 187 @Override 188 public void setLong(String key, long value, int userId) throws RemoteException { 189 checkWritePermission(userId); 190 191 writeToDb(key, Long.toString(value), userId); 192 } 193 194 @Override 195 public void setString(String key, String value, int userId) throws RemoteException { 196 checkWritePermission(userId); 197 198 writeToDb(key, value, userId); 199 } 200 201 @Override 202 public boolean getBoolean(String key, boolean defaultValue, int userId) throws RemoteException { 203 checkReadPermission(key, userId); 204 205 String value = readFromDb(key, null, userId); 206 return TextUtils.isEmpty(value) ? 207 defaultValue : (value.equals("1") || value.equals("true")); 208 } 209 210 @Override 211 public long getLong(String key, long defaultValue, int userId) throws RemoteException { 212 checkReadPermission(key, userId); 213 214 String value = readFromDb(key, null, userId); 215 return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value); 216 } 217 218 @Override 219 public String getString(String key, String defaultValue, int userId) throws RemoteException { 220 checkReadPermission(key, userId); 221 222 return readFromDb(key, defaultValue, userId); 223 } 224 225 private String getLockPatternFilename(int userId) { 226 String dataSystemDirectory = 227 android.os.Environment.getDataDirectory().getAbsolutePath() + 228 SYSTEM_DIRECTORY; 229 if (userId == 0) { 230 // Leave it in the same place for user 0 231 return dataSystemDirectory + LOCK_PATTERN_FILE; 232 } else { 233 return new File(Environment.getUserSystemDirectory(userId), LOCK_PATTERN_FILE) 234 .getAbsolutePath(); 235 } 236 } 237 238 private String getLockPasswordFilename(int userId) { 239 String dataSystemDirectory = 240 android.os.Environment.getDataDirectory().getAbsolutePath() + 241 SYSTEM_DIRECTORY; 242 if (userId == 0) { 243 // Leave it in the same place for user 0 244 return dataSystemDirectory + LOCK_PASSWORD_FILE; 245 } else { 246 return new File(Environment.getUserSystemDirectory(userId), LOCK_PASSWORD_FILE) 247 .getAbsolutePath(); 248 } 249 } 250 251 @Override 252 public boolean havePassword(int userId) throws RemoteException { 253 // Do we need a permissions check here? 254 255 return new File(getLockPasswordFilename(userId)).length() > 0; 256 } 257 258 @Override 259 public boolean havePattern(int userId) throws RemoteException { 260 // Do we need a permissions check here? 261 262 return new File(getLockPatternFilename(userId)).length() > 0; 263 } 264 265 private void maybeUpdateKeystore(String password, int userId) { 266 if (userId == UserHandle.USER_OWNER) { 267 final KeyStore keyStore = KeyStore.getInstance(); 268 // Conditionally reset the keystore if empty. If non-empty, we are just 269 // switching key guard type 270 if (TextUtils.isEmpty(password) && keyStore.isEmpty()) { 271 keyStore.reset(); 272 } else { 273 // Update the keystore password 274 keyStore.password(password); 275 } 276 } 277 } 278 279 @Override 280 public void setLockPattern(String pattern, int userId) throws RemoteException { 281 checkWritePermission(userId); 282 283 maybeUpdateKeystore(pattern, userId); 284 285 final byte[] hash = LockPatternUtils.patternToHash( 286 LockPatternUtils.stringToPattern(pattern)); 287 writeFile(getLockPatternFilename(userId), hash); 288 } 289 290 @Override 291 public void setLockPassword(String password, int userId) throws RemoteException { 292 checkWritePermission(userId); 293 294 maybeUpdateKeystore(password, userId); 295 296 writeFile(getLockPasswordFilename(userId), mLockPatternUtils.passwordToHash(password)); 297 } 298 299 @Override 300 public boolean checkPattern(String pattern, int userId) throws RemoteException { 301 checkPasswordReadPermission(userId); 302 try { 303 // Read all the bytes from the file 304 RandomAccessFile raf = new RandomAccessFile(getLockPatternFilename(userId), "r"); 305 final byte[] stored = new byte[(int) raf.length()]; 306 int got = raf.read(stored, 0, stored.length); 307 raf.close(); 308 if (got <= 0) { 309 return true; 310 } 311 // Compare the hash from the file with the entered pattern's hash 312 final byte[] hash = LockPatternUtils.patternToHash( 313 LockPatternUtils.stringToPattern(pattern)); 314 final boolean matched = Arrays.equals(stored, hash); 315 if (matched && !TextUtils.isEmpty(pattern)) { 316 maybeUpdateKeystore(pattern, userId); 317 } 318 return matched; 319 } catch (FileNotFoundException fnfe) { 320 Slog.e(TAG, "Cannot read file " + fnfe); 321 } catch (IOException ioe) { 322 Slog.e(TAG, "Cannot read file " + ioe); 323 } 324 return true; 325 } 326 327 @Override 328 public boolean checkPassword(String password, int userId) throws RemoteException { 329 checkPasswordReadPermission(userId); 330 331 try { 332 // Read all the bytes from the file 333 RandomAccessFile raf = new RandomAccessFile(getLockPasswordFilename(userId), "r"); 334 final byte[] stored = new byte[(int) raf.length()]; 335 int got = raf.read(stored, 0, stored.length); 336 raf.close(); 337 if (got <= 0) { 338 return true; 339 } 340 // Compare the hash from the file with the entered password's hash 341 final byte[] hash = mLockPatternUtils.passwordToHash(password); 342 final boolean matched = Arrays.equals(stored, hash); 343 if (matched && !TextUtils.isEmpty(password)) { 344 maybeUpdateKeystore(password, userId); 345 } 346 return matched; 347 } catch (FileNotFoundException fnfe) { 348 Slog.e(TAG, "Cannot read file " + fnfe); 349 } catch (IOException ioe) { 350 Slog.e(TAG, "Cannot read file " + ioe); 351 } 352 return true; 353 } 354 355 @Override 356 public boolean checkVoldPassword(int userId) throws RemoteException { 357 if (!mFirstCallToVold) { 358 return false; 359 } 360 mFirstCallToVold = false; 361 362 checkPasswordReadPermission(userId); 363 364 // There's no guarantee that this will safely connect, but if it fails 365 // we will simply show the lock screen when we shouldn't, so relatively 366 // benign. There is an outside chance something nasty would happen if 367 // this service restarted before vold stales out the password in this 368 // case. The nastiness is limited to not showing the lock screen when 369 // we should, within the first minute of decrypting the phone if this 370 // service can't connect to vold, it restarts, and then the new instance 371 // does successfully connect. 372 final IMountService service = getMountService(); 373 String password = service.getPassword(); 374 service.clearPassword(); 375 if (service.getPasswordType() == StorageManager.CRYPT_TYPE_PATTERN) { 376 return checkPattern(password, userId); 377 } else { 378 return checkPassword(password, userId); 379 } 380 } 381 382 @Override 383 public void removeUser(int userId) { 384 checkWritePermission(userId); 385 386 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 387 try { 388 File file = new File(getLockPasswordFilename(userId)); 389 if (file.exists()) { 390 file.delete(); 391 } 392 file = new File(getLockPatternFilename(userId)); 393 if (file.exists()) { 394 file.delete(); 395 } 396 397 db.beginTransaction(); 398 db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null); 399 db.setTransactionSuccessful(); 400 } finally { 401 db.endTransaction(); 402 } 403 } 404 405 private void writeFile(String name, byte[] hash) { 406 try { 407 // Write the hash to file 408 RandomAccessFile raf = new RandomAccessFile(name, "rw"); 409 // Truncate the file if pattern is null, to clear the lock 410 if (hash == null || hash.length == 0) { 411 raf.setLength(0); 412 } else { 413 raf.write(hash, 0, hash.length); 414 } 415 raf.close(); 416 } catch (IOException ioe) { 417 Slog.e(TAG, "Error writing to file " + ioe); 418 } 419 } 420 421 private void writeToDb(String key, String value, int userId) { 422 writeToDb(mOpenHelper.getWritableDatabase(), key, value, userId); 423 } 424 425 private void writeToDb(SQLiteDatabase db, String key, String value, int userId) { 426 ContentValues cv = new ContentValues(); 427 cv.put(COLUMN_KEY, key); 428 cv.put(COLUMN_USERID, userId); 429 cv.put(COLUMN_VALUE, value); 430 431 db.beginTransaction(); 432 try { 433 db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?", 434 new String[] {key, Integer.toString(userId)}); 435 db.insert(TABLE, null, cv); 436 db.setTransactionSuccessful(); 437 } finally { 438 db.endTransaction(); 439 } 440 } 441 442 private String readFromDb(String key, String defaultValue, int userId) { 443 Cursor cursor; 444 String result = defaultValue; 445 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 446 if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY, 447 COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?", 448 new String[] { Integer.toString(userId), key }, 449 null, null, null)) != null) { 450 if (cursor.moveToFirst()) { 451 result = cursor.getString(0); 452 } 453 cursor.close(); 454 } 455 return result; 456 } 457 458 class DatabaseHelper extends SQLiteOpenHelper { 459 private static final String TAG = "LockSettingsDB"; 460 private static final String DATABASE_NAME = "locksettings.db"; 461 462 private static final int DATABASE_VERSION = 2; 463 464 public DatabaseHelper(Context context) { 465 super(context, DATABASE_NAME, null, DATABASE_VERSION); 466 setWriteAheadLoggingEnabled(true); 467 } 468 469 private void createTable(SQLiteDatabase db) { 470 db.execSQL("CREATE TABLE " + TABLE + " (" + 471 "_id INTEGER PRIMARY KEY AUTOINCREMENT," + 472 COLUMN_KEY + " TEXT," + 473 COLUMN_USERID + " INTEGER," + 474 COLUMN_VALUE + " TEXT" + 475 ");"); 476 } 477 478 @Override 479 public void onCreate(SQLiteDatabase db) { 480 createTable(db); 481 initializeDefaults(db); 482 } 483 484 private void initializeDefaults(SQLiteDatabase db) { 485 // Get the lockscreen default from a system property, if available 486 boolean lockScreenDisable = SystemProperties.getBoolean("ro.lockscreen.disable.default", 487 false); 488 if (lockScreenDisable) { 489 writeToDb(db, LockPatternUtils.DISABLE_LOCKSCREEN_KEY, "1", 0); 490 } 491 } 492 493 @Override 494 public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) { 495 int upgradeVersion = oldVersion; 496 if (upgradeVersion == 1) { 497 // Set the initial value for {@link LockPatternUtils#LOCKSCREEN_WIDGETS_ENABLED} 498 // during upgrade based on whether each user previously had widgets in keyguard. 499 maybeEnableWidgetSettingForUsers(db); 500 upgradeVersion = 2; 501 } 502 503 if (upgradeVersion != DATABASE_VERSION) { 504 Log.w(TAG, "Failed to upgrade database!"); 505 } 506 } 507 508 private void maybeEnableWidgetSettingForUsers(SQLiteDatabase db) { 509 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE); 510 final ContentResolver cr = mContext.getContentResolver(); 511 final List<UserInfo> users = um.getUsers(); 512 for (int i = 0; i < users.size(); i++) { 513 final int userId = users.get(i).id; 514 final boolean enabled = mLockPatternUtils.hasWidgetsEnabledInKeyguard(userId); 515 Log.v(TAG, "Widget upgrade uid=" + userId + ", enabled=" 516 + enabled + ", w[]=" + mLockPatternUtils.getAppWidgets()); 517 loadSetting(db, LockPatternUtils.LOCKSCREEN_WIDGETS_ENABLED, userId, enabled); 518 } 519 } 520 521 private void loadSetting(SQLiteDatabase db, String key, int userId, boolean value) { 522 SQLiteStatement stmt = null; 523 try { 524 stmt = db.compileStatement( 525 "INSERT OR REPLACE INTO locksettings(name,user,value) VALUES(?,?,?);"); 526 stmt.bindString(1, key); 527 stmt.bindLong(2, userId); 528 stmt.bindLong(3, value ? 1 : 0); 529 stmt.execute(); 530 } finally { 531 if (stmt != null) stmt.close(); 532 } 533 } 534 } 535 536 private static final String[] VALID_SETTINGS = new String[] { 537 LockPatternUtils.LOCKOUT_PERMANENT_KEY, 538 LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE, 539 LockPatternUtils.PATTERN_EVER_CHOSEN_KEY, 540 LockPatternUtils.PASSWORD_TYPE_KEY, 541 LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY, 542 LockPatternUtils.LOCK_PASSWORD_SALT_KEY, 543 LockPatternUtils.DISABLE_LOCKSCREEN_KEY, 544 LockPatternUtils.LOCKSCREEN_OPTIONS, 545 LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, 546 LockPatternUtils.BIOMETRIC_WEAK_EVER_CHOSEN_KEY, 547 LockPatternUtils.LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS, 548 LockPatternUtils.PASSWORD_HISTORY_KEY, 549 Secure.LOCK_PATTERN_ENABLED, 550 Secure.LOCK_BIOMETRIC_WEAK_FLAGS, 551 Secure.LOCK_PATTERN_VISIBLE, 552 Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED 553 }; 554 555 // These are protected with a read permission 556 private static final String[] READ_PROFILE_PROTECTED_SETTINGS = new String[] { 557 Secure.LOCK_SCREEN_OWNER_INFO_ENABLED, 558 Secure.LOCK_SCREEN_OWNER_INFO 559 }; 560 561 private IMountService getMountService() { 562 final IBinder service = ServiceManager.getService("mount"); 563 if (service != null) { 564 return IMountService.Stub.asInterface(service); 565 } 566 return null; 567 } 568} 569