• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.server;
18 
19 import android.app.ActivityManagerNative;
20 import android.content.ContentResolver;
21 import android.content.ContentValues;
22 import android.content.Context;
23 import android.content.pm.PackageManager;
24 import android.content.pm.UserInfo;
25 
26 import static android.content.Context.USER_SERVICE;
27 import static android.Manifest.permission.READ_PROFILE;
28 import android.database.Cursor;
29 import android.database.sqlite.SQLiteDatabase;
30 import android.database.sqlite.SQLiteOpenHelper;
31 import android.database.sqlite.SQLiteStatement;
32 import android.media.AudioManager;
33 import android.media.AudioService;
34 import android.os.Binder;
35 import android.os.Environment;
36 import android.os.RemoteException;
37 import android.os.SystemProperties;
38 import android.os.UserHandle;
39 import android.os.UserManager;
40 import android.provider.Settings;
41 import android.provider.Settings.Secure;
42 import android.provider.Settings.SettingNotFoundException;
43 import android.security.KeyStore;
44 import android.text.TextUtils;
45 import android.util.Log;
46 import android.util.Slog;
47 
48 import com.android.internal.widget.ILockSettings;
49 import com.android.internal.widget.LockPatternUtils;
50 
51 import java.io.File;
52 import java.io.FileNotFoundException;
53 import java.io.IOException;
54 import java.io.RandomAccessFile;
55 import java.util.Arrays;
56 import java.util.List;
57 
58 /**
59  * Keeps the lock pattern/password data and related settings for each user.
60  * Used by LockPatternUtils. Needs to be a service because Settings app also needs
61  * to be able to save lockscreen information for secondary users.
62  * @hide
63  */
64 public class LockSettingsService extends ILockSettings.Stub {
65 
66     private static final String PERMISSION = "android.permission.ACCESS_KEYGUARD_SECURE_STORAGE";
67     private final DatabaseHelper mOpenHelper;
68     private static final String TAG = "LockSettingsService";
69 
70     private static final String TABLE = "locksettings";
71     private static final String COLUMN_KEY = "name";
72     private static final String COLUMN_USERID = "user";
73     private static final String COLUMN_VALUE = "value";
74 
75     private static final String[] COLUMNS_FOR_QUERY = {
76         COLUMN_VALUE
77     };
78 
79     private static final String SYSTEM_DIRECTORY = "/system/";
80     private static final String LOCK_PATTERN_FILE = "gesture.key";
81     private static final String LOCK_PASSWORD_FILE = "password.key";
82 
83     private final Context mContext;
84     private LockPatternUtils mLockPatternUtils;
85 
LockSettingsService(Context context)86     public LockSettingsService(Context context) {
87         mContext = context;
88         // Open the database
89         mOpenHelper = new DatabaseHelper(mContext);
90 
91         mLockPatternUtils = new LockPatternUtils(context);
92     }
93 
systemReady()94     public void systemReady() {
95         migrateOldData();
96     }
97 
migrateOldData()98     private void migrateOldData() {
99         try {
100             // These Settings moved before multi-user was enabled, so we only have to do it for the
101             // root user.
102             if (getString("migrated", null, 0) == null) {
103                 final ContentResolver cr = mContext.getContentResolver();
104                 for (String validSetting : VALID_SETTINGS) {
105                     String value = Settings.Secure.getString(cr, validSetting);
106                     if (value != null) {
107                         setString(validSetting, value, 0);
108                     }
109                 }
110                 // No need to move the password / pattern files. They're already in the right place.
111                 setString("migrated", "true", 0);
112                 Slog.i(TAG, "Migrated lock settings to new location");
113             }
114 
115             // These Settings changed after multi-user was enabled, hence need to be moved per user.
116             if (getString("migrated_user_specific", null, 0) == null) {
117                 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
118                 final ContentResolver cr = mContext.getContentResolver();
119                 List<UserInfo> users = um.getUsers();
120                 for (int user = 0; user < users.size(); user++) {
121                     // Migrate owner info
122                     final int userId = users.get(user).id;
123                     final String OWNER_INFO = Secure.LOCK_SCREEN_OWNER_INFO;
124                     String ownerInfo = Settings.Secure.getStringForUser(cr, OWNER_INFO, userId);
125                     if (ownerInfo != null) {
126                         setString(OWNER_INFO, ownerInfo, userId);
127                         Settings.Secure.putStringForUser(cr, ownerInfo, "", userId);
128                     }
129 
130                     // Migrate owner info enabled.  Note there was a bug where older platforms only
131                     // stored this value if the checkbox was toggled at least once. The code detects
132                     // this case by handling the exception.
133                     final String OWNER_INFO_ENABLED = Secure.LOCK_SCREEN_OWNER_INFO_ENABLED;
134                     boolean enabled;
135                     try {
136                         int ivalue = Settings.Secure.getIntForUser(cr, OWNER_INFO_ENABLED, userId);
137                         enabled = ivalue != 0;
138                         setLong(OWNER_INFO_ENABLED, enabled ? 1 : 0, userId);
139                     } catch (SettingNotFoundException e) {
140                         // Setting was never stored. Store it if the string is not empty.
141                         if (!TextUtils.isEmpty(ownerInfo)) {
142                             setLong(OWNER_INFO_ENABLED, 1, userId);
143                         }
144                     }
145                     Settings.Secure.putIntForUser(cr, OWNER_INFO_ENABLED, 0, userId);
146                 }
147                 // No need to move the password / pattern files. They're already in the right place.
148                 setString("migrated_user_specific", "true", 0);
149                 Slog.i(TAG, "Migrated per-user lock settings to new location");
150             }
151         } catch (RemoteException re) {
152             Slog.e(TAG, "Unable to migrate old data", re);
153         }
154     }
155 
checkWritePermission(int userId)156     private final void checkWritePermission(int userId) {
157         mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsWrite");
158     }
159 
checkPasswordReadPermission(int userId)160     private final void checkPasswordReadPermission(int userId) {
161         mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsRead");
162     }
163 
checkReadPermission(String requestedKey, int userId)164     private final void checkReadPermission(String requestedKey, int userId) {
165         final int callingUid = Binder.getCallingUid();
166         for (int i = 0; i < READ_PROFILE_PROTECTED_SETTINGS.length; i++) {
167             String key = READ_PROFILE_PROTECTED_SETTINGS[i];
168             if (key.equals(requestedKey) && mContext.checkCallingOrSelfPermission(READ_PROFILE)
169                     != PackageManager.PERMISSION_GRANTED) {
170                 throw new SecurityException("uid=" + callingUid
171                         + " needs permission " + READ_PROFILE + " to read "
172                         + requestedKey + " for user " + userId);
173             }
174         }
175     }
176 
177     @Override
setBoolean(String key, boolean value, int userId)178     public void setBoolean(String key, boolean value, int userId) throws RemoteException {
179         checkWritePermission(userId);
180 
181         writeToDb(key, value ? "1" : "0", userId);
182     }
183 
184     @Override
setLong(String key, long value, int userId)185     public void setLong(String key, long value, int userId) throws RemoteException {
186         checkWritePermission(userId);
187 
188         writeToDb(key, Long.toString(value), userId);
189     }
190 
191     @Override
setString(String key, String value, int userId)192     public void setString(String key, String value, int userId) throws RemoteException {
193         checkWritePermission(userId);
194 
195         writeToDb(key, value, userId);
196     }
197 
198     @Override
getBoolean(String key, boolean defaultValue, int userId)199     public boolean getBoolean(String key, boolean defaultValue, int userId) throws RemoteException {
200         checkReadPermission(key, userId);
201 
202         String value = readFromDb(key, null, userId);
203         return TextUtils.isEmpty(value) ?
204                 defaultValue : (value.equals("1") || value.equals("true"));
205     }
206 
207     @Override
getLong(String key, long defaultValue, int userId)208     public long getLong(String key, long defaultValue, int userId) throws RemoteException {
209         checkReadPermission(key, userId);
210 
211         String value = readFromDb(key, null, userId);
212         return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value);
213     }
214 
215     @Override
getString(String key, String defaultValue, int userId)216     public String getString(String key, String defaultValue, int userId) throws RemoteException {
217         checkReadPermission(key, userId);
218 
219         return readFromDb(key, defaultValue, userId);
220     }
221 
getLockPatternFilename(int userId)222     private String getLockPatternFilename(int userId) {
223         String dataSystemDirectory =
224                 android.os.Environment.getDataDirectory().getAbsolutePath() +
225                 SYSTEM_DIRECTORY;
226         if (userId == 0) {
227             // Leave it in the same place for user 0
228             return dataSystemDirectory + LOCK_PATTERN_FILE;
229         } else {
230             return  new File(Environment.getUserSystemDirectory(userId), LOCK_PATTERN_FILE)
231                     .getAbsolutePath();
232         }
233     }
234 
getLockPasswordFilename(int userId)235     private String getLockPasswordFilename(int userId) {
236         String dataSystemDirectory =
237                 android.os.Environment.getDataDirectory().getAbsolutePath() +
238                 SYSTEM_DIRECTORY;
239         if (userId == 0) {
240             // Leave it in the same place for user 0
241             return dataSystemDirectory + LOCK_PASSWORD_FILE;
242         } else {
243             return  new File(Environment.getUserSystemDirectory(userId), LOCK_PASSWORD_FILE)
244                     .getAbsolutePath();
245         }
246     }
247 
248     @Override
havePassword(int userId)249     public boolean havePassword(int userId) throws RemoteException {
250         // Do we need a permissions check here?
251 
252         return new File(getLockPasswordFilename(userId)).length() > 0;
253     }
254 
255     @Override
havePattern(int userId)256     public boolean havePattern(int userId) throws RemoteException {
257         // Do we need a permissions check here?
258 
259         return new File(getLockPatternFilename(userId)).length() > 0;
260     }
261 
maybeUpdateKeystore(String password, int userId)262     private void maybeUpdateKeystore(String password, int userId) {
263         if (userId == UserHandle.USER_OWNER) {
264             final KeyStore keyStore = KeyStore.getInstance();
265             // Conditionally reset the keystore if empty. If non-empty, we are just
266             // switching key guard type
267             if (TextUtils.isEmpty(password) && keyStore.isEmpty()) {
268                 keyStore.reset();
269             } else {
270                 // Update the keystore password
271                 keyStore.password(password);
272             }
273         }
274     }
275 
276     @Override
setLockPattern(String pattern, int userId)277     public void setLockPattern(String pattern, int userId) throws RemoteException {
278         checkWritePermission(userId);
279 
280         maybeUpdateKeystore(pattern, userId);
281 
282         final byte[] hash = LockPatternUtils.patternToHash(
283                 LockPatternUtils.stringToPattern(pattern));
284         writeFile(getLockPatternFilename(userId), hash);
285     }
286 
287     @Override
setLockPassword(String password, int userId)288     public void setLockPassword(String password, int userId) throws RemoteException {
289         checkWritePermission(userId);
290 
291         maybeUpdateKeystore(password, userId);
292 
293         writeFile(getLockPasswordFilename(userId), mLockPatternUtils.passwordToHash(password));
294     }
295 
296     @Override
checkPattern(String pattern, int userId)297     public boolean checkPattern(String pattern, int userId) throws RemoteException {
298         checkPasswordReadPermission(userId);
299         try {
300             // Read all the bytes from the file
301             RandomAccessFile raf = new RandomAccessFile(getLockPatternFilename(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 pattern's hash
309             final byte[] hash = LockPatternUtils.patternToHash(
310                     LockPatternUtils.stringToPattern(pattern));
311             final boolean matched = Arrays.equals(stored, hash);
312             if (matched && !TextUtils.isEmpty(pattern)) {
313                 maybeUpdateKeystore(pattern, userId);
314             }
315             return matched;
316         } catch (FileNotFoundException fnfe) {
317             Slog.e(TAG, "Cannot read file " + fnfe);
318         } catch (IOException ioe) {
319             Slog.e(TAG, "Cannot read file " + ioe);
320         }
321         return true;
322     }
323 
324     @Override
checkPassword(String password, int userId)325     public boolean checkPassword(String password, int userId) throws RemoteException {
326         checkPasswordReadPermission(userId);
327 
328         try {
329             // Read all the bytes from the file
330             RandomAccessFile raf = new RandomAccessFile(getLockPasswordFilename(userId), "r");
331             final byte[] stored = new byte[(int) raf.length()];
332             int got = raf.read(stored, 0, stored.length);
333             raf.close();
334             if (got <= 0) {
335                 return true;
336             }
337             // Compare the hash from the file with the entered password's hash
338             final byte[] hash = mLockPatternUtils.passwordToHash(password);
339             final boolean matched = Arrays.equals(stored, hash);
340             if (matched && !TextUtils.isEmpty(password)) {
341                 maybeUpdateKeystore(password, userId);
342             }
343             return matched;
344         } catch (FileNotFoundException fnfe) {
345             Slog.e(TAG, "Cannot read file " + fnfe);
346         } catch (IOException ioe) {
347             Slog.e(TAG, "Cannot read file " + ioe);
348         }
349         return true;
350     }
351 
352     @Override
removeUser(int userId)353     public void removeUser(int userId) {
354         checkWritePermission(userId);
355 
356         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
357         try {
358             File file = new File(getLockPasswordFilename(userId));
359             if (file.exists()) {
360                 file.delete();
361             }
362             file = new File(getLockPatternFilename(userId));
363             if (file.exists()) {
364                 file.delete();
365             }
366 
367             db.beginTransaction();
368             db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null);
369             db.setTransactionSuccessful();
370         } finally {
371             db.endTransaction();
372         }
373     }
374 
writeFile(String name, byte[] hash)375     private void writeFile(String name, byte[] hash) {
376         try {
377             // Write the hash to file
378             RandomAccessFile raf = new RandomAccessFile(name, "rw");
379             // Truncate the file if pattern is null, to clear the lock
380             if (hash == null || hash.length == 0) {
381                 raf.setLength(0);
382             } else {
383                 raf.write(hash, 0, hash.length);
384             }
385             raf.close();
386         } catch (IOException ioe) {
387             Slog.e(TAG, "Error writing to file " + ioe);
388         }
389     }
390 
writeToDb(String key, String value, int userId)391     private void writeToDb(String key, String value, int userId) {
392         writeToDb(mOpenHelper.getWritableDatabase(), key, value, userId);
393     }
394 
writeToDb(SQLiteDatabase db, String key, String value, int userId)395     private void writeToDb(SQLiteDatabase db, String key, String value, int userId) {
396         ContentValues cv = new ContentValues();
397         cv.put(COLUMN_KEY, key);
398         cv.put(COLUMN_USERID, userId);
399         cv.put(COLUMN_VALUE, value);
400 
401         db.beginTransaction();
402         try {
403             db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?",
404                     new String[] {key, Integer.toString(userId)});
405             db.insert(TABLE, null, cv);
406             db.setTransactionSuccessful();
407         } finally {
408             db.endTransaction();
409         }
410     }
411 
readFromDb(String key, String defaultValue, int userId)412     private String readFromDb(String key, String defaultValue, int userId) {
413         Cursor cursor;
414         String result = defaultValue;
415         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
416         if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY,
417                 COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?",
418                 new String[] { Integer.toString(userId), key },
419                 null, null, null)) != null) {
420             if (cursor.moveToFirst()) {
421                 result = cursor.getString(0);
422             }
423             cursor.close();
424         }
425         return result;
426     }
427 
428     class DatabaseHelper extends SQLiteOpenHelper {
429         private static final String TAG = "LockSettingsDB";
430         private static final String DATABASE_NAME = "locksettings.db";
431 
432         private static final int DATABASE_VERSION = 2;
433 
DatabaseHelper(Context context)434         public DatabaseHelper(Context context) {
435             super(context, DATABASE_NAME, null, DATABASE_VERSION);
436             setWriteAheadLoggingEnabled(true);
437         }
438 
createTable(SQLiteDatabase db)439         private void createTable(SQLiteDatabase db) {
440             db.execSQL("CREATE TABLE " + TABLE + " (" +
441                     "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
442                     COLUMN_KEY + " TEXT," +
443                     COLUMN_USERID + " INTEGER," +
444                     COLUMN_VALUE + " TEXT" +
445                     ");");
446         }
447 
448         @Override
onCreate(SQLiteDatabase db)449         public void onCreate(SQLiteDatabase db) {
450             createTable(db);
451             initializeDefaults(db);
452         }
453 
initializeDefaults(SQLiteDatabase db)454         private void initializeDefaults(SQLiteDatabase db) {
455             // Get the lockscreen default from a system property, if available
456             boolean lockScreenDisable = SystemProperties.getBoolean("ro.lockscreen.disable.default",
457                     false);
458             if (lockScreenDisable) {
459                 writeToDb(db, LockPatternUtils.DISABLE_LOCKSCREEN_KEY, "1", 0);
460             }
461         }
462 
463         @Override
onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion)464         public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
465             int upgradeVersion = oldVersion;
466             if (upgradeVersion == 1) {
467                 // Set the initial value for {@link LockPatternUtils#LOCKSCREEN_WIDGETS_ENABLED}
468                 // during upgrade based on whether each user previously had widgets in keyguard.
469                 maybeEnableWidgetSettingForUsers(db);
470                 upgradeVersion = 2;
471             }
472 
473             if (upgradeVersion != DATABASE_VERSION) {
474                 Log.w(TAG, "Failed to upgrade database!");
475             }
476         }
477 
maybeEnableWidgetSettingForUsers(SQLiteDatabase db)478         private void maybeEnableWidgetSettingForUsers(SQLiteDatabase db) {
479             final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
480             final ContentResolver cr = mContext.getContentResolver();
481             final List<UserInfo> users = um.getUsers();
482             for (int i = 0; i < users.size(); i++) {
483                 final int userId = users.get(i).id;
484                 final boolean enabled = mLockPatternUtils.hasWidgetsEnabledInKeyguard(userId);
485                 Log.v(TAG, "Widget upgrade uid=" + userId + ", enabled="
486                         + enabled + ", w[]=" + mLockPatternUtils.getAppWidgets());
487                 loadSetting(db, LockPatternUtils.LOCKSCREEN_WIDGETS_ENABLED, userId, enabled);
488             }
489         }
490 
loadSetting(SQLiteDatabase db, String key, int userId, boolean value)491         private void loadSetting(SQLiteDatabase db, String key, int userId, boolean value) {
492             SQLiteStatement stmt = null;
493             try {
494                 stmt = db.compileStatement(
495                         "INSERT OR REPLACE INTO locksettings(name,user,value) VALUES(?,?,?);");
496                 stmt.bindString(1, key);
497                 stmt.bindLong(2, userId);
498                 stmt.bindLong(3, value ? 1 : 0);
499                 stmt.execute();
500             } finally {
501                 if (stmt != null) stmt.close();
502             }
503         }
504     }
505 
506     private static final String[] VALID_SETTINGS = new String[] {
507         LockPatternUtils.LOCKOUT_PERMANENT_KEY,
508         LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE,
509         LockPatternUtils.PATTERN_EVER_CHOSEN_KEY,
510         LockPatternUtils.PASSWORD_TYPE_KEY,
511         LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
512         LockPatternUtils.LOCK_PASSWORD_SALT_KEY,
513         LockPatternUtils.DISABLE_LOCKSCREEN_KEY,
514         LockPatternUtils.LOCKSCREEN_OPTIONS,
515         LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK,
516         LockPatternUtils.BIOMETRIC_WEAK_EVER_CHOSEN_KEY,
517         LockPatternUtils.LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS,
518         LockPatternUtils.PASSWORD_HISTORY_KEY,
519         Secure.LOCK_PATTERN_ENABLED,
520         Secure.LOCK_BIOMETRIC_WEAK_FLAGS,
521         Secure.LOCK_PATTERN_VISIBLE,
522         Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED
523     };
524 
525     // These are protected with a read permission
526     private static final String[] READ_PROFILE_PROTECTED_SETTINGS = new String[] {
527         Secure.LOCK_SCREEN_OWNER_INFO_ENABLED,
528         Secure.LOCK_SCREEN_OWNER_INFO
529     };
530 }
531