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