Home | History | Annotate | Download | only in server
      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 
     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 
     94     public void systemReady() {
     95         migrateOldData();
     96     }
     97 
     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 
    156     private final void checkWritePermission(int userId) {
    157         mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsWrite");
    158     }
    159 
    160     private final void checkPasswordReadPermission(int userId) {
    161         mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsRead");
    162     }
    163 
    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
    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
    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
    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
    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
    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
    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 
    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 
    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
    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
    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 
    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
    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
    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
    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
    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
    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 
    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 
    391     private void writeToDb(String key, String value, int userId) {
    392         writeToDb(mOpenHelper.getWritableDatabase(), key, value, userId);
    393     }
    394 
    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 
    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 
    434         public DatabaseHelper(Context context) {
    435             super(context, DATABASE_NAME, null, DATABASE_VERSION);
    436             setWriteAheadLoggingEnabled(true);
    437         }
    438 
    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
    449         public void onCreate(SQLiteDatabase db) {
    450             createTable(db);
    451             initializeDefaults(db);
    452         }
    453 
    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
    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 
    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 
    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