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