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