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