Merge "pullers now cache data to throttle frequent pull requests. all pullers have a default 1s cool down before next pull. We can adjust these later. Also add puller stats in StatsdStats"
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index 6512b98..3a6a5b2 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -707,7 +707,6 @@
* redirects them into main-thread actions. This serializes the backup
* progress callbacks nicely within the usual main-thread lifecycle pattern.
*/
- @SystemApi
private class BackupObserverWrapper extends IBackupObserver.Stub {
final Handler mHandler;
final BackupObserver mObserver;
diff --git a/core/java/android/hardware/hdmi/HdmiTimerRecordSources.java b/core/java/android/hardware/hdmi/HdmiTimerRecordSources.java
index 6fe13ca..d7c2e1b 100644
--- a/core/java/android/hardware/hdmi/HdmiTimerRecordSources.java
+++ b/core/java/android/hardware/hdmi/HdmiTimerRecordSources.java
@@ -187,7 +187,6 @@
* Base class for time-related information.
* @hide
*/
- @SystemApi
/* package */ static class TimeUnit {
/* package */ final int mHour;
/* package */ final int mMinute;
diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java
index 21943bd..9c1c9e3 100644
--- a/core/java/android/view/WindowManagerPolicyConstants.java
+++ b/core/java/android/view/WindowManagerPolicyConstants.java
@@ -18,8 +18,6 @@
import static android.view.Display.DEFAULT_DISPLAY;
-import android.annotation.SystemApi;
-
/**
* Constants for interfacing with WindowManagerService and WindowManagerPolicyInternal.
* @hide
@@ -62,7 +60,6 @@
* Set to {@code true} when intent was invoked from pressing the home key.
* @hide
*/
- @SystemApi
String EXTRA_FROM_HOME_KEY = "android.intent.extra.FROM_HOME_KEY";
// TODO: move this to a more appropriate place.
diff --git a/core/java/com/android/internal/colorextraction/types/Tonal.java b/core/java/com/android/internal/colorextraction/types/Tonal.java
index 71baaf1..9b7383f 100644
--- a/core/java/com/android/internal/colorextraction/types/Tonal.java
+++ b/core/java/com/android/internal/colorextraction/types/Tonal.java
@@ -53,10 +53,8 @@
public static final int THRESHOLD_COLOR_LIGHT = 0xffe0e0e0;
public static final int MAIN_COLOR_LIGHT = 0xffe0e0e0;
- public static final int SECONDARY_COLOR_LIGHT = 0xff9e9e9e;
public static final int THRESHOLD_COLOR_DARK = 0xff212121;
public static final int MAIN_COLOR_DARK = 0xff000000;
- public static final int SECONDARY_COLOR_DARK = 0xff000000;
private final TonalPalette mGreyPalette;
private final ArrayList<TonalPalette> mTonalPalettes;
@@ -211,10 +209,8 @@
}
// Normal colors:
- // best fit + a 2 colors offset
outColorsNormal.setMainColor(mainColor);
- int secondaryIndex = primaryIndex + (primaryIndex >= 2 ? -2 : 2);
- outColorsNormal.setSecondaryColor(getColorInt(secondaryIndex, h, s, l));
+ outColorsNormal.setSecondaryColor(mainColor);
// Dark colors:
// Stops at 4th color, only lighter if dark text is supported
@@ -225,9 +221,9 @@
} else {
primaryIndex = Math.min(fitIndex, 3);
}
- secondaryIndex = primaryIndex + (primaryIndex >= 2 ? -2 : 2);
- outColorsDark.setMainColor(getColorInt(primaryIndex, h, s, l));
- outColorsDark.setSecondaryColor(getColorInt(secondaryIndex, h, s, l));
+ mainColor = getColorInt(primaryIndex, h, s, l);
+ outColorsDark.setMainColor(mainColor);
+ outColorsDark.setSecondaryColor(mainColor);
// Extra Dark:
// Stay close to dark colors until dark text is supported
@@ -238,9 +234,9 @@
} else {
primaryIndex = 2;
}
- secondaryIndex = primaryIndex + (primaryIndex >= 2 ? -2 : 2);
- outColorsExtraDark.setMainColor(getColorInt(primaryIndex, h, s, l));
- outColorsExtraDark.setSecondaryColor(getColorInt(secondaryIndex, h, s, l));
+ mainColor = getColorInt(primaryIndex, h, s, l);
+ outColorsExtraDark.setMainColor(mainColor);
+ outColorsExtraDark.setSecondaryColor(mainColor);
outColorsNormal.setSupportsDarkText(supportsDarkText);
outColorsDark.setSupportsDarkText(supportsDarkText);
@@ -273,11 +269,10 @@
boolean light = inWallpaperColors != null
&& (inWallpaperColors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_TEXT)
!= 0;
- int innerColor = light ? MAIN_COLOR_LIGHT : MAIN_COLOR_DARK;
- int outerColor = light ? SECONDARY_COLOR_LIGHT : SECONDARY_COLOR_DARK;
+ final int color = light ? MAIN_COLOR_LIGHT : MAIN_COLOR_DARK;
- outGradientColors.setMainColor(innerColor);
- outGradientColors.setSecondaryColor(outerColor);
+ outGradientColors.setMainColor(color);
+ outGradientColors.setSecondaryColor(color);
outGradientColors.setSupportsDarkText(light);
}
diff --git a/core/java/com/android/internal/widget/RecyclerView.java b/core/java/com/android/internal/widget/RecyclerView.java
index 7abc76a..408a4e9 100644
--- a/core/java/com/android/internal/widget/RecyclerView.java
+++ b/core/java/com/android/internal/widget/RecyclerView.java
@@ -9556,7 +9556,7 @@
if (vScroll == 0 && hScroll == 0) {
return false;
}
- mRecyclerView.scrollBy(hScroll, vScroll);
+ mRecyclerView.smoothScrollBy(hScroll, vScroll);
return true;
}
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index e313e86..b5abb45 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -95,6 +95,12 @@
<bool name="config_dead_zone_flash">false</bool>
+ <!-- Whether to enable dimming navigation buttons when wallpaper is not visible, should be
+ enabled for OLED devices to reduce/prevent burn in on the navigation bar (because of the
+ black background and static button placements) and disabled for all other devices to
+ prevent wasting cpu cycles on the dimming animation -->
+ <bool name="config_navigation_bar_enable_auto_dim_no_visible_wallpaper">true</bool>
+
<!-- Whether QuickSettings is in a phone landscape -->
<bool name="quick_settings_wide">false</bool>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
index b81a3b0..bd6421c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
@@ -26,7 +26,6 @@
import android.view.IWindowManager;
import android.view.MotionEvent;
import android.view.View;
-import android.view.WindowManagerGlobal;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.Dependency;
@@ -37,6 +36,7 @@
private final NavigationBarView mView;
private final IStatusBarService mBarService;
private final LightBarTransitionsController mLightTransitionsController;
+ private final boolean mAllowAutoDimWallpaperNotVisible;
private boolean mWallpaperVisible;
private boolean mLightsOut;
@@ -49,6 +49,8 @@
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
mLightTransitionsController = new LightBarTransitionsController(view.getContext(),
this::applyDarkIntensity);
+ mAllowAutoDimWallpaperNotVisible = view.getContext().getResources()
+ .getBoolean(R.bool.config_navigation_bar_enable_auto_dim_no_visible_wallpaper);
IWindowManager windowManagerService = Dependency.get(IWindowManager.class);
Handler handler = Handler.getMain();
@@ -80,8 +82,8 @@
@Override
protected boolean isLightsOut(int mode) {
- return super.isLightsOut(mode) || (mAutoDim && !mWallpaperVisible
- && mode != MODE_WARNING);
+ return super.isLightsOut(mode) || (mAllowAutoDimWallpaperNotVisible && mAutoDim
+ && !mWallpaperVisible && mode != MODE_WARNING);
}
public LightBarTransitionsController getLightTransitionsController() {
diff --git a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
index 2d2993d..3cf374f 100644
--- a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
+++ b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
@@ -70,12 +70,28 @@
private static final String DEFAULT_HOME_KEY = "@home@";
// Sentinel: start of state file, followed by a version number
+ // Note that STATE_FILE_VERSION=2 is tied to UNDEFINED_ANCESTRAL_RECORD_VERSION=-1 *as well as*
+ // ANCESTRAL_RECORD_VERSION=1 (introduced Android P).
+ // Should the ANCESTRAL_RECORD_VERSION be bumped up in the future, STATE_FILE_VERSION will also
+ // need bumping up, assuming more data needs saving to the state file.
private static final String STATE_FILE_HEADER = "=state=";
private static final int STATE_FILE_VERSION = 2;
- // Current version of the saved ancestral-dataset file format
+ // key under which we store the saved ancestral-dataset format (starting from Android P)
+ // IMPORTANT: this key needs to come first in the restore data stream (to find out
+ // whether this version of Android knows how to restore the incoming data set), so it needs
+ // to be always the first one in alphabetical order of all the keys
+ private static final String ANCESTRAL_RECORD_KEY = "@ancestral_record@";
+
+ // Current version of the saved ancestral-dataset format
+ // Note that this constant was not used until Android P, and started being used
+ // to version @pm@ data for forwards-compatibility.
private static final int ANCESTRAL_RECORD_VERSION = 1;
+ // Undefined version of the saved ancestral-dataset file format means that the restore data
+ // is coming from pre-Android P device.
+ private static final int UNDEFINED_ANCESTRAL_RECORD_VERSION = -1;
+
private List<PackageInfo> mAllPackages;
private PackageManager mPackageManager;
// version & signature info of each app in a restore set
@@ -175,9 +191,8 @@
// additional involvement by the transport to obtain.
return mRestoredSignatures.keySet();
}
-
- // The backed up data is the signature block for each app, keyed by
- // the package name.
+
+ // The backed up data is the signature block for each app, keyed by the package name.
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) {
if (DEBUG) Slog.v(TAG, "onBackup()");
@@ -196,6 +211,22 @@
mExisting.clear();
}
+ /*
+ * Ancestral record version:
+ *
+ * int ancestralRecordVersion -- the version of the format in which this backup set is
+ * produced
+ */
+ try {
+ if (DEBUG) Slog.v(TAG, "Storing ancestral record version key");
+ outputBufferStream.writeInt(ANCESTRAL_RECORD_VERSION);
+ writeEntity(data, ANCESTRAL_RECORD_KEY, outputBuffer.toByteArray());
+ } catch (IOException e) {
+ // Real error writing data
+ Slog.e(TAG, "Unable to write package backup data file!");
+ return;
+ }
+
long homeVersion = 0;
ArrayList<byte[]> homeSigHashes = null;
PackageInfo homeInfo = null;
@@ -230,6 +261,7 @@
Slog.i(TAG, "Home preference changed; backing up new state " + home);
}
if (home != null) {
+ outputBuffer.reset();
outputBufferStream.writeUTF(home.flattenToString());
outputBufferStream.writeLong(homeVersion);
outputBufferStream.writeUTF(homeInstaller != null ? homeInstaller : "" );
@@ -244,8 +276,8 @@
* Global metadata:
*
* int SDKversion -- the SDK version of the OS itself on the device
- * that produced this backup set. Used to reject
- * backups from later OSes onto earlier ones.
+ * that produced this backup set. Before Android P it was used to
+ * reject backups from later OSes onto earlier ones.
* String incremental -- the incremental release name of the OS stored in
* the backup set.
*/
@@ -354,7 +386,7 @@
// Finally, write the new state blob -- just the list of all apps we handled
writeStateFile(mAllPackages, home, homeVersion, homeSigHashes, newState);
}
-
+
private static void writeEntity(BackupDataOutput data, String key, byte[] bytes)
throws IOException {
data.writeEntityHeader(key, bytes.length);
@@ -366,83 +398,57 @@
// image. We'll use those later to determine what we can legitimately restore.
public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
throws IOException {
- List<ApplicationInfo> restoredApps = new ArrayList<ApplicationInfo>();
- HashMap<String, Metadata> sigMap = new HashMap<String, Metadata>();
if (DEBUG) Slog.v(TAG, "onRestore()");
- int storedSystemVersion = -1;
- while (data.readNextHeader()) {
+ // we expect the ANCESTRAL_RECORD_KEY ("@ancestral_record@") to always come first in the
+ // restore set - based on that value we use different mechanisms to consume the data;
+ // if the ANCESTRAL_RECORD_KEY is missing in the restore set, it means that the data is
+ // is coming from a pre-Android P device, and we consume the header data in the legacy way
+ // TODO: add a CTS test to verify that backups of PMBA generated on Android P+ always
+ // contain the ANCESTRAL_RECORD_KEY, and it's always the first key
+ int ancestralRecordVersion = getAncestralRecordVersionValue(data);
+
+ RestoreDataConsumer consumer = getRestoreDataConsumer(ancestralRecordVersion);
+ if (consumer == null) {
+ Slog.w(TAG, "Ancestral restore set version is unknown"
+ + " to this Android version; not restoring");
+ return;
+ } else {
+ consumer.consumeRestoreData(data);
+ }
+ }
+
+ private int getAncestralRecordVersionValue(BackupDataInput data) throws IOException {
+ int ancestralRecordVersionValue = UNDEFINED_ANCESTRAL_RECORD_VERSION;
+ if (data.readNextHeader()) {
String key = data.getKey();
int dataSize = data.getDataSize();
if (DEBUG) Slog.v(TAG, " got key=" + key + " dataSize=" + dataSize);
- // generic setup to parse any entity data
- byte[] inputBytes = new byte[dataSize];
- data.readEntityData(inputBytes, 0, dataSize);
- ByteArrayInputStream inputBuffer = new ByteArrayInputStream(inputBytes);
- DataInputStream inputBufferStream = new DataInputStream(inputBuffer);
+ if (ANCESTRAL_RECORD_KEY.equals(key)) {
+ // generic setup to parse any entity data
+ byte[] inputBytes = new byte[dataSize];
+ data.readEntityData(inputBytes, 0, dataSize);
+ ByteArrayInputStream inputBuffer = new ByteArrayInputStream(inputBytes);
+ DataInputStream inputBufferStream = new DataInputStream(inputBuffer);
- if (key.equals(GLOBAL_METADATA_KEY)) {
- int storedSdkVersion = inputBufferStream.readInt();
- if (DEBUG) Slog.v(TAG, " storedSystemVersion = " + storedSystemVersion);
- if (storedSystemVersion > Build.VERSION.SDK_INT) {
- // returning before setting the sig map means we rejected the restore set
- Slog.w(TAG, "Restore set was from a later version of Android; not restoring");
- return;
- }
- mStoredSdkVersion = storedSdkVersion;
- mStoredIncrementalVersion = inputBufferStream.readUTF();
- mHasMetadata = true;
- if (DEBUG) {
- Slog.i(TAG, "Restore set version " + storedSystemVersion
- + " is compatible with OS version " + Build.VERSION.SDK_INT
- + " (" + mStoredIncrementalVersion + " vs "
- + Build.VERSION.INCREMENTAL + ")");
- }
- } else if (key.equals(DEFAULT_HOME_KEY)) {
- String cn = inputBufferStream.readUTF();
- mRestoredHome = ComponentName.unflattenFromString(cn);
- mRestoredHomeVersion = inputBufferStream.readLong();
- mRestoredHomeInstaller = inputBufferStream.readUTF();
- mRestoredHomeSigHashes = readSignatureHashArray(inputBufferStream);
- if (DEBUG) {
- Slog.i(TAG, " read preferred home app " + mRestoredHome
- + " version=" + mRestoredHomeVersion
- + " installer=" + mRestoredHomeInstaller
- + " sig=" + mRestoredHomeSigHashes);
- }
- } else {
- // it's a file metadata record
- int versionCodeInt = inputBufferStream.readInt();
- long versionCode;
- if (versionCodeInt == Integer.MIN_VALUE) {
- versionCode = inputBufferStream.readLong();
- } else {
- versionCode = versionCodeInt;
- }
- ArrayList<byte[]> sigs = readSignatureHashArray(inputBufferStream);
- if (DEBUG) {
- Slog.i(TAG, " read metadata for " + key
- + " dataSize=" + dataSize
- + " versionCode=" + versionCode + " sigs=" + sigs);
- }
-
- if (sigs == null || sigs.size() == 0) {
- Slog.w(TAG, "Not restoring package " + key
- + " since it appears to have no signatures.");
- continue;
- }
-
- ApplicationInfo app = new ApplicationInfo();
- app.packageName = key;
- restoredApps.add(app);
- sigMap.put(key, new Metadata(versionCode, sigs));
+ ancestralRecordVersionValue = inputBufferStream.readInt();
}
}
+ return ancestralRecordVersionValue;
+ }
- // On successful completion, cache the signature map for the Backup Manager to use
- mRestoredSignatures = sigMap;
+ private RestoreDataConsumer getRestoreDataConsumer(int ancestralRecordVersion) {
+ switch (ancestralRecordVersion) {
+ case UNDEFINED_ANCESTRAL_RECORD_VERSION:
+ return new LegacyRestoreDataConsumer();
+ case 1:
+ return new AncestralVersion1RestoreDataConsumer();
+ default:
+ Slog.e(TAG, "Unrecognized ANCESTRAL_RECORD_VERSION: " + ancestralRecordVersion);
+ return null;
+ }
}
private static void writeSignatureHashArray(DataOutputStream out, ArrayList<byte[]> hashes)
@@ -639,4 +645,173 @@
Slog.e(TAG, "Unable to write package manager state file!");
}
}
+
+ interface RestoreDataConsumer {
+ void consumeRestoreData(BackupDataInput data) throws IOException;
+ }
+
+ private class LegacyRestoreDataConsumer implements RestoreDataConsumer {
+
+ public void consumeRestoreData(BackupDataInput data) throws IOException {
+ List<ApplicationInfo> restoredApps = new ArrayList<ApplicationInfo>();
+ HashMap<String, Metadata> sigMap = new HashMap<String, Metadata>();
+ int storedSystemVersion = -1;
+
+ if (DEBUG) Slog.i(TAG, "Using LegacyRestoreDataConsumer");
+ // we already have the first header read and "cached", since ANCESTRAL_RECORD_KEY
+ // was missing
+ while (true) {
+ String key = data.getKey();
+ int dataSize = data.getDataSize();
+
+ if (DEBUG) Slog.v(TAG, " got key=" + key + " dataSize=" + dataSize);
+
+ // generic setup to parse any entity data
+ byte[] inputBytes = new byte[dataSize];
+ data.readEntityData(inputBytes, 0, dataSize);
+ ByteArrayInputStream inputBuffer = new ByteArrayInputStream(inputBytes);
+ DataInputStream inputBufferStream = new DataInputStream(inputBuffer);
+
+ if (key.equals(GLOBAL_METADATA_KEY)) {
+ int storedSdkVersion = inputBufferStream.readInt();
+ if (DEBUG) Slog.v(TAG, " storedSystemVersion = " + storedSystemVersion);
+ mStoredSdkVersion = storedSdkVersion;
+ mStoredIncrementalVersion = inputBufferStream.readUTF();
+ mHasMetadata = true;
+ if (DEBUG) {
+ Slog.i(TAG, "Restore set version " + storedSystemVersion
+ + " is compatible with OS version " + Build.VERSION.SDK_INT
+ + " (" + mStoredIncrementalVersion + " vs "
+ + Build.VERSION.INCREMENTAL + ")");
+ }
+ } else if (key.equals(DEFAULT_HOME_KEY)) {
+ String cn = inputBufferStream.readUTF();
+ mRestoredHome = ComponentName.unflattenFromString(cn);
+ mRestoredHomeVersion = inputBufferStream.readLong();
+ mRestoredHomeInstaller = inputBufferStream.readUTF();
+ mRestoredHomeSigHashes = readSignatureHashArray(inputBufferStream);
+ if (DEBUG) {
+ Slog.i(TAG, " read preferred home app " + mRestoredHome
+ + " version=" + mRestoredHomeVersion
+ + " installer=" + mRestoredHomeInstaller
+ + " sig=" + mRestoredHomeSigHashes);
+ }
+ } else {
+ // it's a file metadata record
+ int versionCodeInt = inputBufferStream.readInt();
+ long versionCode;
+ if (versionCodeInt == Integer.MIN_VALUE) {
+ versionCode = inputBufferStream.readLong();
+ } else {
+ versionCode = versionCodeInt;
+ }
+ ArrayList<byte[]> sigs = readSignatureHashArray(inputBufferStream);
+ if (DEBUG) {
+ Slog.i(TAG, " read metadata for " + key
+ + " dataSize=" + dataSize
+ + " versionCode=" + versionCode + " sigs=" + sigs);
+ }
+
+ if (sigs == null || sigs.size() == 0) {
+ Slog.w(TAG, "Not restoring package " + key
+ + " since it appears to have no signatures.");
+ continue;
+ }
+
+ ApplicationInfo app = new ApplicationInfo();
+ app.packageName = key;
+ restoredApps.add(app);
+ sigMap.put(key, new Metadata(versionCode, sigs));
+ }
+
+ boolean readNextHeader = data.readNextHeader();
+ if (!readNextHeader) {
+ if (DEBUG) Slog.v(TAG, "LegacyRestoreDataConsumer:"
+ + " we're done reading all the headers");
+ break;
+ }
+ }
+
+ // On successful completion, cache the signature map for the Backup Manager to use
+ mRestoredSignatures = sigMap;
+ }
+ }
+
+ private class AncestralVersion1RestoreDataConsumer implements RestoreDataConsumer {
+
+ public void consumeRestoreData(BackupDataInput data) throws IOException {
+ List<ApplicationInfo> restoredApps = new ArrayList<ApplicationInfo>();
+ HashMap<String, Metadata> sigMap = new HashMap<String, Metadata>();
+ int storedSystemVersion = -1;
+
+ if (DEBUG) Slog.i(TAG, "Using AncestralVersion1RestoreDataConsumer");
+ while (data.readNextHeader()) {
+ String key = data.getKey();
+ int dataSize = data.getDataSize();
+
+ if (DEBUG) Slog.v(TAG, " got key=" + key + " dataSize=" + dataSize);
+
+ // generic setup to parse any entity data
+ byte[] inputBytes = new byte[dataSize];
+ data.readEntityData(inputBytes, 0, dataSize);
+ ByteArrayInputStream inputBuffer = new ByteArrayInputStream(inputBytes);
+ DataInputStream inputBufferStream = new DataInputStream(inputBuffer);
+
+ if (key.equals(GLOBAL_METADATA_KEY)) {
+ int storedSdkVersion = inputBufferStream.readInt();
+ if (DEBUG) Slog.v(TAG, " storedSystemVersion = " + storedSystemVersion);
+ mStoredSdkVersion = storedSdkVersion;
+ mStoredIncrementalVersion = inputBufferStream.readUTF();
+ mHasMetadata = true;
+ if (DEBUG) {
+ Slog.i(TAG, "Restore set version " + storedSystemVersion
+ + " is compatible with OS version " + Build.VERSION.SDK_INT
+ + " (" + mStoredIncrementalVersion + " vs "
+ + Build.VERSION.INCREMENTAL + ")");
+ }
+ } else if (key.equals(DEFAULT_HOME_KEY)) {
+ String cn = inputBufferStream.readUTF();
+ mRestoredHome = ComponentName.unflattenFromString(cn);
+ mRestoredHomeVersion = inputBufferStream.readLong();
+ mRestoredHomeInstaller = inputBufferStream.readUTF();
+ mRestoredHomeSigHashes = readSignatureHashArray(inputBufferStream);
+ if (DEBUG) {
+ Slog.i(TAG, " read preferred home app " + mRestoredHome
+ + " version=" + mRestoredHomeVersion
+ + " installer=" + mRestoredHomeInstaller
+ + " sig=" + mRestoredHomeSigHashes);
+ }
+ } else {
+ // it's a file metadata record
+ int versionCodeInt = inputBufferStream.readInt();
+ long versionCode;
+ if (versionCodeInt == Integer.MIN_VALUE) {
+ versionCode = inputBufferStream.readLong();
+ } else {
+ versionCode = versionCodeInt;
+ }
+ ArrayList<byte[]> sigs = readSignatureHashArray(inputBufferStream);
+ if (DEBUG) {
+ Slog.i(TAG, " read metadata for " + key
+ + " dataSize=" + dataSize
+ + " versionCode=" + versionCode + " sigs=" + sigs);
+ }
+
+ if (sigs == null || sigs.size() == 0) {
+ Slog.w(TAG, "Not restoring package " + key
+ + " since it appears to have no signatures.");
+ continue;
+ }
+
+ ApplicationInfo app = new ApplicationInfo();
+ app.packageName = key;
+ restoredApps.add(app);
+ sigMap.put(key, new Metadata(versionCode, sigs));
+ }
+ }
+
+ // On successful completion, cache the signature map for the Backup Manager to use
+ mRestoredSignatures = sigMap;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 870811c..81e34df 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -12245,6 +12245,7 @@
mConstants.start(mContext.getContentResolver());
mCoreSettingsObserver = new CoreSettingsObserver(this);
mFontScaleSettingObserver = new FontScaleSettingObserver();
+ GlobalSettingsToPropertiesMapper.start(mContext.getContentResolver());
// Now that the settings provider is published we can consider sending
// in a rescue party.
diff --git a/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java
new file mode 100644
index 0000000..5632fc0
--- /dev/null
+++ b/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2018 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.am;
+
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+/**
+ * Maps global system settings to system properties.
+ * <p>The properties are dynamically updated when settings change.
+ */
+class GlobalSettingsToPropertiesMapper {
+
+ private static final String TAG = "GlobalSettingsToPropertiesMapper";
+
+ private static final String[][] sGlobalSettingsMapping = new String[][] {
+ // List mapping entries in the following format:
+ // {Settings.Global.SETTING_NAME, "system_property_name"},
+ };
+
+
+ private final ContentResolver mContentResolver;
+ private final String[][] mGlobalSettingsMapping;
+
+ @VisibleForTesting
+ GlobalSettingsToPropertiesMapper(ContentResolver contentResolver,
+ String[][] globalSettingsMapping) {
+ mContentResolver = contentResolver;
+ mGlobalSettingsMapping = globalSettingsMapping;
+ }
+
+ void updatePropertiesFromGlobalSettings() {
+ for (String[] entry : mGlobalSettingsMapping) {
+ final String settingName = entry[0];
+ final String propName = entry[1];
+ Uri settingUri = Settings.Global.getUriFor(settingName);
+ Preconditions.checkNotNull(settingUri, "Setting " + settingName + " not found");
+ ContentObserver co = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updatePropertyFromSetting(settingName, propName);
+ }
+ };
+ updatePropertyFromSetting(settingName, propName);
+ mContentResolver.registerContentObserver(settingUri, false, co);
+ }
+ }
+
+ public static void start(ContentResolver contentResolver) {
+ new GlobalSettingsToPropertiesMapper(contentResolver, sGlobalSettingsMapping)
+ .updatePropertiesFromGlobalSettings();
+ }
+
+ private String getGlobalSetting(String name) {
+ return Settings.Global.getString(mContentResolver, name);
+ }
+
+ private void setProperty(String key, String value) {
+ // Check if need to clear the property
+ if (value == null) {
+ // It's impossible to remove system property, therefore we check previous value to
+ // avoid setting an empty string if the property wasn't set.
+ if (TextUtils.isEmpty(systemPropertiesGet(key))) {
+ return;
+ }
+ value = "";
+ }
+ try {
+ systemPropertiesSet(key, value);
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Unable to set property " + key + " value '" + value + "'", e);
+ }
+ }
+
+ @VisibleForTesting
+ protected String systemPropertiesGet(String key) {
+ return SystemProperties.get(key);
+ }
+
+ @VisibleForTesting
+ protected void systemPropertiesSet(String key, String value) {
+ SystemProperties.set(key, value);
+ }
+
+ @VisibleForTesting
+ void updatePropertyFromSetting(String settingName, String propName) {
+ String settingValue = getGlobalSetting(settingName);
+ setProperty(propName, settingValue);
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
index e385833..6dafd0b 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -89,7 +89,7 @@
userId,
credentialType,
credential,
- () -> PlatformKeyManager.getInstance(context, recoverableKeyStoreDb, userId));
+ () -> PlatformKeyManager.getInstance(context, recoverableKeyStoreDb));
}
/**
@@ -246,7 +246,7 @@
throws InsecureUserException, KeyStoreException, UnrecoverableKeyException,
NoSuchAlgorithmException, NoSuchPaddingException, BadPlatformKeyException {
PlatformKeyManager platformKeyManager = mPlatformKeyManagerFactory.newInstance();
- PlatformDecryptionKey decryptKey = platformKeyManager.getDecryptKey();
+ PlatformDecryptionKey decryptKey = platformKeyManager.getDecryptKey(mUserId);
Map<String, WrappedKey> wrappedKeys = mRecoverableKeyStoreDb.getAllKeys(
mUserId, decryptKey.getGenerationId());
return WrappedKey.unwrapKeys(decryptKey, wrappedKeys);
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
index a8b8361..7005de5 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
@@ -71,7 +71,6 @@
private final Context mContext;
private final KeyStoreProxy mKeyStore;
private final RecoverableKeyStoreDb mDatabase;
- private final int mUserId;
private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
@@ -80,34 +79,25 @@
* defined by {@code context}.
*
* @param context This should be the context of the RecoverableKeyStoreLoader service.
- * @param userId The ID of the user to whose lock screen the platform key must be bound.
* @throws KeyStoreException if failed to initialize AndroidKeyStore.
* @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
- * @throws InsecureUserException if the user does not have a lock screen set.
* @throws SecurityException if the caller does not have permission to write to /data/system.
*
* @hide
*/
- public static PlatformKeyManager getInstance(Context context, RecoverableKeyStoreDb database,
- int userId)
- throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException {
- context = context.getApplicationContext();
- PlatformKeyManager keyManager = new PlatformKeyManager(
- userId,
- context,
+ public static PlatformKeyManager getInstance(Context context, RecoverableKeyStoreDb database)
+ throws KeyStoreException, NoSuchAlgorithmException {
+ return new PlatformKeyManager(
+ context.getApplicationContext(),
new KeyStoreProxyImpl(getAndLoadAndroidKeyStore()),
database);
- keyManager.init();
- return keyManager;
}
@VisibleForTesting
PlatformKeyManager(
- int userId,
Context context,
KeyStoreProxy keyStore,
RecoverableKeyStoreDb database) {
- mUserId = userId;
mKeyStore = keyStore;
mContext = context;
mDatabase = database;
@@ -118,67 +108,91 @@
* key has to be replaced. (e.g., because the user has removed and then re-added their lock
* screen). Returns -1 if no key has been generated yet.
*
+ * @param userId The ID of the user to whose lock screen the platform key must be bound.
+ *
* @hide
*/
- public int getGenerationId() {
- return mDatabase.getPlatformKeyGenerationId(mUserId);
+ public int getGenerationId(int userId) {
+ return mDatabase.getPlatformKeyGenerationId(userId);
}
/**
* Returns {@code true} if the platform key is available. A platform key won't be available if
* the user has not set up a lock screen.
*
+ * @param userId The ID of the user to whose lock screen the platform key must be bound.
+ *
* @hide
*/
- public boolean isAvailable() {
- return mContext.getSystemService(KeyguardManager.class).isDeviceSecure(mUserId);
+ public boolean isAvailable(int userId) {
+ return mContext.getSystemService(KeyguardManager.class).isDeviceSecure(userId);
}
/**
* Generates a new key and increments the generation ID. Should be invoked if the platform key
* is corrupted and needs to be rotated.
*
+ * @param userId The ID of the user to whose lock screen the platform key must be bound.
* @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
* @throws KeyStoreException if there is an error in AndroidKeyStore.
+ * @throws InsecureUserException if the user does not have a lock screen set.
*
* @hide
*/
- public void regenerate() throws NoSuchAlgorithmException, KeyStoreException {
- int nextId = getGenerationId() + 1;
- generateAndLoadKey(nextId);
+ public void regenerate(int userId)
+ throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException {
+ if (!isAvailable(userId)) {
+ throw new InsecureUserException(String.format(
+ Locale.US, "%d does not have a lock screen set.", userId));
+ }
+
+ int generationId = getGenerationId(userId);
+ int nextId;
+ if (generationId == -1) {
+ nextId = 1;
+ } else {
+ nextId = generationId + 1;
+ }
+ generateAndLoadKey(userId, nextId);
}
/**
* Returns the platform key used for encryption.
*
+ * @param userId The ID of the user to whose lock screen the platform key must be bound.
* @throws KeyStoreException if there was an AndroidKeyStore error.
* @throws UnrecoverableKeyException if the key could not be recovered.
* @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
+ * @throws InsecureUserException if the user does not have a lock screen set.
*
* @hide
*/
- public PlatformEncryptionKey getEncryptKey()
- throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException {
- int generationId = getGenerationId();
+ public PlatformEncryptionKey getEncryptKey(int userId) throws KeyStoreException,
+ UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
+ init(userId);
+ int generationId = getGenerationId(userId);
AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey(
- getEncryptAlias(generationId), /*password=*/ null);
+ getEncryptAlias(userId, generationId), /*password=*/ null);
return new PlatformEncryptionKey(generationId, key);
}
/**
* Returns the platform key used for decryption. Only works after a recent screen unlock.
*
+ * @param userId The ID of the user to whose lock screen the platform key must be bound.
* @throws KeyStoreException if there was an AndroidKeyStore error.
* @throws UnrecoverableKeyException if the key could not be recovered.
* @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
+ * @throws InsecureUserException if the user does not have a lock screen set.
*
* @hide
*/
- public PlatformDecryptionKey getDecryptKey()
- throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException {
- int generationId = getGenerationId();
+ public PlatformDecryptionKey getDecryptKey(int userId) throws KeyStoreException,
+ UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
+ init(userId);
+ int generationId = getGenerationId(userId);
AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey(
- getDecryptAlias(generationId), /*password=*/ null);
+ getDecryptAlias(userId, generationId), /*password=*/ null);
return new PlatformDecryptionKey(generationId, key);
}
@@ -186,38 +200,36 @@
* Initializes the class. If there is no current platform key, and the user has a lock screen
* set, will create the platform key and set the generation ID.
*
+ * @param userId The ID of the user to whose lock screen the platform key must be bound.
* @throws KeyStoreException if there was an error in AndroidKeyStore.
* @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
*
* @hide
*/
- public void init() throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException {
- if (!isAvailable()) {
+ void init(int userId)
+ throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException {
+ if (!isAvailable(userId)) {
throw new InsecureUserException(String.format(
- Locale.US, "%d does not have a lock screen set.", mUserId));
+ Locale.US, "%d does not have a lock screen set.", userId));
}
- int generationId = getGenerationId();
- if (isKeyLoaded(generationId)) {
+ int generationId = getGenerationId(userId);
+ if (isKeyLoaded(userId, generationId)) {
Log.i(TAG, String.format(
Locale.US, "Platform key generation %d exists already.", generationId));
return;
}
if (generationId == -1) {
Log.i(TAG, "Generating initial platform ID.");
+ generationId = 1;
} else {
Log.w(TAG, String.format(Locale.US, "Platform generation ID was %d but no "
+ "entry was present in AndroidKeyStore. Generating fresh key.", generationId));
- }
-
- if (generationId == -1) {
- generationId = 1;
- } else {
// Had to generate a fresh key, bump the generation id
generationId++;
}
- generateAndLoadKey(generationId);
+ generateAndLoadKey(userId, generationId);
}
/**
@@ -227,11 +239,12 @@
* <p>These IDs look as follows:
* {@code com.security.recoverablekeystore/platform/<user id>/<generation id>/encrypt}
*
+ * @param userId The ID of the user to whose lock screen the platform key must be bound.
* @param generationId The generation ID.
* @return The alias.
*/
- private String getEncryptAlias(int generationId) {
- return KEY_ALIAS_PREFIX + mUserId + "/" + generationId + "/" + ENCRYPT_KEY_ALIAS_SUFFIX;
+ private String getEncryptAlias(int userId, int generationId) {
+ return KEY_ALIAS_PREFIX + userId + "/" + generationId + "/" + ENCRYPT_KEY_ALIAS_SUFFIX;
}
/**
@@ -241,18 +254,19 @@
* <p>These IDs look as follows:
* {@code com.security.recoverablekeystore/platform/<user id>/<generation id>/decrypt}
*
+ * @param userId The ID of the user to whose lock screen the platform key must be bound.
* @param generationId The generation ID.
* @return The alias.
*/
- private String getDecryptAlias(int generationId) {
- return KEY_ALIAS_PREFIX + mUserId + "/" + generationId + "/" + DECRYPT_KEY_ALIAS_SUFFIX;
+ private String getDecryptAlias(int userId, int generationId) {
+ return KEY_ALIAS_PREFIX + userId + "/" + generationId + "/" + DECRYPT_KEY_ALIAS_SUFFIX;
}
/**
* Sets the current generation ID to {@code generationId}.
*/
- private void setGenerationId(int generationId) {
- mDatabase.setPlatformKeyGenerationId(mUserId, generationId);
+ private void setGenerationId(int userId, int generationId) {
+ mDatabase.setPlatformKeyGenerationId(userId, generationId);
}
/**
@@ -261,9 +275,9 @@
*
* @throws KeyStoreException if there was an error checking AndroidKeyStore.
*/
- private boolean isKeyLoaded(int generationId) throws KeyStoreException {
- return mKeyStore.containsAlias(getEncryptAlias(generationId))
- && mKeyStore.containsAlias(getDecryptAlias(generationId));
+ private boolean isKeyLoaded(int userId, int generationId) throws KeyStoreException {
+ return mKeyStore.containsAlias(getEncryptAlias(userId, generationId))
+ && mKeyStore.containsAlias(getDecryptAlias(userId, generationId));
}
/**
@@ -274,10 +288,10 @@
* available since API version 1.
* @throws KeyStoreException if there was an issue loading the keys into AndroidKeyStore.
*/
- private void generateAndLoadKey(int generationId)
+ private void generateAndLoadKey(int userId, int generationId)
throws NoSuchAlgorithmException, KeyStoreException {
- String encryptAlias = getEncryptAlias(generationId);
- String decryptAlias = getDecryptAlias(generationId);
+ String encryptAlias = getEncryptAlias(userId, generationId);
+ String decryptAlias = getDecryptAlias(userId, generationId);
SecretKey secretKey = generateAesKey();
mKeyStore.setEntry(
@@ -296,10 +310,10 @@
USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
- .setBoundToSpecificSecureUserId(mUserId)
+ .setBoundToSpecificSecureUserId(userId)
.build());
- setGenerationId(generationId);
+ setGenerationId(userId, generationId);
try {
secretKey.destroy();
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index 84d4722..3379f83 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -85,22 +85,34 @@
private final RecoverySnapshotListenersStorage mListenersStorage;
private final RecoverableKeyGenerator mRecoverableKeyGenerator;
private final RecoverySnapshotStorage mSnapshotStorage;
+ private final PlatformKeyManager mPlatformKeyManager;
/**
* Returns a new or existing instance.
*
* @hide
*/
- public static synchronized RecoverableKeyStoreManager getInstance(Context mContext) {
+ public static synchronized RecoverableKeyStoreManager getInstance(Context context) {
if (mInstance == null) {
- RecoverableKeyStoreDb db = RecoverableKeyStoreDb.newInstance(mContext);
+ RecoverableKeyStoreDb db = RecoverableKeyStoreDb.newInstance(context);
+ PlatformKeyManager platformKeyManager;
+ try {
+ platformKeyManager = PlatformKeyManager.getInstance(context, db);
+ } catch (NoSuchAlgorithmException e) {
+ // Impossible: all algorithms must be supported by AOSP
+ throw new RuntimeException(e);
+ } catch (KeyStoreException e) {
+ throw new ServiceSpecificException(ERROR_KEYSTORE_INTERNAL_ERROR, e.getMessage());
+ }
+
mInstance = new RecoverableKeyStoreManager(
- mContext.getApplicationContext(),
+ context.getApplicationContext(),
db,
new RecoverySessionStorage(),
Executors.newSingleThreadExecutor(),
new RecoverySnapshotStorage(),
- new RecoverySnapshotListenersStorage());
+ new RecoverySnapshotListenersStorage(),
+ platformKeyManager);
}
return mInstance;
}
@@ -112,13 +124,16 @@
RecoverySessionStorage recoverySessionStorage,
ExecutorService executorService,
RecoverySnapshotStorage snapshotStorage,
- RecoverySnapshotListenersStorage listenersStorage) {
+ RecoverySnapshotListenersStorage listenersStorage,
+ PlatformKeyManager platformKeyManager) {
mContext = context;
mDatabase = recoverableKeyStoreDb;
mRecoverySessionStorage = recoverySessionStorage;
mExecutorService = executorService;
mListenersStorage = listenersStorage;
mSnapshotStorage = snapshotStorage;
+ mPlatformKeyManager = platformKeyManager;
+
try {
mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mDatabase);
} catch (NoSuchAlgorithmException e) {
@@ -380,12 +395,10 @@
int uid = Binder.getCallingUid();
int userId = UserHandle.getCallingUserId();
- PlatformEncryptionKey encryptionKey;
+ PlatformEncryptionKey encryptionKey;
try {
- PlatformKeyManager platformKeyManager = PlatformKeyManager.getInstance(
- mContext, mDatabase, userId);
- encryptionKey = platformKeyManager.getEncryptKey();
+ encryptionKey = mPlatformKeyManager.getEncryptKey(userId);
} catch (NoSuchAlgorithmException e) {
// Impossible: all algorithms must be supported by AOSP
throw new RuntimeException(e);
diff --git a/services/tests/servicestests/src/com/android/server/am/GlobalSettingsToPropertiesMapperTest.java b/services/tests/servicestests/src/com/android/server/am/GlobalSettingsToPropertiesMapperTest.java
new file mode 100644
index 0000000..d9b3e1c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/GlobalSettingsToPropertiesMapperTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2018 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.am;
+
+import android.content.ContentResolver;
+import android.provider.Settings;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.mock.MockContentResolver;
+
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.test.FakeSettingsProvider;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Tests for {@link GlobalSettingsToPropertiesMapper}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class GlobalSettingsToPropertiesMapperTest {
+ private static final String[][] TEST_MAPPING = new String[][] {
+ {Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, "TestProperty"}
+ };
+
+ private TestMapper mTestMapper;
+ private MockContentResolver mMockContentResolver;
+
+ @Before
+ public void setup() {
+ // Use FakeSettingsProvider to not affect global state
+ mMockContentResolver = new MockContentResolver(InstrumentationRegistry.getContext());
+ mMockContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+ mTestMapper = new TestMapper(mMockContentResolver);
+ }
+
+ @Test
+ public void testUpdatePropertiesFromGlobalSettings() {
+ Settings.Global.putString(mMockContentResolver,
+ Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, "testValue");
+
+ mTestMapper.updatePropertiesFromGlobalSettings();
+ String propValue = mTestMapper.systemPropertiesGet("TestProperty");
+ Assert.assertEquals("testValue", propValue);
+
+ Settings.Global.putString(mMockContentResolver,
+ Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, "testValue2");
+ mTestMapper.updatePropertyFromSetting(Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS,
+ "TestProperty");
+ propValue = mTestMapper.systemPropertiesGet("TestProperty");
+ Assert.assertEquals("testValue2", propValue);
+
+ Settings.Global.putString(mMockContentResolver,
+ Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, null);
+ mTestMapper.updatePropertyFromSetting(Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS,
+ "TestProperty");
+ propValue = mTestMapper.systemPropertiesGet("TestProperty");
+ Assert.assertEquals("", propValue);
+ }
+
+ @Test
+ public void testUpdatePropertiesFromGlobalSettings_PropertyAndSettingNotPresent() {
+ // Test that empty property will not not be set if setting is not set
+ mTestMapper.updatePropertiesFromGlobalSettings();
+ String propValue = mTestMapper.systemPropertiesGet("TestProperty");
+ Assert.assertNull("Property should not be set if setting is null", propValue);
+ }
+
+ private static class TestMapper extends GlobalSettingsToPropertiesMapper {
+ private final Map<String, String> mProps = new HashMap<>();
+
+ TestMapper(ContentResolver contentResolver) {
+ super(contentResolver, TEST_MAPPING);
+ }
+
+ @Override
+ protected String systemPropertiesGet(String key) {
+ Preconditions.checkNotNull(key);
+ return mProps.get(key);
+ }
+
+ @Override
+ protected void systemPropertiesSet(String key, String value) {
+ Preconditions.checkNotNull(value);
+ Preconditions.checkNotNull(key);
+ mProps.put(key, value);
+ }
+ }
+
+}
+
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
index 1aac8ce..a0dcfdc 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
@@ -114,7 +114,7 @@
mWrappingKey = generateAndroidKeyStoreKey();
mEncryptKey = new PlatformEncryptionKey(TEST_GENERATION_ID, mWrappingKey);
- when(mPlatformKeyManager.getDecryptKey()).thenReturn(
+ when(mPlatformKeyManager.getDecryptKey(TEST_USER_ID)).thenReturn(
new PlatformDecryptionKey(TEST_GENERATION_ID, mWrappingKey));
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
index 97fbca2..b1bff70 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
@@ -78,7 +78,7 @@
mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
mPlatformKeyManager = new PlatformKeyManager(
- USER_ID_FIXTURE, mContext, mKeyStoreProxy, mRecoverableKeyStoreDb);
+ mContext, mKeyStoreProxy, mRecoverableKeyStoreDb);
when(mContext.getSystemService(anyString())).thenReturn(mKeyguardManager);
when(mContext.getSystemServiceName(any())).thenReturn("test");
@@ -93,7 +93,7 @@
@Test
public void init_createsEncryptKeyWithCorrectAlias() throws Exception {
- mPlatformKeyManager.init();
+ mPlatformKeyManager.init(USER_ID_FIXTURE);
verify(mKeyStoreProxy).setEntry(
eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
@@ -103,14 +103,14 @@
@Test
public void init_createsEncryptKeyWithCorrectPurposes() throws Exception {
- mPlatformKeyManager.init();
+ mPlatformKeyManager.init(USER_ID_FIXTURE);
assertEquals(KeyProperties.PURPOSE_ENCRYPT, getEncryptKeyProtection().getPurposes());
}
@Test
public void init_createsEncryptKeyWithCorrectPaddings() throws Exception {
- mPlatformKeyManager.init();
+ mPlatformKeyManager.init(USER_ID_FIXTURE);
assertArrayEquals(
new String[] { KeyProperties.ENCRYPTION_PADDING_NONE },
@@ -119,7 +119,7 @@
@Test
public void init_createsEncryptKeyWithCorrectBlockModes() throws Exception {
- mPlatformKeyManager.init();
+ mPlatformKeyManager.init(USER_ID_FIXTURE);
assertArrayEquals(
new String[] { KeyProperties.BLOCK_MODE_GCM },
@@ -128,14 +128,14 @@
@Test
public void init_createsEncryptKeyWithoutAuthenticationRequired() throws Exception {
- mPlatformKeyManager.init();
+ mPlatformKeyManager.init(USER_ID_FIXTURE);
assertFalse(getEncryptKeyProtection().isUserAuthenticationRequired());
}
@Test
public void init_createsDecryptKeyWithCorrectAlias() throws Exception {
- mPlatformKeyManager.init();
+ mPlatformKeyManager.init(USER_ID_FIXTURE);
verify(mKeyStoreProxy).setEntry(
eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
@@ -145,14 +145,14 @@
@Test
public void init_createsDecryptKeyWithCorrectPurposes() throws Exception {
- mPlatformKeyManager.init();
+ mPlatformKeyManager.init(USER_ID_FIXTURE);
assertEquals(KeyProperties.PURPOSE_DECRYPT, getDecryptKeyProtection().getPurposes());
}
@Test
public void init_createsDecryptKeyWithCorrectPaddings() throws Exception {
- mPlatformKeyManager.init();
+ mPlatformKeyManager.init(USER_ID_FIXTURE);
assertArrayEquals(
new String[] { KeyProperties.ENCRYPTION_PADDING_NONE },
@@ -161,7 +161,7 @@
@Test
public void init_createsDecryptKeyWithCorrectBlockModes() throws Exception {
- mPlatformKeyManager.init();
+ mPlatformKeyManager.init(USER_ID_FIXTURE);
assertArrayEquals(
new String[] { KeyProperties.BLOCK_MODE_GCM },
@@ -170,14 +170,14 @@
@Test
public void init_createsDecryptKeyWithAuthenticationRequired() throws Exception {
- mPlatformKeyManager.init();
+ mPlatformKeyManager.init(USER_ID_FIXTURE);
assertTrue(getDecryptKeyProtection().isUserAuthenticationRequired());
}
@Test
public void init_createsDecryptKeyWithAuthenticationValidFor15Seconds() throws Exception {
- mPlatformKeyManager.init();
+ mPlatformKeyManager.init(USER_ID_FIXTURE);
assertEquals(
USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS,
@@ -186,7 +186,7 @@
@Test
public void init_createsDecryptKeyBoundToTheUsersAuthentication() throws Exception {
- mPlatformKeyManager.init();
+ mPlatformKeyManager.init(USER_ID_FIXTURE);
assertEquals(
USER_ID_FIXTURE,
@@ -195,7 +195,7 @@
@Test
public void init_createsBothKeysWithSameMaterial() throws Exception {
- mPlatformKeyManager.init();
+ mPlatformKeyManager.init(USER_ID_FIXTURE);
verify(mKeyStoreProxy, times(2)).setEntry(any(), mEntryArgumentCaptor.capture(), any());
List<KeyStore.Entry> entries = mEntryArgumentCaptor.getAllValues();
@@ -206,7 +206,7 @@
@Test
public void init_savesGenerationIdToDatabase() throws Exception {
- mPlatformKeyManager.init();
+ mPlatformKeyManager.init(USER_ID_FIXTURE);
assertEquals(1,
mRecoverableKeyStoreDb.getPlatformKeyGenerationId(USER_ID_FIXTURE));
@@ -214,23 +214,23 @@
@Test
public void init_setsGenerationIdTo1() throws Exception {
- mPlatformKeyManager.init();
+ mPlatformKeyManager.init(USER_ID_FIXTURE);
- assertEquals(1, mPlatformKeyManager.getGenerationId());
+ assertEquals(1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
}
@Test
public void init_incrementsGenerationIdIfKeyIsUnavailable() throws Exception {
- mPlatformKeyManager.init();
+ mPlatformKeyManager.init(USER_ID_FIXTURE);
- mPlatformKeyManager.init();
+ mPlatformKeyManager.init(USER_ID_FIXTURE);
- assertEquals(2, mPlatformKeyManager.getGenerationId());
+ assertEquals(2, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
}
@Test
public void init_doesNotIncrementGenerationIdIfKeyAvailable() throws Exception {
- mPlatformKeyManager.init();
+ mPlatformKeyManager.init(USER_ID_FIXTURE);
when(mKeyStoreProxy
.containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ "platform/42/1/decrypt")).thenReturn(true);
@@ -238,21 +238,19 @@
.containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ "platform/42/1/encrypt")).thenReturn(true);
- mPlatformKeyManager.init();
+ mPlatformKeyManager.init(USER_ID_FIXTURE);
- assertEquals(1, mPlatformKeyManager.getGenerationId());
+ assertEquals(1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
}
@Test
public void getGenerationId_returnsMinusOneIfNotInitialized() throws Exception {
- assertEquals(-1, mPlatformKeyManager.getGenerationId());
+ assertEquals(-1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
}
@Test
public void getDecryptKey_getsDecryptKeyWithCorrectAlias() throws Exception {
- mPlatformKeyManager.init();
-
- mPlatformKeyManager.getDecryptKey();
+ mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
verify(mKeyStoreProxy).getKey(
eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
@@ -261,9 +259,7 @@
@Test
public void getEncryptKey_getsDecryptKeyWithCorrectAlias() throws Exception {
- mPlatformKeyManager.init();
-
- mPlatformKeyManager.getEncryptKey();
+ mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
verify(mKeyStoreProxy).getKey(
eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
@@ -272,18 +268,18 @@
@Test
public void regenerate_incrementsTheGenerationId() throws Exception {
- mPlatformKeyManager.init();
+ mPlatformKeyManager.init(USER_ID_FIXTURE);
- mPlatformKeyManager.regenerate();
+ mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
- assertEquals(2, mPlatformKeyManager.getGenerationId());
+ assertEquals(2, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
}
@Test
public void regenerate_generatesANewEncryptKeyWithTheCorrectAlias() throws Exception {
- mPlatformKeyManager.init();
+ mPlatformKeyManager.init(USER_ID_FIXTURE);
- mPlatformKeyManager.regenerate();
+ mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
verify(mKeyStoreProxy).setEntry(
eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
@@ -293,9 +289,9 @@
@Test
public void regenerate_generatesANewDecryptKeyWithTheCorrectAlias() throws Exception {
- mPlatformKeyManager.init();
+ mPlatformKeyManager.init(USER_ID_FIXTURE);
- mPlatformKeyManager.regenerate();
+ mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
verify(mKeyStoreProxy).setEntry(
eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index 445fbde..26f27b4 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -36,9 +36,11 @@
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
-import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.UserHandle;
+import android.security.keystore.AndroidKeyStoreSecretKey;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
import android.security.recoverablekeystore.KeyDerivationParameters;
import android.security.recoverablekeystore.KeyEntryRecoveryData;
import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
@@ -67,9 +69,8 @@
import java.util.Map;
import java.util.Random;
-import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
-import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
@SmallTest
@@ -77,7 +78,6 @@
public class RecoverableKeyStoreManagerTest {
private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
- private static final String KEY_WRAP_CIPHER_ALGORITHM = "AES/GCM/NoPadding";
private static final String TEST_SESSION_ID = "karlin";
private static final byte[] TEST_PUBLIC_KEY = new byte[] {
(byte) 0x30, (byte) 0x59, (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, (byte) 0x2a,
@@ -97,6 +97,8 @@
private static final byte[] TEST_SECRET = getUtf8Bytes("password1234");
private static final byte[] TEST_VAULT_CHALLENGE = getUtf8Bytes("vault_challenge");
private static final byte[] TEST_VAULT_PARAMS = getUtf8Bytes("vault_params");
+ private static final int TEST_GENERATION_ID = 2;
+ private static final int TEST_USER_ID = 10009;
private static final int KEY_CLAIMANT_LENGTH_BYTES = 16;
private static final byte[] RECOVERY_RESPONSE_HEADER =
"V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8);
@@ -105,20 +107,24 @@
private static final int GENERATION_ID = 1;
private static final byte[] NONCE = getUtf8Bytes("nonce");
private static final byte[] KEY_MATERIAL = getUtf8Bytes("keymaterial");
- private static final int GCM_TAG_SIZE_BITS = 128;
+ private static final String KEY_ALGORITHM = "AES";
+ private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
+ private static final String WRAPPING_KEY_ALIAS = "RecoverableKeyStoreManagerTest/WrappingKey";
@Mock private Context mMockContext;
@Mock private RecoverySnapshotListenersStorage mMockListenersStorage;
@Mock private KeyguardManager mKeyguardManager;
+ @Mock private PlatformKeyManager mPlatformKeyManager;
private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
private File mDatabaseFile;
private RecoverableKeyStoreManager mRecoverableKeyStoreManager;
private RecoverySessionStorage mRecoverySessionStorage;
private RecoverySnapshotStorage mRecoverySnapshotStorage;
+ private PlatformEncryptionKey mPlatformEncryptionKey;
@Before
- public void setUp() {
+ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
Context context = InstrumentationRegistry.getTargetContext();
@@ -130,7 +136,11 @@
when(mMockContext.getSystemService(anyString())).thenReturn(mKeyguardManager);
when(mMockContext.getSystemServiceName(any())).thenReturn("test");
when(mMockContext.getApplicationContext()).thenReturn(mMockContext);
- when(mKeyguardManager.isDeviceSecure(anyInt())).thenReturn(true);
+ when(mKeyguardManager.isDeviceSecure(TEST_USER_ID)).thenReturn(true);
+
+ mPlatformEncryptionKey =
+ new PlatformEncryptionKey(TEST_GENERATION_ID, generateAndroidKeyStoreKey());
+ when(mPlatformKeyManager.getEncryptKey(anyInt())).thenReturn(mPlatformEncryptionKey);
mRecoverableKeyStoreManager = new RecoverableKeyStoreManager(
mMockContext,
@@ -138,7 +148,8 @@
mRecoverySessionStorage,
Executors.newSingleThreadExecutor(),
mRecoverySnapshotStorage,
- mMockListenersStorage);
+ mMockListenersStorage,
+ mPlatformKeyManager);
}
@After
@@ -212,7 +223,7 @@
TEST_VAULT_CHALLENGE,
ImmutableList.of());
fail("should have thrown");
- } catch (RemoteException e) {
+ } catch (ServiceSpecificException e) {
assertEquals("Only a single KeyStoreRecoveryMetadata is supported", e.getMessage());
}
}
@@ -232,7 +243,7 @@
KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
TEST_SECRET)));
fail("should have thrown");
- } catch (RemoteException e) {
+ } catch (ServiceSpecificException e) {
assertEquals("Not a valid X509 key", e.getMessage());
}
}
@@ -273,7 +284,6 @@
fail("should have thrown");
} catch (ServiceSpecificException e) {
assertThat(e.getMessage()).startsWith("Failed to decrypt recovery key");
- //assertEquals("Failed to decrypt recovery key", e.getMessage());
}
}
@@ -304,8 +314,8 @@
/*encryptedRecoveryKey=*/ encryptedClaimResponse,
/*applicationKeys=*/ ImmutableList.of(badApplicationKey));
fail("should have thrown");
- } catch (RemoteException e) {
- assertEquals("Failed to recover key with alias 'nick'", e.getMessage());
+ } catch (ServiceSpecificException e) {
+ assertThat(e.getMessage()).startsWith("Failed to recover key with alias 'nick'");
}
}
@@ -466,4 +476,16 @@
new Random().nextBytes(bytes);
return bytes;
}
+
+ private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception {
+ KeyGenerator keyGenerator = KeyGenerator.getInstance(
+ KEY_ALGORITHM,
+ ANDROID_KEY_STORE_PROVIDER);
+ keyGenerator.init(new KeyGenParameterSpec.Builder(
+ WRAPPING_KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ .build());
+ return (AndroidKeyStoreSecretKey) keyGenerator.generateKey();
+ }
}