LockSettingsService.java revision de1af08dd3a073f007ae4b8a114352cae3775028
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
17package com.android.server;
18
19import android.app.ActivityManagerNative;
20import android.content.ContentResolver;
21import android.content.ContentValues;
22import android.content.Context;
23import android.content.pm.PackageManager;
24import android.content.pm.UserInfo;
25
26import static android.content.Context.USER_SERVICE;
27import static android.Manifest.permission.READ_PROFILE;
28import android.database.Cursor;
29import android.database.sqlite.SQLiteDatabase;
30import android.database.sqlite.SQLiteOpenHelper;
31import android.database.sqlite.SQLiteStatement;
32import android.media.AudioManager;
33import android.media.AudioService;
34import android.os.Binder;
35import android.os.Environment;
36import android.os.RemoteException;
37import android.os.SystemProperties;
38import android.os.UserHandle;
39import android.os.UserManager;
40import android.provider.Settings;
41import android.provider.Settings.Secure;
42import android.provider.Settings.SettingNotFoundException;
43import android.security.KeyStore;
44import android.text.TextUtils;
45import android.util.Log;
46import android.util.Slog;
47
48import com.android.internal.widget.ILockSettings;
49import com.android.internal.widget.LockPatternUtils;
50
51import java.io.File;
52import java.io.FileNotFoundException;
53import java.io.IOException;
54import java.io.RandomAccessFile;
55import java.util.Arrays;
56import 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 */
64public 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.checkCallingOrSelfPermission(PERMISSION);
158    }
159
160    private final void checkPasswordReadPermission(int userId) {
161        mContext.checkCallingOrSelfPermission(PERMISSION);
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