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