blob: 2fb81ac7b069b1371dbde8ac1686aaef35b11f08 [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;
26import android.os.RemoteException;
Amith Yamasanid1645f82012-06-12 11:53:26 -070027import android.os.SystemProperties;
Amith Yamasani52c489c2012-03-28 11:42:42 -070028import 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) {
Amith Yamasanid1645f82012-06-12 11:53:26 -0700307 writeToDb(mOpenHelper.getWritableDatabase(), key, value, userId);
308 }
309
310 private void writeToDb(SQLiteDatabase db, String key, String value, int userId) {
Amith Yamasani52c489c2012-03-28 11:42:42 -0700311 ContentValues cv = new ContentValues();
312 cv.put(COLUMN_KEY, key);
313 cv.put(COLUMN_USERID, userId);
314 cv.put(COLUMN_VALUE, value);
315
Amith Yamasani52c489c2012-03-28 11:42:42 -0700316 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);
Amith Yamasanid1645f82012-06-12 11:53:26 -0700366 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 }
Amith Yamasani52c489c2012-03-28 11:42:42 -0700376 }
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}