blob: d52a8e2b3b9b741c64a8c41ed9a4b633767b620b [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 Miller158fe192013-04-17 15:23:55 -070022import android.content.pm.PackageManager;
Jim Miller187ec582013-04-15 18:27:54 -070023import android.content.pm.UserInfo;
24
25import static android.content.Context.USER_SERVICE;
Jim Miller158fe192013-04-17 15:23:55 -070026import static android.Manifest.permission.READ_PROFILE;
Amith Yamasani52c489c2012-03-28 11:42:42 -070027import android.database.Cursor;
28import android.database.sqlite.SQLiteDatabase;
29import android.database.sqlite.SQLiteOpenHelper;
30import android.os.Binder;
Amith Yamasani61f57372012-08-31 12:12:28 -070031import android.os.Environment;
Amith Yamasani52c489c2012-03-28 11:42:42 -070032import android.os.RemoteException;
Amith Yamasanid1645f82012-06-12 11:53:26 -070033import android.os.SystemProperties;
Dianne Hackbornf02b60a2012-08-16 10:48:27 -070034import android.os.UserHandle;
Jim Miller187ec582013-04-15 18:27:54 -070035import android.os.UserManager;
Amith Yamasani52c489c2012-03-28 11:42:42 -070036import android.provider.Settings;
37import android.provider.Settings.Secure;
Jim Miller187ec582013-04-15 18:27:54 -070038import android.provider.Settings.SettingNotFoundException;
Amith Yamasani52c489c2012-03-28 11:42:42 -070039import android.text.TextUtils;
40import android.util.Slog;
41
Jeff Sharkey7a96c392012-11-15 14:01:46 -080042import com.android.internal.widget.ILockSettings;
43import com.android.internal.widget.LockPatternUtils;
44
Amith Yamasani52c489c2012-03-28 11:42:42 -070045import java.io.File;
46import java.io.FileNotFoundException;
47import java.io.IOException;
48import java.io.RandomAccessFile;
49import java.util.Arrays;
Jim Miller187ec582013-04-15 18:27:54 -070050import java.util.List;
Amith Yamasani52c489c2012-03-28 11:42:42 -070051
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 */
58public 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 {
Jim Miller187ec582013-04-15 18:27:54 -070090 // 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");
Amith Yamasani52c489c2012-03-28 11:42:42 -0700103 }
104
Jim Miller187ec582013-04-15 18:27:54 -0700105 // 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++) {
Jim Miller2d8ecf9d2013-04-22 17:17:03 -0700111 // 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 }
Jim Miller187ec582013-04-15 18:27:54 -0700119
Jim Miller2d8ecf9d2013-04-22 17:17:03 -0700120 // 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);
Jim Miller187ec582013-04-15 18:27:54 -0700133 }
134 }
Jim Miller2d8ecf9d2013-04-22 17:17:03 -0700135 Settings.Secure.putIntForUser(cr, OWNER_INFO_ENABLED, 0, userId);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700136 }
Jim Miller187ec582013-04-15 18:27:54 -0700137 // 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");
Amith Yamasani52c489c2012-03-28 11:42:42 -0700140 }
Amith Yamasani52c489c2012-03-28 11:42:42 -0700141 } catch (RemoteException re) {
Jim Miller187ec582013-04-15 18:27:54 -0700142 Slog.e(TAG, "Unable to migrate old data", re);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700143 }
144 }
145
146 private static final void checkWritePermission(int userId) {
147 final int callingUid = Binder.getCallingUid();
Dianne Hackbornf02b60a2012-08-16 10:48:27 -0700148 if (UserHandle.getAppId(callingUid) != android.os.Process.SYSTEM_UID) {
Amith Yamasani52c489c2012-03-28 11:42:42 -0700149 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();
Dianne Hackbornf02b60a2012-08-16 10:48:27 -0700156 if (UserHandle.getAppId(callingUid) != android.os.Process.SYSTEM_UID) {
Amith Yamasani52c489c2012-03-28 11:42:42 -0700157 throw new SecurityException("uid=" + callingUid
158 + " not authorized to read lock password");
159 }
160 }
161
Jim Miller158fe192013-04-17 15:23:55 -0700162 private final void checkReadPermission(String requestedKey, int userId) {
Amith Yamasani52c489c2012-03-28 11:42:42 -0700163 final int callingUid = Binder.getCallingUid();
Jim Miller158fe192013-04-17 15:23:55 -0700164 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 }
Amith Yamasani52c489c2012-03-28 11:42:42 -0700172 }
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 {
Jim Miller158fe192013-04-17 15:23:55 -0700198 checkReadPermission(key, userId);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700199
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 {
Jim Miller158fe192013-04-17 15:23:55 -0700207 checkReadPermission(key, userId);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700208
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 {
Jim Miller158fe192013-04-17 15:23:55 -0700215 checkReadPermission(key, userId);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700216
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 {
Amith Yamasani61f57372012-08-31 12:12:28 -0700228 return new File(Environment.getUserSystemDirectory(userId), LOCK_PATTERN_FILE)
229 .getAbsolutePath();
Amith Yamasani52c489c2012-03-28 11:42:42 -0700230 }
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 {
Amith Yamasani61f57372012-08-31 12:12:28 -0700241 return new File(Environment.getUserSystemDirectory(userId), LOCK_PASSWORD_FILE)
242 .getAbsolutePath();
Amith Yamasani52c489c2012-03-28 11:42:42 -0700243 }
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) {
Amith Yamasanid1645f82012-06-12 11:53:26 -0700361 writeToDb(mOpenHelper.getWritableDatabase(), key, value, userId);
362 }
363
364 private void writeToDb(SQLiteDatabase db, String key, String value, int userId) {
Amith Yamasani52c489c2012-03-28 11:42:42 -0700365 ContentValues cv = new ContentValues();
366 cv.put(COLUMN_KEY, key);
367 cv.put(COLUMN_USERID, userId);
368 cv.put(COLUMN_VALUE, value);
369
Amith Yamasani52c489c2012-03-28 11:42:42 -0700370 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);
Amith Yamasanid1645f82012-06-12 11:53:26 -0700420 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 }
Amith Yamasani52c489c2012-03-28 11:42:42 -0700430 }
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
Jim Miller187ec582013-04-15 18:27:54 -0700455 };
456
Jim Miller2d8ecf9d2013-04-22 17:17:03 -0700457 // These are protected with a read permission
458 private static final String[] READ_PROFILE_PROTECTED_SETTINGS = new String[] {
Jim Miller187ec582013-04-15 18:27:54 -0700459 Secure.LOCK_SCREEN_OWNER_INFO_ENABLED,
460 Secure.LOCK_SCREEN_OWNER_INFO
461 };
Amith Yamasani52c489c2012-03-28 11:42:42 -0700462}