Home | History | Annotate | Download | only in widget
      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.internal.widget;
     18 
     19 import android.content.ContentResolver;
     20 import android.content.ContentValues;
     21 import android.content.Context;
     22 import android.database.Cursor;
     23 import android.database.sqlite.SQLiteDatabase;
     24 import android.database.sqlite.SQLiteOpenHelper;
     25 import android.os.Binder;
     26 import android.os.RemoteException;
     27 import android.os.UserId;
     28 import android.provider.Settings;
     29 import android.provider.Settings.Secure;
     30 import android.text.TextUtils;
     31 import android.util.Slog;
     32 
     33 import java.io.File;
     34 import java.io.FileNotFoundException;
     35 import java.io.IOException;
     36 import java.io.RandomAccessFile;
     37 import java.util.Arrays;
     38 
     39 /**
     40  * Keeps the lock pattern/password data and related settings for each user.
     41  * Used by LockPatternUtils. Needs to be a service because Settings app also needs
     42  * to be able to save lockscreen information for secondary users.
     43  * @hide
     44  */
     45 public class LockSettingsService extends ILockSettings.Stub {
     46 
     47     private final DatabaseHelper mOpenHelper;
     48     private static final String TAG = "LockSettingsService";
     49 
     50     private static final String TABLE = "locksettings";
     51     private static final String COLUMN_KEY = "name";
     52     private static final String COLUMN_USERID = "user";
     53     private static final String COLUMN_VALUE = "value";
     54 
     55     private static final String[] COLUMNS_FOR_QUERY = {
     56         COLUMN_VALUE
     57     };
     58 
     59     private static final String SYSTEM_DIRECTORY = "/system/";
     60     private static final String LOCK_PATTERN_FILE = "gesture.key";
     61     private static final String LOCK_PASSWORD_FILE = "password.key";
     62 
     63     private final Context mContext;
     64 
     65     public LockSettingsService(Context context) {
     66         mContext = context;
     67         // Open the database
     68         mOpenHelper = new DatabaseHelper(mContext);
     69     }
     70 
     71     public void systemReady() {
     72         migrateOldData();
     73     }
     74 
     75     private void migrateOldData() {
     76         try {
     77             if (getString("migrated", null, 0) != null) {
     78                 // Already migrated
     79                 return;
     80             }
     81 
     82             final ContentResolver cr = mContext.getContentResolver();
     83             for (String validSetting : VALID_SETTINGS) {
     84                 String value = Settings.Secure.getString(cr, validSetting);
     85                 if (value != null) {
     86                     setString(validSetting, value, 0);
     87                 }
     88             }
     89             // No need to move the password / pattern files. They're already in the right place.
     90             setString("migrated", "true", 0);
     91             Slog.i(TAG, "Migrated lock settings to new location");
     92         } catch (RemoteException re) {
     93             Slog.e(TAG, "Unable to migrate old data");
     94         }
     95     }
     96 
     97     private static final void checkWritePermission(int userId) {
     98         final int callingUid = Binder.getCallingUid();
     99         if (UserId.getAppId(callingUid) != android.os.Process.SYSTEM_UID) {
    100             throw new SecurityException("uid=" + callingUid
    101                     + " not authorized to write lock settings");
    102         }
    103     }
    104 
    105     private static final void checkPasswordReadPermission(int userId) {
    106         final int callingUid = Binder.getCallingUid();
    107         if (UserId.getAppId(callingUid) != android.os.Process.SYSTEM_UID) {
    108             throw new SecurityException("uid=" + callingUid
    109                     + " not authorized to read lock password");
    110         }
    111     }
    112 
    113     private static final void checkReadPermission(int userId) {
    114         final int callingUid = Binder.getCallingUid();
    115         if (UserId.getAppId(callingUid) != android.os.Process.SYSTEM_UID
    116                 && UserId.getUserId(callingUid) != userId) {
    117             throw new SecurityException("uid=" + callingUid
    118                     + " not authorized to read settings of user " + userId);
    119         }
    120     }
    121 
    122     @Override
    123     public void setBoolean(String key, boolean value, int userId) throws RemoteException {
    124         checkWritePermission(userId);
    125 
    126         writeToDb(key, value ? "1" : "0", userId);
    127     }
    128 
    129     @Override
    130     public void setLong(String key, long value, int userId) throws RemoteException {
    131         checkWritePermission(userId);
    132 
    133         writeToDb(key, Long.toString(value), userId);
    134     }
    135 
    136     @Override
    137     public void setString(String key, String value, int userId) throws RemoteException {
    138         checkWritePermission(userId);
    139 
    140         writeToDb(key, value, userId);
    141     }
    142 
    143     @Override
    144     public boolean getBoolean(String key, boolean defaultValue, int userId) throws RemoteException {
    145         //checkReadPermission(userId);
    146 
    147         String value = readFromDb(key, null, userId);
    148         return TextUtils.isEmpty(value) ?
    149                 defaultValue : (value.equals("1") || value.equals("true"));
    150     }
    151 
    152     @Override
    153     public long getLong(String key, long defaultValue, int userId) throws RemoteException {
    154         //checkReadPermission(userId);
    155 
    156         String value = readFromDb(key, null, userId);
    157         return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value);
    158     }
    159 
    160     @Override
    161     public String getString(String key, String defaultValue, int userId) throws RemoteException {
    162         //checkReadPermission(userId);
    163 
    164         return readFromDb(key, defaultValue, userId);
    165     }
    166 
    167     private String getLockPatternFilename(int userId) {
    168         String dataSystemDirectory =
    169                 android.os.Environment.getDataDirectory().getAbsolutePath() +
    170                 SYSTEM_DIRECTORY;
    171         if (userId == 0) {
    172             // Leave it in the same place for user 0
    173             return dataSystemDirectory + LOCK_PATTERN_FILE;
    174         } else {
    175             return  dataSystemDirectory + "users/" + userId + "/" + LOCK_PATTERN_FILE;
    176         }
    177     }
    178 
    179     private String getLockPasswordFilename(int userId) {
    180         String dataSystemDirectory =
    181                 android.os.Environment.getDataDirectory().getAbsolutePath() +
    182                 SYSTEM_DIRECTORY;
    183         if (userId == 0) {
    184             // Leave it in the same place for user 0
    185             return dataSystemDirectory + LOCK_PASSWORD_FILE;
    186         } else {
    187             return  dataSystemDirectory + "users/" + userId + "/" + LOCK_PASSWORD_FILE;
    188         }
    189     }
    190 
    191     @Override
    192     public boolean havePassword(int userId) throws RemoteException {
    193         // Do we need a permissions check here?
    194 
    195         return new File(getLockPasswordFilename(userId)).length() > 0;
    196     }
    197 
    198     @Override
    199     public boolean havePattern(int userId) throws RemoteException {
    200         // Do we need a permissions check here?
    201 
    202         return new File(getLockPatternFilename(userId)).length() > 0;
    203     }
    204 
    205     @Override
    206     public void setLockPattern(byte[] hash, int userId) throws RemoteException {
    207         checkWritePermission(userId);
    208 
    209         writeFile(getLockPatternFilename(userId), hash);
    210     }
    211 
    212     @Override
    213     public boolean checkPattern(byte[] hash, int userId) throws RemoteException {
    214         checkPasswordReadPermission(userId);
    215         try {
    216             // Read all the bytes from the file
    217             RandomAccessFile raf = new RandomAccessFile(getLockPatternFilename(userId), "r");
    218             final byte[] stored = new byte[(int) raf.length()];
    219             int got = raf.read(stored, 0, stored.length);
    220             raf.close();
    221             if (got <= 0) {
    222                 return true;
    223             }
    224             // Compare the hash from the file with the entered pattern's hash
    225             return Arrays.equals(stored, hash);
    226         } catch (FileNotFoundException fnfe) {
    227             Slog.e(TAG, "Cannot read file " + fnfe);
    228             return true;
    229         } catch (IOException ioe) {
    230             Slog.e(TAG, "Cannot read file " + ioe);
    231             return true;
    232         }
    233     }
    234 
    235     @Override
    236     public void setLockPassword(byte[] hash, int userId) throws RemoteException {
    237         checkWritePermission(userId);
    238 
    239         writeFile(getLockPasswordFilename(userId), hash);
    240     }
    241 
    242     @Override
    243     public boolean checkPassword(byte[] hash, int userId) throws RemoteException {
    244         checkPasswordReadPermission(userId);
    245 
    246         try {
    247             // Read all the bytes from the file
    248             RandomAccessFile raf = new RandomAccessFile(getLockPasswordFilename(userId), "r");
    249             final byte[] stored = new byte[(int) raf.length()];
    250             int got = raf.read(stored, 0, stored.length);
    251             raf.close();
    252             if (got <= 0) {
    253                 return true;
    254             }
    255             // Compare the hash from the file with the entered password's hash
    256             return Arrays.equals(stored, hash);
    257         } catch (FileNotFoundException fnfe) {
    258             Slog.e(TAG, "Cannot read file " + fnfe);
    259             return true;
    260         } catch (IOException ioe) {
    261             Slog.e(TAG, "Cannot read file " + ioe);
    262             return true;
    263         }
    264     }
    265 
    266     @Override
    267     public void removeUser(int userId) {
    268         checkWritePermission(userId);
    269 
    270         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    271         try {
    272             File file = new File(getLockPasswordFilename(userId));
    273             if (file.exists()) {
    274                 file.delete();
    275             }
    276             file = new File(getLockPatternFilename(userId));
    277             if (file.exists()) {
    278                 file.delete();
    279             }
    280 
    281             db.beginTransaction();
    282             db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null);
    283             db.setTransactionSuccessful();
    284         } finally {
    285             db.endTransaction();
    286         }
    287     }
    288 
    289     private void writeFile(String name, byte[] hash) {
    290         try {
    291             // Write the hash to file
    292             RandomAccessFile raf = new RandomAccessFile(name, "rw");
    293             // Truncate the file if pattern is null, to clear the lock
    294             if (hash == null || hash.length == 0) {
    295                 raf.setLength(0);
    296             } else {
    297                 raf.write(hash, 0, hash.length);
    298             }
    299             raf.close();
    300         } catch (IOException ioe) {
    301             Slog.e(TAG, "Error writing to file " + ioe);
    302         }
    303     }
    304 
    305     private void writeToDb(String key, String value, int userId) {
    306         ContentValues cv = new ContentValues();
    307         cv.put(COLUMN_KEY, key);
    308         cv.put(COLUMN_USERID, userId);
    309         cv.put(COLUMN_VALUE, value);
    310 
    311         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    312         db.beginTransaction();
    313         try {
    314             db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?",
    315                     new String[] {key, Integer.toString(userId)});
    316             db.insert(TABLE, null, cv);
    317             db.setTransactionSuccessful();
    318         } finally {
    319             db.endTransaction();
    320         }
    321     }
    322 
    323     private String readFromDb(String key, String defaultValue, int userId) {
    324         Cursor cursor;
    325         String result = defaultValue;
    326         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
    327         if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY,
    328                 COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?",
    329                 new String[] { Integer.toString(userId), key },
    330                 null, null, null)) != null) {
    331             if (cursor.moveToFirst()) {
    332                 result = cursor.getString(0);
    333             }
    334             cursor.close();
    335         }
    336         return result;
    337     }
    338 
    339     class DatabaseHelper extends SQLiteOpenHelper {
    340         private static final String TAG = "LockSettingsDB";
    341         private static final String DATABASE_NAME = "locksettings.db";
    342 
    343         private static final int DATABASE_VERSION = 1;
    344 
    345         public DatabaseHelper(Context context) {
    346             super(context, DATABASE_NAME, null, DATABASE_VERSION);
    347             setWriteAheadLoggingEnabled(true);
    348         }
    349 
    350         private void createTable(SQLiteDatabase db) {
    351             db.execSQL("CREATE TABLE " + TABLE + " (" +
    352                     "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
    353                     COLUMN_KEY + " TEXT," +
    354                     COLUMN_USERID + " INTEGER," +
    355                     COLUMN_VALUE + " TEXT" +
    356                     ");");
    357         }
    358 
    359         @Override
    360         public void onCreate(SQLiteDatabase db) {
    361             createTable(db);
    362         }
    363 
    364         @Override
    365         public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
    366             // Nothing yet
    367         }
    368     }
    369 
    370     private static final String[] VALID_SETTINGS = new String[] {
    371         LockPatternUtils.LOCKOUT_PERMANENT_KEY,
    372         LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE,
    373         LockPatternUtils.PATTERN_EVER_CHOSEN_KEY,
    374         LockPatternUtils.PASSWORD_TYPE_KEY,
    375         LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
    376         LockPatternUtils.LOCK_PASSWORD_SALT_KEY,
    377         LockPatternUtils.DISABLE_LOCKSCREEN_KEY,
    378         LockPatternUtils.LOCKSCREEN_OPTIONS,
    379         LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK,
    380         LockPatternUtils.BIOMETRIC_WEAK_EVER_CHOSEN_KEY,
    381         LockPatternUtils.LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS,
    382         LockPatternUtils.PASSWORD_HISTORY_KEY,
    383         Secure.LOCK_PATTERN_ENABLED,
    384         Secure.LOCK_BIOMETRIC_WEAK_FLAGS,
    385         Secure.LOCK_PATTERN_VISIBLE,
    386         Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED
    387         };
    388 }
    389