531 lines
20 KiB
Java
Raw Normal View History

/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server;
import android.app.ActivityManagerNative;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import static android.content.Context.USER_SERVICE;
import static android.Manifest.permission.READ_PROFILE;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteStatement;
import android.media.AudioManager;
import android.media.AudioService;
import android.os.Binder;
import android.os.Environment;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.provider.Settings.SettingNotFoundException;
import android.security.KeyStore;
import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
import com.android.internal.widget.ILockSettings;
import com.android.internal.widget.LockPatternUtils;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Arrays;
import java.util.List;
/**
* Keeps the lock pattern/password data and related settings for each user.
* Used by LockPatternUtils. Needs to be a service because Settings app also needs
* to be able to save lockscreen information for secondary users.
* @hide
*/
public class LockSettingsService extends ILockSettings.Stub {
Move keyguard to its own process. This is in preparation to moving keyguard into its own process. Moved keyguard source and resources into new .apk. Got basic test app working. Still need to implement MockPatternUtils and means to pass it into KeyguardService with local binder interface. Added new ACCESS_KEYGUARD_SECURE_STORAGE permission. Temporarily disabled USER_PRESENT broadcast. Remove unintentional whitespace changes in PhoneWindowManager, etc. Checkpoint basic working version. Move to systemui process. Synchronize with TOT. Sync with recent user API changes. Fix bug with returing interface instead of stub for IKeyguardResult. Create KeyguardServiceDelegate to allow for runtime-selectable local or remote interface. More keyguard crash robustness. Keyguard crash recovery working. Currently fails safe (locked). Fix selector view which was still using frameworks resources. Remove more references to internal framework variables. Use aliases for those we should move but currently have dependencies. Allow runtime switching between service and local mode. Fix layout issue on tablets where orientation was reading the incorrect constant from the framework. Remove more framework dependencies. Fix PIN keyboard input. Remove unnecessary copy of orientation attrs. Remove unused user selector widget and attempt to get multi user working again. Fix multi-user avatar icon by grabbing it from UserManager rather than directly since keyguard can no longer read it. Merge with AppWidget userId changes in master. Change-Id: I254d6fc6423ae40f6d7fef50aead4caa701e5ad2
2013-01-09 18:50:26 -08:00
private static final String PERMISSION = "android.permission.ACCESS_KEYGUARD_SECURE_STORAGE";
private final DatabaseHelper mOpenHelper;
private static final String TAG = "LockSettingsService";
private static final String TABLE = "locksettings";
private static final String COLUMN_KEY = "name";
private static final String COLUMN_USERID = "user";
private static final String COLUMN_VALUE = "value";
private static final String[] COLUMNS_FOR_QUERY = {
COLUMN_VALUE
};
private static final String SYSTEM_DIRECTORY = "/system/";
private static final String LOCK_PATTERN_FILE = "gesture.key";
private static final String LOCK_PASSWORD_FILE = "password.key";
private final Context mContext;
private LockPatternUtils mLockPatternUtils;
public LockSettingsService(Context context) {
mContext = context;
// Open the database
mOpenHelper = new DatabaseHelper(mContext);
mLockPatternUtils = new LockPatternUtils(context);
}
public void systemReady() {
migrateOldData();
}
private void migrateOldData() {
try {
// These Settings moved before multi-user was enabled, so we only have to do it for the
// root user.
if (getString("migrated", null, 0) == null) {
final ContentResolver cr = mContext.getContentResolver();
for (String validSetting : VALID_SETTINGS) {
String value = Settings.Secure.getString(cr, validSetting);
if (value != null) {
setString(validSetting, value, 0);
}
}
// No need to move the password / pattern files. They're already in the right place.
setString("migrated", "true", 0);
Slog.i(TAG, "Migrated lock settings to new location");
}
// These Settings changed after multi-user was enabled, hence need to be moved per user.
if (getString("migrated_user_specific", null, 0) == null) {
final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
final ContentResolver cr = mContext.getContentResolver();
List<UserInfo> users = um.getUsers();
for (int user = 0; user < users.size(); user++) {
// Migrate owner info
final int userId = users.get(user).id;
final String OWNER_INFO = Secure.LOCK_SCREEN_OWNER_INFO;
String ownerInfo = Settings.Secure.getStringForUser(cr, OWNER_INFO, userId);
if (ownerInfo != null) {
setString(OWNER_INFO, ownerInfo, userId);
Settings.Secure.putStringForUser(cr, ownerInfo, "", userId);
}
// Migrate owner info enabled. Note there was a bug where older platforms only
// stored this value if the checkbox was toggled at least once. The code detects
// this case by handling the exception.
final String OWNER_INFO_ENABLED = Secure.LOCK_SCREEN_OWNER_INFO_ENABLED;
boolean enabled;
try {
int ivalue = Settings.Secure.getIntForUser(cr, OWNER_INFO_ENABLED, userId);
enabled = ivalue != 0;
setLong(OWNER_INFO_ENABLED, enabled ? 1 : 0, userId);
} catch (SettingNotFoundException e) {
// Setting was never stored. Store it if the string is not empty.
if (!TextUtils.isEmpty(ownerInfo)) {
setLong(OWNER_INFO_ENABLED, 1, userId);
}
}
Settings.Secure.putIntForUser(cr, OWNER_INFO_ENABLED, 0, userId);
}
// No need to move the password / pattern files. They're already in the right place.
setString("migrated_user_specific", "true", 0);
Slog.i(TAG, "Migrated per-user lock settings to new location");
}
} catch (RemoteException re) {
Slog.e(TAG, "Unable to migrate old data", re);
}
}
Move keyguard to its own process. This is in preparation to moving keyguard into its own process. Moved keyguard source and resources into new .apk. Got basic test app working. Still need to implement MockPatternUtils and means to pass it into KeyguardService with local binder interface. Added new ACCESS_KEYGUARD_SECURE_STORAGE permission. Temporarily disabled USER_PRESENT broadcast. Remove unintentional whitespace changes in PhoneWindowManager, etc. Checkpoint basic working version. Move to systemui process. Synchronize with TOT. Sync with recent user API changes. Fix bug with returing interface instead of stub for IKeyguardResult. Create KeyguardServiceDelegate to allow for runtime-selectable local or remote interface. More keyguard crash robustness. Keyguard crash recovery working. Currently fails safe (locked). Fix selector view which was still using frameworks resources. Remove more references to internal framework variables. Use aliases for those we should move but currently have dependencies. Allow runtime switching between service and local mode. Fix layout issue on tablets where orientation was reading the incorrect constant from the framework. Remove more framework dependencies. Fix PIN keyboard input. Remove unnecessary copy of orientation attrs. Remove unused user selector widget and attempt to get multi user working again. Fix multi-user avatar icon by grabbing it from UserManager rather than directly since keyguard can no longer read it. Merge with AppWidget userId changes in master. Change-Id: I254d6fc6423ae40f6d7fef50aead4caa701e5ad2
2013-01-09 18:50:26 -08:00
private final void checkWritePermission(int userId) {
mContext.checkCallingOrSelfPermission(PERMISSION);
}
Move keyguard to its own process. This is in preparation to moving keyguard into its own process. Moved keyguard source and resources into new .apk. Got basic test app working. Still need to implement MockPatternUtils and means to pass it into KeyguardService with local binder interface. Added new ACCESS_KEYGUARD_SECURE_STORAGE permission. Temporarily disabled USER_PRESENT broadcast. Remove unintentional whitespace changes in PhoneWindowManager, etc. Checkpoint basic working version. Move to systemui process. Synchronize with TOT. Sync with recent user API changes. Fix bug with returing interface instead of stub for IKeyguardResult. Create KeyguardServiceDelegate to allow for runtime-selectable local or remote interface. More keyguard crash robustness. Keyguard crash recovery working. Currently fails safe (locked). Fix selector view which was still using frameworks resources. Remove more references to internal framework variables. Use aliases for those we should move but currently have dependencies. Allow runtime switching between service and local mode. Fix layout issue on tablets where orientation was reading the incorrect constant from the framework. Remove more framework dependencies. Fix PIN keyboard input. Remove unnecessary copy of orientation attrs. Remove unused user selector widget and attempt to get multi user working again. Fix multi-user avatar icon by grabbing it from UserManager rather than directly since keyguard can no longer read it. Merge with AppWidget userId changes in master. Change-Id: I254d6fc6423ae40f6d7fef50aead4caa701e5ad2
2013-01-09 18:50:26 -08:00
private final void checkPasswordReadPermission(int userId) {
mContext.checkCallingOrSelfPermission(PERMISSION);
}
private final void checkReadPermission(String requestedKey, int userId) {
final int callingUid = Binder.getCallingUid();
for (int i = 0; i < READ_PROFILE_PROTECTED_SETTINGS.length; i++) {
String key = READ_PROFILE_PROTECTED_SETTINGS[i];
if (key.equals(requestedKey) && mContext.checkCallingOrSelfPermission(READ_PROFILE)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("uid=" + callingUid
+ " needs permission " + READ_PROFILE + " to read "
+ requestedKey + " for user " + userId);
}
}
}
@Override
public void setBoolean(String key, boolean value, int userId) throws RemoteException {
checkWritePermission(userId);
writeToDb(key, value ? "1" : "0", userId);
}
@Override
public void setLong(String key, long value, int userId) throws RemoteException {
checkWritePermission(userId);
writeToDb(key, Long.toString(value), userId);
}
@Override
public void setString(String key, String value, int userId) throws RemoteException {
checkWritePermission(userId);
writeToDb(key, value, userId);
}
@Override
public boolean getBoolean(String key, boolean defaultValue, int userId) throws RemoteException {
checkReadPermission(key, userId);
String value = readFromDb(key, null, userId);
return TextUtils.isEmpty(value) ?
defaultValue : (value.equals("1") || value.equals("true"));
}
@Override
public long getLong(String key, long defaultValue, int userId) throws RemoteException {
checkReadPermission(key, userId);
String value = readFromDb(key, null, userId);
return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value);
}
@Override
public String getString(String key, String defaultValue, int userId) throws RemoteException {
checkReadPermission(key, userId);
return readFromDb(key, defaultValue, userId);
}
private String getLockPatternFilename(int userId) {
String dataSystemDirectory =
android.os.Environment.getDataDirectory().getAbsolutePath() +
SYSTEM_DIRECTORY;
if (userId == 0) {
// Leave it in the same place for user 0
return dataSystemDirectory + LOCK_PATTERN_FILE;
} else {
return new File(Environment.getUserSystemDirectory(userId), LOCK_PATTERN_FILE)
.getAbsolutePath();
}
}
private String getLockPasswordFilename(int userId) {
String dataSystemDirectory =
android.os.Environment.getDataDirectory().getAbsolutePath() +
SYSTEM_DIRECTORY;
if (userId == 0) {
// Leave it in the same place for user 0
return dataSystemDirectory + LOCK_PASSWORD_FILE;
} else {
return new File(Environment.getUserSystemDirectory(userId), LOCK_PASSWORD_FILE)
.getAbsolutePath();
}
}
@Override
public boolean havePassword(int userId) throws RemoteException {
// Do we need a permissions check here?
return new File(getLockPasswordFilename(userId)).length() > 0;
}
@Override
public boolean havePattern(int userId) throws RemoteException {
// Do we need a permissions check here?
return new File(getLockPatternFilename(userId)).length() > 0;
}
private void maybeUpdateKeystore(String password, int userId) {
if (userId == UserHandle.USER_OWNER) {
final KeyStore keyStore = KeyStore.getInstance();
// Conditionally reset the keystore if empty. If non-empty, we are just
// switching key guard type
if (TextUtils.isEmpty(password) && keyStore.isEmpty()) {
keyStore.reset();
} else {
// Update the keystore password
keyStore.password(password);
}
}
}
@Override
public void setLockPattern(String pattern, int userId) throws RemoteException {
checkWritePermission(userId);
maybeUpdateKeystore(pattern, userId);
final byte[] hash = LockPatternUtils.patternToHash(
LockPatternUtils.stringToPattern(pattern));
writeFile(getLockPatternFilename(userId), hash);
}
@Override
public void setLockPassword(String password, int userId) throws RemoteException {
checkWritePermission(userId);
maybeUpdateKeystore(password, userId);
writeFile(getLockPasswordFilename(userId), mLockPatternUtils.passwordToHash(password));
}
@Override
public boolean checkPattern(String pattern, int userId) throws RemoteException {
checkPasswordReadPermission(userId);
try {
// Read all the bytes from the file
RandomAccessFile raf = new RandomAccessFile(getLockPatternFilename(userId), "r");
final byte[] stored = new byte[(int) raf.length()];
int got = raf.read(stored, 0, stored.length);
raf.close();
if (got <= 0) {
return true;
}
// Compare the hash from the file with the entered pattern's hash
final byte[] hash = LockPatternUtils.patternToHash(
LockPatternUtils.stringToPattern(pattern));
final boolean matched = Arrays.equals(stored, hash);
if (matched && !TextUtils.isEmpty(pattern)) {
maybeUpdateKeystore(pattern, userId);
}
return matched;
} catch (FileNotFoundException fnfe) {
Slog.e(TAG, "Cannot read file " + fnfe);
} catch (IOException ioe) {
Slog.e(TAG, "Cannot read file " + ioe);
}
return true;
}
@Override
public boolean checkPassword(String password, int userId) throws RemoteException {
checkPasswordReadPermission(userId);
try {
// Read all the bytes from the file
RandomAccessFile raf = new RandomAccessFile(getLockPasswordFilename(userId), "r");
final byte[] stored = new byte[(int) raf.length()];
int got = raf.read(stored, 0, stored.length);
raf.close();
if (got <= 0) {
return true;
}
// Compare the hash from the file with the entered password's hash
final byte[] hash = mLockPatternUtils.passwordToHash(password);
final boolean matched = Arrays.equals(stored, hash);
if (matched && !TextUtils.isEmpty(password)) {
maybeUpdateKeystore(password, userId);
}
return matched;
} catch (FileNotFoundException fnfe) {
Slog.e(TAG, "Cannot read file " + fnfe);
} catch (IOException ioe) {
Slog.e(TAG, "Cannot read file " + ioe);
}
return true;
}
@Override
public void removeUser(int userId) {
checkWritePermission(userId);
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
try {
File file = new File(getLockPasswordFilename(userId));
if (file.exists()) {
file.delete();
}
file = new File(getLockPatternFilename(userId));
if (file.exists()) {
file.delete();
}
db.beginTransaction();
db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
private void writeFile(String name, byte[] hash) {
try {
// Write the hash to file
RandomAccessFile raf = new RandomAccessFile(name, "rw");
// Truncate the file if pattern is null, to clear the lock
if (hash == null || hash.length == 0) {
raf.setLength(0);
} else {
raf.write(hash, 0, hash.length);
}
raf.close();
} catch (IOException ioe) {
Slog.e(TAG, "Error writing to file " + ioe);
}
}
private void writeToDb(String key, String value, int userId) {
writeToDb(mOpenHelper.getWritableDatabase(), key, value, userId);
}
private void writeToDb(SQLiteDatabase db, String key, String value, int userId) {
ContentValues cv = new ContentValues();
cv.put(COLUMN_KEY, key);
cv.put(COLUMN_USERID, userId);
cv.put(COLUMN_VALUE, value);
db.beginTransaction();
try {
db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?",
new String[] {key, Integer.toString(userId)});
db.insert(TABLE, null, cv);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
private String readFromDb(String key, String defaultValue, int userId) {
Cursor cursor;
String result = defaultValue;
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY,
COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?",
new String[] { Integer.toString(userId), key },
null, null, null)) != null) {
if (cursor.moveToFirst()) {
result = cursor.getString(0);
}
cursor.close();
}
return result;
}
class DatabaseHelper extends SQLiteOpenHelper {
private static final String TAG = "LockSettingsDB";
private static final String DATABASE_NAME = "locksettings.db";
private static final int DATABASE_VERSION = 2;
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
setWriteAheadLoggingEnabled(true);
}
private void createTable(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + TABLE + " (" +
"_id INTEGER PRIMARY KEY AUTOINCREMENT," +
COLUMN_KEY + " TEXT," +
COLUMN_USERID + " INTEGER," +
COLUMN_VALUE + " TEXT" +
");");
}
@Override
public void onCreate(SQLiteDatabase db) {
createTable(db);
initializeDefaults(db);
}
private void initializeDefaults(SQLiteDatabase db) {
// Get the lockscreen default from a system property, if available
boolean lockScreenDisable = SystemProperties.getBoolean("ro.lockscreen.disable.default",
false);
if (lockScreenDisable) {
writeToDb(db, LockPatternUtils.DISABLE_LOCKSCREEN_KEY, "1", 0);
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
int upgradeVersion = oldVersion;
if (upgradeVersion == 1) {
// Set the initial value for {@link LockPatternUtils#LOCKSCREEN_WIDGETS_ENABLED}
// during upgrade based on whether each user previously had widgets in keyguard.
maybeEnableWidgetSettingForUsers(db);
upgradeVersion = 2;
}
if (upgradeVersion != DATABASE_VERSION) {
Log.w(TAG, "Failed to upgrade database!");
}
}
private void maybeEnableWidgetSettingForUsers(SQLiteDatabase db) {
final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
final ContentResolver cr = mContext.getContentResolver();
final List<UserInfo> users = um.getUsers();
for (int i = 0; i < users.size(); i++) {
final int userId = users.get(i).id;
final boolean enabled = mLockPatternUtils.hasWidgetsEnabledInKeyguard(userId);
Log.v(TAG, "Widget upgrade uid=" + userId + ", enabled="
+ enabled + ", w[]=" + mLockPatternUtils.getAppWidgets());
loadSetting(db, LockPatternUtils.LOCKSCREEN_WIDGETS_ENABLED, userId, enabled);
}
}
private void loadSetting(SQLiteDatabase db, String key, int userId, boolean value) {
SQLiteStatement stmt = null;
try {
stmt = db.compileStatement(
"INSERT OR REPLACE INTO locksettings(name,user,value) VALUES(?,?,?);");
stmt.bindString(1, key);
stmt.bindLong(2, userId);
stmt.bindLong(3, value ? 1 : 0);
stmt.execute();
} finally {
if (stmt != null) stmt.close();
}
}
}
private static final String[] VALID_SETTINGS = new String[] {
LockPatternUtils.LOCKOUT_PERMANENT_KEY,
LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE,
LockPatternUtils.PATTERN_EVER_CHOSEN_KEY,
LockPatternUtils.PASSWORD_TYPE_KEY,
LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
LockPatternUtils.LOCK_PASSWORD_SALT_KEY,
LockPatternUtils.DISABLE_LOCKSCREEN_KEY,
LockPatternUtils.LOCKSCREEN_OPTIONS,
LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK,
LockPatternUtils.BIOMETRIC_WEAK_EVER_CHOSEN_KEY,
LockPatternUtils.LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS,
LockPatternUtils.PASSWORD_HISTORY_KEY,
Secure.LOCK_PATTERN_ENABLED,
Secure.LOCK_BIOMETRIC_WEAK_FLAGS,
Secure.LOCK_PATTERN_VISIBLE,
Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED
};
// These are protected with a read permission
private static final String[] READ_PROFILE_PROTECTED_SETTINGS = new String[] {
Secure.LOCK_SCREEN_OWNER_INFO_ENABLED,
Secure.LOCK_SCREEN_OWNER_INFO
};
}