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