blob: c5555c8716801d3e81fe9364954f6b1574262610 [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
Jim Millerf45bb402013-08-20 18:58:32 -070019import android.app.ActivityManagerNative;
Amith Yamasani52c489c2012-03-28 11:42:42 -070020import android.content.ContentResolver;
21import android.content.ContentValues;
22import android.content.Context;
Jim Miller158fe192013-04-17 15:23:55 -070023import android.content.pm.PackageManager;
Jim Miller187ec582013-04-15 18:27:54 -070024import android.content.pm.UserInfo;
25
26import static android.content.Context.USER_SERVICE;
Jim Miller158fe192013-04-17 15:23:55 -070027import static android.Manifest.permission.READ_PROFILE;
Amith Yamasani52c489c2012-03-28 11:42:42 -070028import android.database.Cursor;
29import android.database.sqlite.SQLiteDatabase;
30import android.database.sqlite.SQLiteOpenHelper;
Jim Millerf45bb402013-08-20 18:58:32 -070031import android.database.sqlite.SQLiteStatement;
32import android.media.AudioManager;
33import android.media.AudioService;
Amith Yamasani52c489c2012-03-28 11:42:42 -070034import android.os.Binder;
Amith Yamasani61f57372012-08-31 12:12:28 -070035import android.os.Environment;
Amith Yamasani52c489c2012-03-28 11:42:42 -070036import android.os.RemoteException;
Amith Yamasanid1645f82012-06-12 11:53:26 -070037import android.os.SystemProperties;
Dianne Hackbornf02b60a2012-08-16 10:48:27 -070038import android.os.UserHandle;
Jim Miller187ec582013-04-15 18:27:54 -070039import android.os.UserManager;
Amith Yamasani52c489c2012-03-28 11:42:42 -070040import android.provider.Settings;
41import android.provider.Settings.Secure;
Jim Miller187ec582013-04-15 18:27:54 -070042import android.provider.Settings.SettingNotFoundException;
Amith Yamasani52c489c2012-03-28 11:42:42 -070043import android.text.TextUtils;
Jim Millerf45bb402013-08-20 18:58:32 -070044import android.util.Log;
Amith Yamasani52c489c2012-03-28 11:42:42 -070045import android.util.Slog;
46
Jeff Sharkey7a96c392012-11-15 14:01:46 -080047import com.android.internal.widget.ILockSettings;
48import com.android.internal.widget.LockPatternUtils;
49
Amith Yamasani52c489c2012-03-28 11:42:42 -070050import java.io.File;
51import java.io.FileNotFoundException;
52import java.io.IOException;
53import java.io.RandomAccessFile;
54import java.util.Arrays;
Jim Miller187ec582013-04-15 18:27:54 -070055import java.util.List;
Amith Yamasani52c489c2012-03-28 11:42:42 -070056
57/**
58 * Keeps the lock pattern/password data and related settings for each user.
59 * Used by LockPatternUtils. Needs to be a service because Settings app also needs
60 * to be able to save lockscreen information for secondary users.
61 * @hide
62 */
63public class LockSettingsService extends ILockSettings.Stub {
64
Jim Miller5ecd8112013-01-09 18:50:26 -080065 private static final String PERMISSION = "android.permission.ACCESS_KEYGUARD_SECURE_STORAGE";
Amith Yamasani52c489c2012-03-28 11:42:42 -070066 private final DatabaseHelper mOpenHelper;
67 private static final String TAG = "LockSettingsService";
68
69 private static final String TABLE = "locksettings";
70 private static final String COLUMN_KEY = "name";
71 private static final String COLUMN_USERID = "user";
72 private static final String COLUMN_VALUE = "value";
73
74 private static final String[] COLUMNS_FOR_QUERY = {
75 COLUMN_VALUE
76 };
77
78 private static final String SYSTEM_DIRECTORY = "/system/";
79 private static final String LOCK_PATTERN_FILE = "gesture.key";
80 private static final String LOCK_PASSWORD_FILE = "password.key";
81
82 private final Context mContext;
83
84 public LockSettingsService(Context context) {
85 mContext = context;
86 // Open the database
87 mOpenHelper = new DatabaseHelper(mContext);
88 }
89
90 public void systemReady() {
91 migrateOldData();
92 }
93
94 private void migrateOldData() {
95 try {
Jim Miller187ec582013-04-15 18:27:54 -070096 // These Settings moved before multi-user was enabled, so we only have to do it for the
97 // root user.
98 if (getString("migrated", null, 0) == null) {
99 final ContentResolver cr = mContext.getContentResolver();
100 for (String validSetting : VALID_SETTINGS) {
101 String value = Settings.Secure.getString(cr, validSetting);
102 if (value != null) {
103 setString(validSetting, value, 0);
104 }
105 }
106 // No need to move the password / pattern files. They're already in the right place.
107 setString("migrated", "true", 0);
108 Slog.i(TAG, "Migrated lock settings to new location");
Amith Yamasani52c489c2012-03-28 11:42:42 -0700109 }
110
Jim Miller187ec582013-04-15 18:27:54 -0700111 // These Settings changed after multi-user was enabled, hence need to be moved per user.
112 if (getString("migrated_user_specific", null, 0) == null) {
113 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
114 final ContentResolver cr = mContext.getContentResolver();
115 List<UserInfo> users = um.getUsers();
116 for (int user = 0; user < users.size(); user++) {
Jim Miller2d8ecf9d2013-04-22 17:17:03 -0700117 // Migrate owner info
118 final int userId = users.get(user).id;
119 final String OWNER_INFO = Secure.LOCK_SCREEN_OWNER_INFO;
120 String ownerInfo = Settings.Secure.getStringForUser(cr, OWNER_INFO, userId);
121 if (ownerInfo != null) {
122 setString(OWNER_INFO, ownerInfo, userId);
123 Settings.Secure.putStringForUser(cr, ownerInfo, "", userId);
124 }
Jim Miller187ec582013-04-15 18:27:54 -0700125
Jim Miller2d8ecf9d2013-04-22 17:17:03 -0700126 // Migrate owner info enabled. Note there was a bug where older platforms only
127 // stored this value if the checkbox was toggled at least once. The code detects
128 // this case by handling the exception.
129 final String OWNER_INFO_ENABLED = Secure.LOCK_SCREEN_OWNER_INFO_ENABLED;
130 boolean enabled;
131 try {
132 int ivalue = Settings.Secure.getIntForUser(cr, OWNER_INFO_ENABLED, userId);
133 enabled = ivalue != 0;
134 setLong(OWNER_INFO_ENABLED, enabled ? 1 : 0, userId);
135 } catch (SettingNotFoundException e) {
136 // Setting was never stored. Store it if the string is not empty.
137 if (!TextUtils.isEmpty(ownerInfo)) {
138 setLong(OWNER_INFO_ENABLED, 1, userId);
Jim Miller187ec582013-04-15 18:27:54 -0700139 }
140 }
Jim Miller2d8ecf9d2013-04-22 17:17:03 -0700141 Settings.Secure.putIntForUser(cr, OWNER_INFO_ENABLED, 0, userId);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700142 }
Jim Miller187ec582013-04-15 18:27:54 -0700143 // No need to move the password / pattern files. They're already in the right place.
144 setString("migrated_user_specific", "true", 0);
145 Slog.i(TAG, "Migrated per-user lock settings to new location");
Amith Yamasani52c489c2012-03-28 11:42:42 -0700146 }
Amith Yamasani52c489c2012-03-28 11:42:42 -0700147 } catch (RemoteException re) {
Jim Miller187ec582013-04-15 18:27:54 -0700148 Slog.e(TAG, "Unable to migrate old data", re);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700149 }
150 }
151
Jim Miller5ecd8112013-01-09 18:50:26 -0800152 private final void checkWritePermission(int userId) {
153 mContext.checkCallingOrSelfPermission(PERMISSION);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700154 }
155
Jim Miller5ecd8112013-01-09 18:50:26 -0800156 private final void checkPasswordReadPermission(int userId) {
157 mContext.checkCallingOrSelfPermission(PERMISSION);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700158 }
159
Jim Miller158fe192013-04-17 15:23:55 -0700160 private final void checkReadPermission(String requestedKey, int userId) {
Amith Yamasani52c489c2012-03-28 11:42:42 -0700161 final int callingUid = Binder.getCallingUid();
Jim Miller158fe192013-04-17 15:23:55 -0700162 for (int i = 0; i < READ_PROFILE_PROTECTED_SETTINGS.length; i++) {
163 String key = READ_PROFILE_PROTECTED_SETTINGS[i];
164 if (key.equals(requestedKey) && mContext.checkCallingOrSelfPermission(READ_PROFILE)
165 != PackageManager.PERMISSION_GRANTED) {
166 throw new SecurityException("uid=" + callingUid
167 + " needs permission " + READ_PROFILE + " to read "
168 + requestedKey + " for user " + userId);
169 }
Amith Yamasani52c489c2012-03-28 11:42:42 -0700170 }
171 }
172
173 @Override
174 public void setBoolean(String key, boolean value, int userId) throws RemoteException {
175 checkWritePermission(userId);
176
177 writeToDb(key, value ? "1" : "0", userId);
178 }
179
180 @Override
181 public void setLong(String key, long value, int userId) throws RemoteException {
182 checkWritePermission(userId);
183
184 writeToDb(key, Long.toString(value), userId);
185 }
186
187 @Override
188 public void setString(String key, String value, int userId) throws RemoteException {
189 checkWritePermission(userId);
190
191 writeToDb(key, value, userId);
192 }
193
194 @Override
195 public boolean getBoolean(String key, boolean defaultValue, int userId) throws RemoteException {
Jim Miller158fe192013-04-17 15:23:55 -0700196 checkReadPermission(key, userId);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700197
198 String value = readFromDb(key, null, userId);
199 return TextUtils.isEmpty(value) ?
200 defaultValue : (value.equals("1") || value.equals("true"));
201 }
202
203 @Override
204 public long getLong(String key, long defaultValue, int userId) throws RemoteException {
Jim Miller158fe192013-04-17 15:23:55 -0700205 checkReadPermission(key, userId);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700206
207 String value = readFromDb(key, null, userId);
208 return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value);
209 }
210
211 @Override
212 public String getString(String key, String defaultValue, int userId) throws RemoteException {
Jim Miller158fe192013-04-17 15:23:55 -0700213 checkReadPermission(key, userId);
Amith Yamasani52c489c2012-03-28 11:42:42 -0700214
215 return readFromDb(key, defaultValue, userId);
216 }
217
218 private String getLockPatternFilename(int userId) {
219 String dataSystemDirectory =
220 android.os.Environment.getDataDirectory().getAbsolutePath() +
221 SYSTEM_DIRECTORY;
222 if (userId == 0) {
223 // Leave it in the same place for user 0
224 return dataSystemDirectory + LOCK_PATTERN_FILE;
225 } else {
Amith Yamasani61f57372012-08-31 12:12:28 -0700226 return new File(Environment.getUserSystemDirectory(userId), LOCK_PATTERN_FILE)
227 .getAbsolutePath();
Amith Yamasani52c489c2012-03-28 11:42:42 -0700228 }
229 }
230
231 private String getLockPasswordFilename(int userId) {
232 String dataSystemDirectory =
233 android.os.Environment.getDataDirectory().getAbsolutePath() +
234 SYSTEM_DIRECTORY;
235 if (userId == 0) {
236 // Leave it in the same place for user 0
237 return dataSystemDirectory + LOCK_PASSWORD_FILE;
238 } else {
Amith Yamasani61f57372012-08-31 12:12:28 -0700239 return new File(Environment.getUserSystemDirectory(userId), LOCK_PASSWORD_FILE)
240 .getAbsolutePath();
Amith Yamasani52c489c2012-03-28 11:42:42 -0700241 }
242 }
243
244 @Override
245 public boolean havePassword(int userId) throws RemoteException {
246 // Do we need a permissions check here?
247
248 return new File(getLockPasswordFilename(userId)).length() > 0;
249 }
250
251 @Override
252 public boolean havePattern(int userId) throws RemoteException {
253 // Do we need a permissions check here?
254
255 return new File(getLockPatternFilename(userId)).length() > 0;
256 }
257
258 @Override
259 public void setLockPattern(byte[] hash, int userId) throws RemoteException {
260 checkWritePermission(userId);
261
262 writeFile(getLockPatternFilename(userId), hash);
263 }
264
265 @Override
266 public boolean checkPattern(byte[] hash, int userId) throws RemoteException {
267 checkPasswordReadPermission(userId);
268 try {
269 // Read all the bytes from the file
270 RandomAccessFile raf = new RandomAccessFile(getLockPatternFilename(userId), "r");
271 final byte[] stored = new byte[(int) raf.length()];
272 int got = raf.read(stored, 0, stored.length);
273 raf.close();
274 if (got <= 0) {
275 return true;
276 }
277 // Compare the hash from the file with the entered pattern's hash
278 return Arrays.equals(stored, hash);
279 } catch (FileNotFoundException fnfe) {
280 Slog.e(TAG, "Cannot read file " + fnfe);
281 return true;
282 } catch (IOException ioe) {
283 Slog.e(TAG, "Cannot read file " + ioe);
284 return true;
285 }
286 }
287
288 @Override
289 public void setLockPassword(byte[] hash, int userId) throws RemoteException {
290 checkWritePermission(userId);
291
292 writeFile(getLockPasswordFilename(userId), hash);
293 }
294
295 @Override
296 public boolean checkPassword(byte[] hash, int userId) throws RemoteException {
297 checkPasswordReadPermission(userId);
298
299 try {
300 // Read all the bytes from the file
301 RandomAccessFile raf = new RandomAccessFile(getLockPasswordFilename(userId), "r");
302 final byte[] stored = new byte[(int) raf.length()];
303 int got = raf.read(stored, 0, stored.length);
304 raf.close();
305 if (got <= 0) {
306 return true;
307 }
308 // Compare the hash from the file with the entered password's hash
309 return Arrays.equals(stored, hash);
310 } catch (FileNotFoundException fnfe) {
311 Slog.e(TAG, "Cannot read file " + fnfe);
312 return true;
313 } catch (IOException ioe) {
314 Slog.e(TAG, "Cannot read file " + ioe);
315 return true;
316 }
317 }
318
319 @Override
320 public void removeUser(int userId) {
321 checkWritePermission(userId);
322
323 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
324 try {
325 File file = new File(getLockPasswordFilename(userId));
326 if (file.exists()) {
327 file.delete();
328 }
329 file = new File(getLockPatternFilename(userId));
330 if (file.exists()) {
331 file.delete();
332 }
333
334 db.beginTransaction();
335 db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null);
336 db.setTransactionSuccessful();
337 } finally {
338 db.endTransaction();
339 }
340 }
341
342 private void writeFile(String name, byte[] hash) {
343 try {
344 // Write the hash to file
345 RandomAccessFile raf = new RandomAccessFile(name, "rw");
346 // Truncate the file if pattern is null, to clear the lock
347 if (hash == null || hash.length == 0) {
348 raf.setLength(0);
349 } else {
350 raf.write(hash, 0, hash.length);
351 }
352 raf.close();
353 } catch (IOException ioe) {
354 Slog.e(TAG, "Error writing to file " + ioe);
355 }
356 }
357
358 private void writeToDb(String key, String value, int userId) {
Amith Yamasanid1645f82012-06-12 11:53:26 -0700359 writeToDb(mOpenHelper.getWritableDatabase(), key, value, userId);
360 }
361
362 private void writeToDb(SQLiteDatabase db, String key, String value, int userId) {
Amith Yamasani52c489c2012-03-28 11:42:42 -0700363 ContentValues cv = new ContentValues();
364 cv.put(COLUMN_KEY, key);
365 cv.put(COLUMN_USERID, userId);
366 cv.put(COLUMN_VALUE, value);
367
Amith Yamasani52c489c2012-03-28 11:42:42 -0700368 db.beginTransaction();
369 try {
370 db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?",
371 new String[] {key, Integer.toString(userId)});
372 db.insert(TABLE, null, cv);
373 db.setTransactionSuccessful();
374 } finally {
375 db.endTransaction();
376 }
377 }
378
379 private String readFromDb(String key, String defaultValue, int userId) {
380 Cursor cursor;
381 String result = defaultValue;
382 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
383 if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY,
384 COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?",
385 new String[] { Integer.toString(userId), key },
386 null, null, null)) != null) {
387 if (cursor.moveToFirst()) {
388 result = cursor.getString(0);
389 }
390 cursor.close();
391 }
392 return result;
393 }
394
395 class DatabaseHelper extends SQLiteOpenHelper {
396 private static final String TAG = "LockSettingsDB";
397 private static final String DATABASE_NAME = "locksettings.db";
398
Jim Millerf45bb402013-08-20 18:58:32 -0700399 private static final int DATABASE_VERSION = 2;
Amith Yamasani52c489c2012-03-28 11:42:42 -0700400
401 public DatabaseHelper(Context context) {
402 super(context, DATABASE_NAME, null, DATABASE_VERSION);
403 setWriteAheadLoggingEnabled(true);
404 }
405
406 private void createTable(SQLiteDatabase db) {
407 db.execSQL("CREATE TABLE " + TABLE + " (" +
408 "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
409 COLUMN_KEY + " TEXT," +
410 COLUMN_USERID + " INTEGER," +
411 COLUMN_VALUE + " TEXT" +
412 ");");
413 }
414
415 @Override
416 public void onCreate(SQLiteDatabase db) {
417 createTable(db);
Amith Yamasanid1645f82012-06-12 11:53:26 -0700418 initializeDefaults(db);
419 }
420
421 private void initializeDefaults(SQLiteDatabase db) {
422 // Get the lockscreen default from a system property, if available
423 boolean lockScreenDisable = SystemProperties.getBoolean("ro.lockscreen.disable.default",
424 false);
425 if (lockScreenDisable) {
426 writeToDb(db, LockPatternUtils.DISABLE_LOCKSCREEN_KEY, "1", 0);
427 }
Amith Yamasani52c489c2012-03-28 11:42:42 -0700428 }
429
430 @Override
431 public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
Jim Millerf45bb402013-08-20 18:58:32 -0700432 int upgradeVersion = oldVersion;
433 if (upgradeVersion == 1) {
434 // Set the initial value for {@link LockPatternUtils#LOCKSCREEN_WIDGETS_ENABLED}
435 // during upgrade based on whether each user previously had widgets in keyguard.
436 maybeEnableWidgetSettingForUsers(db);
437 upgradeVersion = 2;
438 }
439
440 if (upgradeVersion != DATABASE_VERSION) {
441 Log.w(TAG, "Failed to upgrade database!");
442 }
443 }
444
445 private void maybeEnableWidgetSettingForUsers(SQLiteDatabase db) {
446 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
447 final ContentResolver cr = mContext.getContentResolver();
448 final LockPatternUtils utils = new LockPatternUtils(mContext);
449 final List<UserInfo> users = um.getUsers();
450 for (int i = 0; i < users.size(); i++) {
451 final int userId = users.get(i).id;
452 final boolean enabled = utils.hasWidgetsEnabledInKeyguard(userId);
453 Log.v(TAG, "Widget upgrade uid=" + userId + ", enabled="
454 + enabled + ", w[]=" + utils.getAppWidgets());
455 loadSetting(db, LockPatternUtils.LOCKSCREEN_WIDGETS_ENABLED, userId, enabled);
456 }
457 }
458
459 private void loadSetting(SQLiteDatabase db, String key, int userId, boolean value) {
460 SQLiteStatement stmt = null;
461 try {
462 stmt = db.compileStatement(
463 "INSERT OR REPLACE INTO locksettings(name,user,value) VALUES(?,?,?);");
464 stmt.bindString(1, key);
465 stmt.bindLong(2, userId);
466 stmt.bindLong(3, value ? 1 : 0);
467 stmt.execute();
468 } finally {
469 if (stmt != null) stmt.close();
470 }
Amith Yamasani52c489c2012-03-28 11:42:42 -0700471 }
472 }
473
474 private static final String[] VALID_SETTINGS = new String[] {
475 LockPatternUtils.LOCKOUT_PERMANENT_KEY,
476 LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE,
477 LockPatternUtils.PATTERN_EVER_CHOSEN_KEY,
478 LockPatternUtils.PASSWORD_TYPE_KEY,
479 LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
480 LockPatternUtils.LOCK_PASSWORD_SALT_KEY,
481 LockPatternUtils.DISABLE_LOCKSCREEN_KEY,
482 LockPatternUtils.LOCKSCREEN_OPTIONS,
483 LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK,
484 LockPatternUtils.BIOMETRIC_WEAK_EVER_CHOSEN_KEY,
485 LockPatternUtils.LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS,
486 LockPatternUtils.PASSWORD_HISTORY_KEY,
487 Secure.LOCK_PATTERN_ENABLED,
488 Secure.LOCK_BIOMETRIC_WEAK_FLAGS,
489 Secure.LOCK_PATTERN_VISIBLE,
490 Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED
Jim Miller187ec582013-04-15 18:27:54 -0700491 };
492
Jim Miller2d8ecf9d2013-04-22 17:17:03 -0700493 // These are protected with a read permission
494 private static final String[] READ_PROFILE_PROTECTED_SETTINGS = new String[] {
Jim Miller187ec582013-04-15 18:27:54 -0700495 Secure.LOCK_SCREEN_OWNER_INFO_ENABLED,
496 Secure.LOCK_SCREEN_OWNER_INFO
497 };
Amith Yamasani52c489c2012-03-28 11:42:42 -0700498}