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