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