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