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