LockSettingsStorage.java revision 261d5ab8f4c3fdd34163468fd48ab07f7ad13d3c
1/*
2 * Copyright (C) 2014 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.server;
18
19import android.content.ContentValues;
20import android.content.Context;
21import android.content.pm.UserInfo;
22import android.database.Cursor;
23import android.database.sqlite.SQLiteDatabase;
24import android.database.sqlite.SQLiteOpenHelper;
25import android.os.Environment;
26import android.os.UserManager;
27import android.util.ArrayMap;
28import android.util.Log;
29import android.util.Slog;
30
31import java.io.File;
32import java.io.IOException;
33import java.io.RandomAccessFile;
34
35import static android.content.Context.USER_SERVICE;
36
37/**
38 * Storage for the lock settings service.
39 */
40class LockSettingsStorage {
41
42    private static final String TAG = "LockSettingsStorage";
43    private static final String TABLE = "locksettings";
44
45    private static final String COLUMN_KEY = "name";
46    private static final String COLUMN_USERID = "user";
47    private static final String COLUMN_VALUE = "value";
48
49    private static final String[] COLUMNS_FOR_QUERY = {
50            COLUMN_VALUE
51    };
52
53    private static final String SYSTEM_DIRECTORY = "/system/";
54    private static final String LOCK_PATTERN_FILE = "gesture.key";
55    private static final String LOCK_PASSWORD_FILE = "password.key";
56
57    private final DatabaseHelper mOpenHelper;
58    private final Context mContext;
59    private final Object mFileWriteLock = new Object();
60
61    LockSettingsStorage(Context context, Callback callback) {
62        mContext = context;
63        mOpenHelper = new DatabaseHelper(context, callback);
64    }
65
66    void writeKeyValue(String key, String value, int userId) {
67        writeKeyValue(mOpenHelper.getWritableDatabase(), key, value, userId);
68    }
69
70    void writeKeyValue(SQLiteDatabase db, String key, String value, int userId) {
71        ContentValues cv = new ContentValues();
72        cv.put(COLUMN_KEY, key);
73        cv.put(COLUMN_USERID, userId);
74        cv.put(COLUMN_VALUE, value);
75
76        db.beginTransaction();
77        try {
78            db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?",
79                    new String[] {key, Integer.toString(userId)});
80            db.insert(TABLE, null, cv);
81            db.setTransactionSuccessful();
82        } finally {
83            db.endTransaction();
84        }
85
86    }
87
88    String readKeyValue(String key, String defaultValue, int userId) {
89        Cursor cursor;
90        String result = defaultValue;
91        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
92        if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY,
93                COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?",
94                new String[] { Integer.toString(userId), key },
95                null, null, null)) != null) {
96            if (cursor.moveToFirst()) {
97                result = cursor.getString(0);
98            }
99            cursor.close();
100        }
101        return result;
102    }
103
104    byte[] readPasswordHash(int userId) {
105        final byte[] stored = readFile(getLockPasswordFilename(userId), userId);
106        if (stored != null && stored.length > 0) {
107            return stored;
108        }
109        return null;
110    }
111
112    byte[] readPatternHash(int userId) {
113        final byte[] stored = readFile(getLockPatternFilename(userId), userId);
114        if (stored != null && stored.length > 0) {
115            return stored;
116        }
117        return null;
118    }
119
120    boolean hasPassword(int userId) {
121        return hasFile(getLockPasswordFilename(userId), userId);
122    }
123
124    boolean hasPattern(int userId) {
125        return hasFile(getLockPatternFilename(userId), userId);
126    }
127
128    private boolean hasFile(String name, int userId) {
129        byte[] contents = readFile(name, userId);
130        return contents != null && contents.length > 0;
131    }
132
133    private byte[] readFile(String name, int userId) {
134        RandomAccessFile raf = null;
135        byte[] stored = null;
136        try {
137            raf = new RandomAccessFile(name, "r");
138            stored = new byte[(int) raf.length()];
139            raf.readFully(stored, 0, stored.length);
140            raf.close();
141        } catch (IOException e) {
142            Slog.e(TAG, "Cannot read file " + e);
143        } finally {
144            if (raf != null) {
145                try {
146                    raf.close();
147                } catch (IOException e) {
148                    Slog.e(TAG, "Error closing file " + e);
149                }
150            }
151        }
152        return stored;
153    }
154
155    private void writeFile(String name, byte[] hash, int userId) {
156        synchronized (mFileWriteLock) {
157            RandomAccessFile raf = null;
158            try {
159                // Write the hash to file
160                raf = new RandomAccessFile(name, "rw");
161                // Truncate the file if pattern is null, to clear the lock
162                if (hash == null || hash.length == 0) {
163                    raf.setLength(0);
164                } else {
165                    raf.write(hash, 0, hash.length);
166                }
167                raf.close();
168            } catch (IOException e) {
169                Slog.e(TAG, "Error writing to file " + e);
170            } finally {
171                if (raf != null) {
172                    try {
173                        raf.close();
174                    } catch (IOException e) {
175                        Slog.e(TAG, "Error closing file " + e);
176                    }
177                }
178            }
179        }
180    }
181
182    public void writePatternHash(byte[] hash, int userId) {
183        writeFile(getLockPatternFilename(userId), hash, userId);
184    }
185
186    public void writePasswordHash(byte[] hash, int userId) {
187        writeFile(getLockPasswordFilename(userId), hash, userId);
188    }
189
190
191    private String getLockPatternFilename(int userId) {
192        String dataSystemDirectory =
193                android.os.Environment.getDataDirectory().getAbsolutePath() +
194                        SYSTEM_DIRECTORY;
195        userId = getUserParentOrSelfId(userId);
196        if (userId == 0) {
197            // Leave it in the same place for user 0
198            return dataSystemDirectory + LOCK_PATTERN_FILE;
199        } else {
200            return new File(Environment.getUserSystemDirectory(userId), LOCK_PATTERN_FILE)
201                    .getAbsolutePath();
202        }
203    }
204
205    private String getLockPasswordFilename(int userId) {
206        userId = getUserParentOrSelfId(userId);
207        String dataSystemDirectory =
208                android.os.Environment.getDataDirectory().getAbsolutePath() +
209                        SYSTEM_DIRECTORY;
210        if (userId == 0) {
211            // Leave it in the same place for user 0
212            return dataSystemDirectory + LOCK_PASSWORD_FILE;
213        } else {
214            return new File(Environment.getUserSystemDirectory(userId), LOCK_PASSWORD_FILE)
215                    .getAbsolutePath();
216        }
217    }
218
219    private int getUserParentOrSelfId(int userId) {
220        if (userId != 0) {
221            final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
222            final UserInfo pi = um.getProfileParent(userId);
223            if (pi != null) {
224                return pi.id;
225            }
226        }
227        return userId;
228    }
229
230
231    public void removeUser(int userId) {
232        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
233
234        final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
235        final UserInfo parentInfo = um.getProfileParent(userId);
236
237        synchronized (mFileWriteLock) {
238            if (parentInfo == null) {
239                // This user owns its lock settings files - safe to delete them
240                File file = new File(getLockPasswordFilename(userId));
241                if (file.exists()) {
242                    file.delete();
243                }
244                file = new File(getLockPatternFilename(userId));
245                if (file.exists()) {
246                    file.delete();
247                }
248            }
249        }
250
251        try {
252            db.beginTransaction();
253            db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null);
254            db.setTransactionSuccessful();
255        } finally {
256            db.endTransaction();
257        }
258    }
259
260
261    interface Callback {
262        void initialize(SQLiteDatabase db);
263    }
264
265    class DatabaseHelper extends SQLiteOpenHelper {
266        private static final String TAG = "LockSettingsDB";
267        private static final String DATABASE_NAME = "locksettings.db";
268
269        private static final int DATABASE_VERSION = 2;
270
271        private final Callback mCallback;
272
273        public DatabaseHelper(Context context, Callback callback) {
274            super(context, DATABASE_NAME, null, DATABASE_VERSION);
275            setWriteAheadLoggingEnabled(true);
276            mCallback = callback;
277        }
278
279        private void createTable(SQLiteDatabase db) {
280            db.execSQL("CREATE TABLE " + TABLE + " (" +
281                    "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
282                    COLUMN_KEY + " TEXT," +
283                    COLUMN_USERID + " INTEGER," +
284                    COLUMN_VALUE + " TEXT" +
285                    ");");
286        }
287
288        @Override
289        public void onCreate(SQLiteDatabase db) {
290            createTable(db);
291            mCallback.initialize(db);
292        }
293
294        @Override
295        public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
296            int upgradeVersion = oldVersion;
297            if (upgradeVersion == 1) {
298                // Previously migrated lock screen widget settings. Now defunct.
299                upgradeVersion = 2;
300            }
301
302            if (upgradeVersion != DATABASE_VERSION) {
303                Log.w(TAG, "Failed to upgrade database!");
304            }
305        }
306    }
307}
308