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.content.ContentResolver;
     20 import android.content.ContentValues;
     21 import android.content.Context;
     22 import android.content.pm.PackageManager;
     23 import android.content.pm.UserInfo;
     24 
     25 import static android.content.Context.USER_SERVICE;
     26 import static android.Manifest.permission.READ_PROFILE;
     27 import android.database.Cursor;
     28 import android.database.sqlite.SQLiteDatabase;
     29 import android.database.sqlite.SQLiteOpenHelper;
     30 import android.os.Binder;
     31 import android.os.Environment;
     32 import android.os.RemoteException;
     33 import android.os.SystemProperties;
     34 import android.os.UserHandle;
     35 import android.os.UserManager;
     36 import android.provider.Settings;
     37 import android.provider.Settings.Secure;
     38 import android.provider.Settings.SettingNotFoundException;
     39 import android.text.TextUtils;
     40 import android.util.Slog;
     41 
     42 import com.android.internal.widget.ILockSettings;
     43 import com.android.internal.widget.LockPatternUtils;
     44 
     45 import java.io.File;
     46 import java.io.FileNotFoundException;
     47 import java.io.IOException;
     48 import java.io.RandomAccessFile;
     49 import java.util.Arrays;
     50 import java.util.List;
     51 
     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  */
     58 public 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 {
     90             // 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");
    103             }
    104 
    105             // 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++) {
    111                     // 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                     }
    119 
    120                     // 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);
    133                         }
    134                     }
    135                     Settings.Secure.putIntForUser(cr, OWNER_INFO_ENABLED, 0, userId);
    136                 }
    137                 // 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");
    140             }
    141         } catch (RemoteException re) {
    142             Slog.e(TAG, "Unable to migrate old data", re);
    143         }
    144     }
    145 
    146     private static final void checkWritePermission(int userId) {
    147         final int callingUid = Binder.getCallingUid();
    148         if (UserHandle.getAppId(callingUid) != android.os.Process.SYSTEM_UID) {
    149             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();
    156         if (UserHandle.getAppId(callingUid) != android.os.Process.SYSTEM_UID) {
    157             throw new SecurityException("uid=" + callingUid
    158                     + " not authorized to read lock password");
    159         }
    160     }
    161 
    162     private final void checkReadPermission(String requestedKey, int userId) {
    163         final int callingUid = Binder.getCallingUid();
    164         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             }
    172         }
    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 {
    198         checkReadPermission(key, userId);
    199 
    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 {
    207         checkReadPermission(key, userId);
    208 
    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 {
    215         checkReadPermission(key, userId);
    216 
    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 {
    228             return  new File(Environment.getUserSystemDirectory(userId), LOCK_PATTERN_FILE)
    229                     .getAbsolutePath();
    230         }
    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 {
    241             return  new File(Environment.getUserSystemDirectory(userId), LOCK_PASSWORD_FILE)
    242                     .getAbsolutePath();
    243         }
    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) {
    361         writeToDb(mOpenHelper.getWritableDatabase(), key, value, userId);
    362     }
    363 
    364     private void writeToDb(SQLiteDatabase db, String key, String value, int userId) {
    365         ContentValues cv = new ContentValues();
    366         cv.put(COLUMN_KEY, key);
    367         cv.put(COLUMN_USERID, userId);
    368         cv.put(COLUMN_VALUE, value);
    369 
    370         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);
    420             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             }
    430         }
    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
    455     };
    456 
    457     // These are protected with a read permission
    458     private static final String[] READ_PROFILE_PROTECTED_SETTINGS = new String[] {
    459         Secure.LOCK_SCREEN_OWNER_INFO_ENABLED,
    460         Secure.LOCK_SCREEN_OWNER_INFO
    461     };
    462 }
    463