Merge "Removed Parcelable from FieldClassification."
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index e2fd82d..21e203b 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -87,7 +87,6 @@
import android.util.TypedValue;
import android.util.apk.ApkSignatureSchemeV2Verifier;
import android.util.apk.ApkSignatureVerifier;
-import android.util.apk.SignatureNotFoundException;
import android.view.Gravity;
import com.android.internal.R;
@@ -1561,41 +1560,35 @@
boolean systemDir = (parseFlags & PARSE_IS_SYSTEM_DIR) != 0;
int minSignatureScheme = ApkSignatureVerifier.VERSION_JAR_SIGNATURE_SCHEME;
- if ((parseFlags & PARSE_IS_EPHEMERAL) != 0 || pkg.applicationInfo.isStaticSharedLibrary()) {
+ if (pkg.applicationInfo.isStaticSharedLibrary()) {
// must use v2 signing scheme
minSignatureScheme = ApkSignatureVerifier.VERSION_APK_SIGNATURE_SCHEME_V2;
}
- try {
- ApkSignatureVerifier.Result verified =
- ApkSignatureVerifier.verify(apkPath, minSignatureScheme, systemDir);
- if (pkg.mCertificates == null) {
- pkg.mCertificates = verified.certs;
- pkg.mSignatures = verified.sigs;
- pkg.mSigningKeys = new ArraySet<>(verified.certs.length);
- for (int i = 0; i < verified.certs.length; i++) {
- Certificate[] signerCerts = verified.certs[i];
- Certificate signerCert = signerCerts[0];
- pkg.mSigningKeys.add(signerCert.getPublicKey());
- }
- } else {
- if (!Signature.areExactMatch(pkg.mSignatures, verified.sigs)) {
- throw new PackageParserException(
- INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
- apkPath + " has mismatched certificates");
- }
- }
- } catch (SignatureNotFoundException e) {
+ ApkSignatureVerifier.Result verified =
+ ApkSignatureVerifier.verify(apkPath, minSignatureScheme, systemDir);
+ if (verified.signatureSchemeVersion
+ < ApkSignatureVerifier.VERSION_APK_SIGNATURE_SCHEME_V2) {
+ // TODO (b/68860689): move this logic to packagemanagerserivce
if ((parseFlags & PARSE_IS_EPHEMERAL) != 0) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
- "No APK Signature Scheme v2 signature in ephemeral package " + apkPath,
- e);
+ "No APK Signature Scheme v2 signature in ephemeral package " + apkPath);
}
- if (pkg.applicationInfo.isStaticSharedLibrary()) {
- throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
- "Static shared libs must use v2 signature scheme " + apkPath);
+ }
+ if (pkg.mCertificates == null) {
+ pkg.mCertificates = verified.certs;
+ pkg.mSignatures = verified.sigs;
+ pkg.mSigningKeys = new ArraySet<>(verified.certs.length);
+ for (int i = 0; i < verified.certs.length; i++) {
+ Certificate[] signerCerts = verified.certs[i];
+ Certificate signerCert = signerCerts[0];
+ pkg.mSigningKeys.add(signerCert.getPublicKey());
}
- throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
- "No APK Signature Scheme v2 signature in package " + apkPath, e);
+ } else {
+ if (!Signature.areExactMatch(pkg.mSignatures, verified.sigs)) {
+ throw new PackageParserException(
+ INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
+ apkPath + " has mismatched certificates");
+ }
}
}
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index 61b0eb0..8623524 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -36,15 +36,26 @@
import java.util.List;
/**
- * The ShortcutManager manages an app's <em>shortcuts</em>. Shortcuts provide users with quick
- * access to activities other than an app's main activity in the currently-active launcher, provided
- * that the launcher supports app shortcuts. For example, an email app may publish the "compose new
- * email" action, which will directly open the compose activity. The {@link ShortcutInfo} class
- * contains information about each of the shortcuts themselves.
+ * The ShortcutManager performs operations on an app's set of <em>shortcuts</em>. The
+ * {@link ShortcutInfo} class contains information about each of the shortcuts themselves.
+ *
+ * <p>An app's shortcuts represent specific tasks and actions that users can take within your app.
+ * When a user selects a shortcut in the currently-active launcher, your app opens an activity other
+ * than the app's starting activity, provided that the currently-active launcher supports app
+ * shortcuts.</p>
+ *
+ * <p>The types of shortcuts that you create for your app depend on the app's key use cases. For
+ * example, an email app may publish the "compose new email" shortcut, which allows the app to
+ * directly open the compose activity.</p>
+ *
+ * <p class="note"><b>Note:</b> Only main activities—activities that handle the
+ * {@link Intent#ACTION_MAIN} action and the {@link Intent#CATEGORY_LAUNCHER} category—can
+ * have shortcuts. If an app has multiple main activities, you need to define the set of shortcuts
+ * for <em>each</em> activity.
*
* <p>This page discusses the implementation details of the <code>ShortcutManager</code> class. For
- * guidance on performing operations on app shortcuts within your app, see the
- * <a href="/guide/topics/ui/shortcuts.html">App Shortcuts</a> feature guide.
+ * definitions of key terms and guidance on performing operations on shortcuts within your app, see
+ * the <a href="/guide/topics/ui/shortcuts.html">App Shortcuts</a> feature guide.
*
* <h3>Shortcut characteristics</h3>
*
@@ -69,8 +80,8 @@
* <ul>
* <li>The user removes it.
* <li>The publisher app associated with the shortcut is uninstalled.
- * <li>The user performs the clear data action on the publisher app from the device's
- * <b>Settings</b> app.
+ * <li>The user selects <b>Clear data</b> from the publisher app's <i>Storage</i> screen, within
+ * the system's <b>Settings</b> app.
* </ul>
*
* <p>Because the system performs
@@ -84,12 +95,15 @@
* <p>When the launcher displays an app's shortcuts, they should appear in the following order:
*
* <ul>
- * <li>Static shortcuts (if {@link ShortcutInfo#isDeclaredInManifest()} is {@code true}),
- * and then show dynamic shortcuts (if {@link ShortcutInfo#isDynamic()} is {@code true}).
- * <li>Within each shortcut type (static and dynamic), sort the shortcuts in order of increasing
+ * <li>Static shortcuts—shortcuts whose {@link ShortcutInfo#isDeclaredInManifest()} method
+ * returns {@code true}—followed by dynamic shortcuts—shortcuts whose
+ * {@link ShortcutInfo#isDynamic()} method returns {@code true}.
+ * <li>Within each shortcut type (static and dynamic), shortcuts are sorted in order of increasing
* rank according to {@link ShortcutInfo#getRank()}.
* </ul>
*
+ * <h4>Shortcut ranks</h4>
+ *
* <p>Shortcut ranks are non-negative, sequential integers that determine the order in which
* shortcuts appear, assuming that the shortcuts are all in the same category. You can update ranks
* of existing shortcuts when you call {@link #updateShortcuts(List)},
@@ -103,64 +117,99 @@
*
* <h3>Options for static shortcuts</h3>
*
- * The following list includes descriptions for the different attributes within a static shortcut:
+ * The following list includes descriptions for the different attributes within a static shortcut.
+ * You must provide a value for {@code android:shortcutId}, {@code android:shortcutShortLabel}; all
+ * other values are optional.
+ *
* <dl>
* <dt>{@code android:shortcutId}</dt>
- * <dd>Mandatory shortcut ID.
- * <p>
- * This must be a string literal.
- * A resource string, such as <code>@string/foo</code>, cannot be used.
+ * <dd><p>A string literal, which represents the shortcut when a {@code ShortcutManager} object
+ * performs operations on it.</p>
+ * <p class="note"><b>Note: </b>You cannot set this attribute's value to a resource string, such
+ * as <code>@string/foo</code>.</p>
* </dd>
*
* <dt>{@code android:enabled}</dt>
- * <dd>Default is {@code true}. Can be set to {@code false} in order
- * to disable a static shortcut that was published in a previous version and set a custom
- * disabled message. If a custom disabled message is not needed, then a static shortcut can
- * be simply removed from the XML file rather than keeping it with {@code enabled="false"}.</dd>
+ * <dd><p>Whether the user can interact with the shortcut from a supported launcher.</p>
+ * <p>The default value is {@code true}. If you set it to {@code false}, you should also set
+ * {@code android:shortcutDisabledMessage} to a message that explains why you've disabled the
+ * shortcut. If you don't think you need to provide such a message, it's easiest to just remove
+ * the shortcut from the XML file entirely, rather than changing the values of its
+ * {@code android:enabled} and {@code android:shortcutDisabledMessage} attributes.
+ * </dd>
*
* <dt>{@code android:icon}</dt>
- * <dd>Shortcut icon.</dd>
+ * <dd><p>The <a href="/topic/performance/graphics/index.html">bitmap</a> or
+ * <a href="/guide/practices/ui_guidelines/icon_design_adaptive.html">adaptive icon</a> that the
+ * launcher uses when displaying the shortcut to the user. This value can be either the path to an
+ * image or the resource file that contains the image. Use adaptive icons whenever possible to
+ * improve performance and consistency.</p>
+ * <p class="note"><b>Note: </b>Shortcut icons cannot include
+ * <a href="/training/material/drawables.html#DrawableTint">tints</a>.
+ * </dd>
*
* <dt>{@code android:shortcutShortLabel}</dt>
- * <dd>Mandatory shortcut short label.
- * See {@link ShortcutInfo.Builder#setShortLabel(CharSequence)}.
- * <p>
- * This must be a resource string, such as <code>@string/shortcut_label</code>.
+ * <dd><p>A concise phrase that describes the shortcut's purpose. For more information, see
+ * {@link ShortcutInfo.Builder#setShortLabel(CharSequence)}.</p>
+ * <p class="note"><b>Note: </b>This attribute's value must be a resource string, such as
+ * <code>@string/shortcut_label</code>.</p>
* </dd>
*
* <dt>{@code android:shortcutLongLabel}</dt>
- * <dd>Shortcut long label.
- * See {@link ShortcutInfo.Builder#setLongLabel(CharSequence)}.
- * <p>
- * This must be a resource string, such as <code>@string/shortcut_long_label</code>.
+ * <dd><p>An extended phrase that describes the shortcut's purpose. If there's enough space, the
+ * launcher displays this value instead of {@code android:shortcutShortLabel}. For more
+ * information, see {@link ShortcutInfo.Builder#setLongLabel(CharSequence)}.</p>
+ * <p class="note"><b>Note: </b>This attribute's value must be a resource string, such as
+ * <code>@string/shortcut_long_label</code>.</p>
* </dd>
*
* <dt>{@code android:shortcutDisabledMessage}</dt>
- * <dd>When {@code android:enabled} is set to
- * {@code false}, this attribute is used to display a custom disabled message.
- * <p>
- * This must be a resource string, such as <code>@string/shortcut_disabled_message</code>.
+ * <dd><p>The message that appears in a supported launcher when the user attempts to launch a
+ * disabled shortcut. This attribute's value has no effect if {@code android:enabled} is
+ * {@code true}. The message should explain to the user why the shortcut is now disabled.</p>
+ * <p class="note"><b>Note: </b>This attribute's value must be a resource string, such as
+ * <code>@string/shortcut_disabled_message</code>.</p>
* </dd>
+ * </dl>
*
+ * <h3>Inner elements that define static shortcuts</h3>
+ *
+ * <p>The XML file that lists an app's static shortcuts supports the following elements inside each
+ * {@code <shortcut>} element. You must include an {@code intent} inner element for each
+ * static shortcut that you define.</p>
+ *
+ * <dl>
* <dt>{@code intent}</dt>
- * <dd>Intent to launch when the user selects the shortcut.
- * {@code android:action} is mandatory.
- * See <a href="{@docRoot}guide/topics/ui/settings.html#Intents">Using intents</a> for the
- * other supported tags.
+ * <dd><p>The action that the system launches when the user selects the shortcut. This intent must
+ * provide a value for the {@code android:action} attribute.</p>
* <p>You can provide multiple intents for a single shortcut so that the last defined activity is
* launched with the other activities in the
* <a href="/guide/components/tasks-and-back-stack.html">back stack</a>. See
- * {@link android.app.TaskStackBuilder} for details.
- * <p><b>Note:</b> String resources may not be used within an {@code <intent>} element.
+ * <a href="/guide/topics/ui/shortcuts.html#static">Using Static Shortcuts</a> and the
+ * {@link android.app.TaskStackBuilder} class reference for details.</p>
+ * <p class="note"><b>Note:</b> This {@code intent} element cannot include string resources.</p>
+ * <p>For more information, see
+ * <a href="{@docRoot}guide/topics/ui/settings.html#Intents">Using intents</a>.</p>
* </dd>
+ *
* <dt>{@code categories}</dt>
- * <dd>Specify shortcut categories. Currently only
- * {@link ShortcutInfo#SHORTCUT_CATEGORY_CONVERSATION} is defined in the framework.
+ * <dd><p>Provides a grouping for the types of actions that your app's shortcuts perform, such as
+ * creating new chat messages.</p>
+ * <p>For a list of supported shortcut categories, see the {@link ShortcutInfo} class reference
+ * for a list of supported shortcut categories.
* </dd>
* </dl>
*
* <h3>Updating shortcuts</h3>
*
+ * <p>Each app's launcher icon can contain at most {@link #getMaxShortcutCountPerActivity()} number
+ * of static and dynamic shortcuts combined. There is no limit to the number of pinned shortcuts
+ * that an app can create, though.
+ *
+ * <p>When a dynamic shortcut is pinned, even when the publisher removes it as a dynamic shortcut,
+ * the pinned shortcut is still visible and launchable. This allows an app to have more than
+ * {@link #getMaxShortcutCountPerActivity()} number of shortcuts.
+ *
* <p>As an example, suppose {@link #getMaxShortcutCountPerActivity()} is 5:
* <ol>
* <li>A chat app publishes 5 dynamic shortcuts for the 5 most recent
@@ -168,18 +217,13 @@
*
* <li>The user pins all 5 of the shortcuts.
*
- * <li>Later, the user has started 3 additional conversations (c6, c7, and c8),
- * so the publisher app
- * re-publishes its dynamic shortcuts. The new dynamic shortcut list is:
- * c4, c5, ..., c8.
- * The publisher app has to remove c1, c2, and c3 because it can't have more than
- * 5 dynamic shortcuts.
- *
- * <li>However, even though c1, c2, and c3 are no longer dynamic shortcuts, the pinned
- * shortcuts for these conversations are still available and launchable.
- *
- * <li>At this point, the user can access a total of 8 shortcuts that link to activities in
- * the publisher app, including the 3 pinned shortcuts, even though an app can have at most 5
+ * <li>Later, the user has started 3 additional conversations (c6, c7, and c8), so the publisher
+ * app re-publishes its dynamic shortcuts. The new dynamic shortcut list is: c4, c5, ..., c8.
+ * <p>The publisher app has to remove c1, c2, and c3 because it can't have more than 5 dynamic
+ * shortcuts. However, c1, c2, and c3 are still pinned shortcuts that the user can access and
+ * launch.
+ * <p>At this point, the user can access a total of 8 shortcuts that link to activities in the
+ * publisher app, including the 3 pinned shortcuts, even though an app can have at most 5
* dynamic shortcuts.
*
* <li>The app can use {@link #updateShortcuts(List)} to update <em>any</em> of the existing
@@ -196,44 +240,23 @@
* Dynamic shortcuts can be published with any set of {@link Intent#addFlags Intent} flags.
* Typically, {@link Intent#FLAG_ACTIVITY_CLEAR_TASK} is specified, possibly along with other
* flags; otherwise, if the app is already running, the app is simply brought to
- * the foreground, and the target activity may not appear.
+ * the foreground, and the target activity might not appear.
*
* <p>Static shortcuts <b>cannot</b> have custom intent flags.
* The first intent of a static shortcut will always have {@link Intent#FLAG_ACTIVITY_NEW_TASK}
* and {@link Intent#FLAG_ACTIVITY_CLEAR_TASK} set. This means, when the app is already running, all
- * the existing activities in your app will be destroyed when a static shortcut is launched.
+ * the existing activities in your app are destroyed when a static shortcut is launched.
* If this behavior is not desirable, you can use a <em>trampoline activity</em>, or an invisible
* activity that starts another activity in {@link Activity#onCreate}, then calls
* {@link Activity#finish()}:
* <ol>
* <li>In the <code>AndroidManifest.xml</code> file, the trampoline activity should include the
* attribute assignment {@code android:taskAffinity=""}.
- * <li>In the shortcuts resource file, the intent within the static shortcut should point at
+ * <li>In the shortcuts resource file, the intent within the static shortcut should reference
* the trampoline activity.
* </ol>
*
- * <h3>Handling system locale changes</h3>
- *
- * <p>Apps should update dynamic and pinned shortcuts when the system locale changes using the
- * {@link Intent#ACTION_LOCALE_CHANGED} broadcast. When the system locale changes,
- * <a href="/guide/topics/ui/shortcuts.html#rate-limit">rate limiting</a> is reset, so even
- * background apps can add and update dynamic shortcuts until the rate limit is reached again.
- *
- * <h3>Shortcut limits</h3>
- *
- * <p>Only main activities—activities that handle the {@code MAIN} action and the
- * {@code LAUNCHER} category—can have shortcuts. If an app has multiple main activities, you
- * need to define the set of shortcuts for <em>each</em> activity.
- *
- * <p>Each launcher icon can have at most {@link #getMaxShortcutCountPerActivity()} number of
- * static and dynamic shortcuts combined. There is no limit to the number of pinned shortcuts that
- * an app can create.
- *
- * <p>When a dynamic shortcut is pinned, even when the publisher removes it as a dynamic shortcut,
- * the pinned shortcut is still visible and launchable. This allows an app to have more than
- * {@link #getMaxShortcutCountPerActivity()} number of shortcuts.
- *
- * <h4>Rate limiting</h4>
+ * <h3>Rate limiting</h3>
*
* <p>When <a href="/guide/topics/ui/shortcuts.html#rate-limit">rate limiting</a> is active,
* {@link #isRateLimitingActive()} returns {@code true}.
@@ -243,8 +266,20 @@
* <ul>
* <li>An app comes to the foreground.
* <li>The system locale changes.
- * <li>The user performs the <strong>inline reply</strong> action on a notification.
+ * <li>The user performs the <a href="/guide/topics/ui/notifiers/notifications.html#direct">inline
+ * reply</a> action on a notification.
* </ul>
+ *
+ * <h3>Handling system locale changes</h3>
+ *
+ * <p>Apps should update dynamic and pinned shortcuts when they receive the
+ * {@link Intent#ACTION_LOCALE_CHANGED} broadcast, indicating that the system locale has changed.
+ * <p>When the system locale changes, <a href="/guide/topics/ui/shortcuts.html#rate-limit">rate
+ * limiting</a> is reset, so even background apps can add and update dynamic shortcuts until the
+ * rate limit is reached again.
+ *
+ * <h3>Retrieving class instances</h3>
+ * <!-- Provides a heading for the content filled in by the @SystemService annotation below -->
*/
@SystemService(Context.SHORTCUT_SERVICE)
public class ShortcutManager {
diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java
index 73a9478..75c1000 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -65,18 +65,14 @@
* v2 stripping rollback protection, or verify integrity of the APK.
*
* @throws PackageParserException if the APK's signature failed to verify.
- * @throws SignatureNotFoundException if a signature corresponding to minLevel or greater
- * is not found, except in the case of no JAR signature.
*/
public static Result verify(String apkPath, int minSignatureSchemeVersion, boolean systemDir)
- throws PackageParserException, SignatureNotFoundException {
- boolean verified = false;
- Certificate[][] signerCerts;
- int level = VERSION_APK_SIGNATURE_SCHEME_V2;
+ throws PackageParserException {
// first try v2
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV2");
try {
+ Certificate[][] signerCerts;
signerCerts = ApkSignatureSchemeV2Verifier.verify(apkPath);
Signature[] signerSigs = convertToSignatures(signerCerts);
@@ -93,12 +89,12 @@
} finally {
closeQuietly(jarFile);
}
- return new Result(signerCerts, signerSigs);
+ return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V2);
} catch (SignatureNotFoundException e) {
// not signed with v2, try older if allowed
if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V2) {
- throw new SignatureNotFoundException(
- "No APK Signature Scheme v2 signature found for " + apkPath, e);
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "No APK Signature Scheme v2 signature in package " + apkPath, e);
}
} catch (PackageParserException e) {
// preserve any new exceptions explicitly thrown here
@@ -178,7 +174,7 @@
}
}
}
- return new Result(lastCerts, lastSigs);
+ return new Result(lastCerts, lastSigs, VERSION_JAR_SIGNATURE_SCHEME);
} catch (GeneralSecurityException e) {
throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING,
"Failed to collect certificates from " + apkPath, e);
@@ -254,10 +250,12 @@
public static class Result {
public final Certificate[][] certs;
public final Signature[] sigs;
+ public final int signatureSchemeVersion;
- public Result(Certificate[][] certs, Signature[] sigs) {
+ public Result(Certificate[][] certs, Signature[] sigs, int signingVersion) {
this.certs = certs;
this.sigs = sigs;
+ this.signatureSchemeVersion = signingVersion;
}
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3f8da093..2c82ac4 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -327,6 +327,8 @@
// This is used to reduce the race between window focus changes being dispatched from
// the window manager and input events coming through the input system.
@GuardedBy("this")
+ boolean mWindowFocusChanged;
+ @GuardedBy("this")
boolean mUpcomingWindowFocus;
@GuardedBy("this")
boolean mUpcomingInTouchMode;
@@ -2472,14 +2474,14 @@
final boolean hasWindowFocus;
final boolean inTouchMode;
synchronized (this) {
+ if (!mWindowFocusChanged) {
+ return;
+ }
+ mWindowFocusChanged = false;
hasWindowFocus = mUpcomingWindowFocus;
inTouchMode = mUpcomingInTouchMode;
}
- if (mAttachInfo.mHasWindowFocus == hasWindowFocus) {
- return;
- }
-
if (mAdded) {
profileRendering(hasWindowFocus);
@@ -7181,6 +7183,7 @@
public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
synchronized (this) {
+ mWindowFocusChanged = true;
mUpcomingWindowFocus = hasFocus;
mUpcomingInTouchMode = inTouchMode;
}
diff --git a/core/res/res/interpolator/aggressive_ease.xml b/core/res/res/interpolator/aggressive_ease.xml
new file mode 100644
index 0000000..620424f
--- /dev/null
+++ b/core/res/res/interpolator/aggressive_ease.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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
+ -->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:controlX1="0.2"
+ android:controlY1="0"
+ android:controlX2="0"
+ android:controlY2="1"/>
\ No newline at end of file
diff --git a/core/res/res/interpolator/emphasized_deceleration.xml b/core/res/res/interpolator/emphasized_deceleration.xml
new file mode 100644
index 0000000..60c315c
--- /dev/null
+++ b/core/res/res/interpolator/emphasized_deceleration.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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
+ -->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:controlX1="0.1"
+ android:controlY1="0.8"
+ android:controlX2="0.2"
+ android:controlY2="1"/>
\ No newline at end of file
diff --git a/core/res/res/interpolator/exaggerated_ease.xml b/core/res/res/interpolator/exaggerated_ease.xml
new file mode 100644
index 0000000..4961c1c
--- /dev/null
+++ b/core/res/res/interpolator/exaggerated_ease.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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
+ -->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:pathData="M 0,0 C 0.05, 0, 0.133333, 0.08, 0.166666, 0.4 C 0.225, 0.94, 0.25, 1, 1, 1"/>
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index fa2499f..5c73d54 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -21,6 +21,9 @@
import android.app.Application;
import android.app.usage.StorageStats;
import android.app.usage.StorageStatsManager;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleObserver;
+import android.arch.lifecycle.OnLifecycleEvent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -52,11 +55,6 @@
import com.android.internal.R;
import com.android.internal.util.ArrayUtils;
-import com.android.settingslib.core.lifecycle.Lifecycle;
-import com.android.settingslib.core.lifecycle.LifecycleObserver;
-import com.android.settingslib.core.lifecycle.events.OnDestroy;
-import com.android.settingslib.core.lifecycle.events.OnPause;
-import com.android.settingslib.core.lifecycle.events.OnResume;
import java.io.File;
import java.io.IOException;
@@ -595,7 +593,7 @@
.replaceAll("").toLowerCase();
}
- public class Session implements LifecycleObserver, OnPause, OnResume, OnDestroy {
+ public class Session implements LifecycleObserver {
final Callbacks mCallbacks;
boolean mResumed;
@@ -621,6 +619,7 @@
}
}
+ @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
public void onResume() {
if (DEBUG_LOCKING) Log.v(TAG, "resume about to acquire lock...");
synchronized (mEntriesMap) {
@@ -633,6 +632,7 @@
if (DEBUG_LOCKING) Log.v(TAG, "...resume releasing lock");
}
+ @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
public void onPause() {
if (DEBUG_LOCKING) Log.v(TAG, "pause about to acquire lock...");
synchronized (mEntriesMap) {
@@ -752,6 +752,7 @@
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
}
+ @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
public void onDestroy() {
if (!mHasLifecycle) {
// TODO: Legacy, remove this later once all usages are switched to Lifecycle
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java
new file mode 100644
index 0000000..2b6d09f
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2017 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.settingslib.fuelgauge;
+
+import android.os.IDeviceIdleController;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.support.annotation.VisibleForTesting;
+import android.util.ArraySet;
+import android.util.Log;
+
+/**
+ * Handles getting/changing the whitelist for the exceptions to battery saving features.
+ */
+public class PowerWhitelistBackend {
+
+ private static final String TAG = "PowerWhitelistBackend";
+
+ private static final String DEVICE_IDLE_SERVICE = "deviceidle";
+
+ private static PowerWhitelistBackend sInstance;
+
+ private final IDeviceIdleController mDeviceIdleService;
+ private final ArraySet<String> mWhitelistedApps = new ArraySet<>();
+ private final ArraySet<String> mSysWhitelistedApps = new ArraySet<>();
+
+ public PowerWhitelistBackend() {
+ mDeviceIdleService = IDeviceIdleController.Stub.asInterface(
+ ServiceManager.getService(DEVICE_IDLE_SERVICE));
+ refreshList();
+ }
+
+ @VisibleForTesting
+ PowerWhitelistBackend(IDeviceIdleController deviceIdleService) {
+ mDeviceIdleService = deviceIdleService;
+ refreshList();
+ }
+
+ public int getWhitelistSize() {
+ return mWhitelistedApps.size();
+ }
+
+ public boolean isSysWhitelisted(String pkg) {
+ return mSysWhitelistedApps.contains(pkg);
+ }
+
+ public boolean isWhitelisted(String pkg) {
+ return mWhitelistedApps.contains(pkg);
+ }
+
+ public void addApp(String pkg) {
+ try {
+ mDeviceIdleService.addPowerSaveWhitelistApp(pkg);
+ mWhitelistedApps.add(pkg);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to reach IDeviceIdleController", e);
+ }
+ }
+
+ public void removeApp(String pkg) {
+ try {
+ mDeviceIdleService.removePowerSaveWhitelistApp(pkg);
+ mWhitelistedApps.remove(pkg);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to reach IDeviceIdleController", e);
+ }
+ }
+
+ @VisibleForTesting
+ public void refreshList() {
+ mSysWhitelistedApps.clear();
+ mWhitelistedApps.clear();
+ try {
+ String[] whitelistedApps = mDeviceIdleService.getFullPowerWhitelist();
+ for (String app : whitelistedApps) {
+ mWhitelistedApps.add(app);
+ }
+ String[] sysWhitelistedApps = mDeviceIdleService.getSystemPowerWhitelist();
+ for (String app : sysWhitelistedApps) {
+ mSysWhitelistedApps.add(app);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to reach IDeviceIdleController", e);
+ }
+ }
+
+ public static PowerWhitelistBackend getInstance() {
+ if (sInstance == null) {
+ sInstance = new PowerWhitelistBackend();
+ }
+ return sInstance;
+ }
+
+}
diff --git a/packages/SettingsLib/tests/robotests/Android.mk b/packages/SettingsLib/tests/robotests/Android.mk
index 2738027..02a4973 100644
--- a/packages/SettingsLib/tests/robotests/Android.mk
+++ b/packages/SettingsLib/tests/robotests/Android.mk
@@ -49,7 +49,7 @@
LOCAL_JAVA_LIBRARIES := \
junit \
- platform-robolectric-3.4.2-prebuilt
+ platform-robolectric-3.5.1-prebuilt
LOCAL_INSTRUMENTATION_FOR := SettingsLibShell
LOCAL_MODULE := SettingsLibRoboTests
@@ -74,4 +74,4 @@
LOCAL_ROBOTEST_TIMEOUT := 36000
-include prebuilts/misc/common/robolectric/3.4.2/run_robotests.mk
+include prebuilts/misc/common/robolectric/3.5.1/run_robotests.mk
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingsLibRobolectricTestRunner.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingsLibRobolectricTestRunner.java
index 698e442..df850be 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingsLibRobolectricTestRunner.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingsLibRobolectricTestRunner.java
@@ -38,32 +38,25 @@
final String resDir = appRoot + "/tests/robotests/res";
final String assetsDir = appRoot + config.assetDir();
- final AndroidManifest manifest = new AndroidManifest(Fs.fileFromPath(manifestPath),
- Fs.fileFromPath(resDir), Fs.fileFromPath(assetsDir)) {
+ return new AndroidManifest(Fs.fileFromPath(manifestPath), Fs.fileFromPath(resDir),
+ Fs.fileFromPath(assetsDir), "com.android.settingslib") {
@Override
public List<ResourcePath> getIncludedResourcePaths() {
List<ResourcePath> paths = super.getIncludedResourcePaths();
- SettingsLibRobolectricTestRunner.getIncludedResourcePaths(getPackageName(), paths);
+ paths.add(new ResourcePath(
+ null,
+ Fs.fileFromPath("./frameworks/base/packages/SettingsLib/res"),
+ null));
+ paths.add(new ResourcePath(
+ null,
+ Fs.fileFromPath("./frameworks/base/core/res/res"),
+ null));
+ paths.add(new ResourcePath(
+ null,
+ Fs.fileFromPath("./frameworks/support/v7/appcompat/res"),
+ null));
return paths;
}
};
- manifest.setPackageName("com.android.settingslib");
- return manifest;
}
-
- static void getIncludedResourcePaths(String packageName, List<ResourcePath> paths) {
- paths.add(new ResourcePath(
- null,
- Fs.fileFromPath("./frameworks/base/packages/SettingsLib/res"),
- null));
- paths.add(new ResourcePath(
- null,
- Fs.fileFromPath("./frameworks/base/core/res/res"),
- null));
- paths.add(new ResourcePath(
- null,
- Fs.fileFromPath("./frameworks/support/v7/appcompat/res"),
- null));
- }
-
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java
new file mode 100644
index 0000000..fc0019d
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2017 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.settingslib.fuelgauge;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.os.IDeviceIdleController;
+
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class PowerWhitelistBackendTest {
+
+ private static final String PACKAGE_ONE = "com.example.packageone";
+ private static final String PACKAGE_TWO = "com.example.packagetwo";
+
+ private PowerWhitelistBackend mPowerWhitelistBackend;
+ @Mock
+ private IDeviceIdleController mDeviceIdleService;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ doReturn(new String[] {}).when(mDeviceIdleService).getFullPowerWhitelist();
+ doReturn(new String[] {}).when(mDeviceIdleService).getSystemPowerWhitelist();
+ doNothing().when(mDeviceIdleService).addPowerSaveWhitelistApp(anyString());
+ doNothing().when(mDeviceIdleService).removePowerSaveWhitelistApp(anyString());
+ mPowerWhitelistBackend = new PowerWhitelistBackend(mDeviceIdleService);
+ }
+
+ @Test
+ public void testIsWhitelisted() throws Exception {
+ doReturn(new String[] {PACKAGE_ONE}).when(mDeviceIdleService).getFullPowerWhitelist();
+ mPowerWhitelistBackend.refreshList();
+
+ assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_ONE)).isTrue();
+ assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_TWO)).isFalse();
+
+ mPowerWhitelistBackend.addApp(PACKAGE_TWO);
+
+ verify(mDeviceIdleService, atLeastOnce()).addPowerSaveWhitelistApp(PACKAGE_TWO);
+ assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_ONE)).isTrue();
+ assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_TWO)).isTrue();
+
+ mPowerWhitelistBackend.removeApp(PACKAGE_TWO);
+
+ verify(mDeviceIdleService, atLeastOnce()).removePowerSaveWhitelistApp(PACKAGE_TWO);
+ assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_ONE)).isTrue();
+ assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_TWO)).isFalse();
+
+ mPowerWhitelistBackend.removeApp(PACKAGE_ONE);
+
+ verify(mDeviceIdleService, atLeastOnce()).removePowerSaveWhitelistApp(PACKAGE_ONE);
+ assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_ONE)).isFalse();
+ assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_TWO)).isFalse();
+ }
+
+ @Test
+ public void testIsSystemWhitelisted() throws Exception {
+ doReturn(new String[] {PACKAGE_ONE}).when(mDeviceIdleService).getSystemPowerWhitelist();
+ mPowerWhitelistBackend.refreshList();
+
+ assertThat(mPowerWhitelistBackend.isSysWhitelisted(PACKAGE_ONE)).isTrue();
+ assertThat(mPowerWhitelistBackend.isSysWhitelisted(PACKAGE_TWO)).isFalse();
+ assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_ONE)).isFalse();
+
+ }
+}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index efa0bf8..2d7a6ad 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3884,7 +3884,8 @@
IsInCall = telecomManager.isInCall();
Binder.restoreCallingIdentity(ident);
- return (IsInCall || getMode() == AudioManager.MODE_IN_COMMUNICATION);
+ return (IsInCall || getMode() == AudioManager.MODE_IN_COMMUNICATION ||
+ getMode() == AudioManager.MODE_IN_CALL);
}
/**
diff --git a/services/core/java/com/android/server/location/ContextHubService.java b/services/core/java/com/android/server/location/ContextHubService.java
index 1ad0cf9..b5fc9d0 100644
--- a/services/core/java/com/android/server/location/ContextHubService.java
+++ b/services/core/java/com/android/server/location/ContextHubService.java
@@ -269,13 +269,9 @@
checkPermissions();
int[] returnArray = new int[mContextHubInfo.length];
- Log.d(TAG, "System supports " + returnArray.length + " hubs");
for (int i = 0; i < returnArray.length; ++i) {
returnArray[i] = i;
- Log.d(TAG, String.format("Hub %s is mapped to %d",
- mContextHubInfo[i].getName(), returnArray[i]));
}
-
return returnArray;
}
@@ -425,12 +421,6 @@
for (int i = 0; i < foundInstances.size(); i++) {
retArray[i] = foundInstances.get(i).intValue();
}
-
- if (retArray.length == 0) {
- Log.d(TAG, "No nanoapps found on hub ID " + hubHandle + " using NanoAppFilter: "
- + filter);
- }
-
return retArray;
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformEncryptionKey.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformEncryptionKey.java
deleted file mode 100644
index 38f5b45..0000000
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformEncryptionKey.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2017 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.locksettings.recoverablekeystore;
-
-import android.security.keystore.AndroidKeyStoreSecretKey;
-
-/**
- * Private key stored in AndroidKeyStore. Used to wrap recoverable keys before writing them to disk.
- *
- * <p>Identified by a generation ID, which increments whenever a new platform key is generated. A
- * new key must be generated whenever the user disables their lock screen, as the decryption key is
- * tied to lock-screen authentication.
- *
- * <p>One current platform key exists per profile on the device. (As each must be tied to a
- * different user's lock screen.)
- *
- * @hide
- */
-public class PlatformEncryptionKey {
-
- private final int mGenerationId;
- private final AndroidKeyStoreSecretKey mKey;
-
- /**
- * A new instance.
- *
- * @param generationId The generation ID of the key.
- * @param key The secret key handle. Can be used to encrypt WITHOUT requiring screen unlock.
- */
- public PlatformEncryptionKey(int generationId, AndroidKeyStoreSecretKey key) {
- mGenerationId = generationId;
- mKey = key;
- }
-
- /**
- * Returns the generation ID of the key.
- */
- public int getGenerationId() {
- return mGenerationId;
- }
-
- /**
- * Returns the actual key, which can only be used to encrypt.
- */
- public AndroidKeyStoreSecretKey getKey() {
- return mKey;
- }
-}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java
index b22ba4e..40c7889 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java
@@ -16,6 +16,7 @@
package com.android.server.locksettings.recoverablekeystore;
+import android.security.keystore.AndroidKeyStoreSecretKey;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.util.Log;
@@ -55,7 +56,7 @@
* @hide
*/
public static RecoverableKeyGenerator newInstance(
- PlatformEncryptionKey platformKey, RecoverableKeyStorage recoverableKeyStorage)
+ AndroidKeyStoreSecretKey platformKey, RecoverableKeyStorage recoverableKeyStorage)
throws NoSuchAlgorithmException {
// NB: This cannot use AndroidKeyStore as the provider, as we need access to the raw key
// material, so that it can be synced to disk in encrypted form.
@@ -65,11 +66,11 @@
private final KeyGenerator mKeyGenerator;
private final RecoverableKeyStorage mRecoverableKeyStorage;
- private final PlatformEncryptionKey mPlatformKey;
+ private final AndroidKeyStoreSecretKey mPlatformKey;
private RecoverableKeyGenerator(
KeyGenerator keyGenerator,
- PlatformEncryptionKey platformKey,
+ AndroidKeyStoreSecretKey platformKey,
RecoverableKeyStorage recoverableKeyStorage) {
mKeyGenerator = keyGenerator;
mRecoverableKeyStorage = recoverableKeyStorage;
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java
index a0e34c3..f18e796 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java
@@ -44,7 +44,6 @@
private static final String APPLICATION_KEY_ALGORITHM = "AES";
private static final int GCM_TAG_LENGTH_BITS = 128;
- private final int mPlatformKeyGenerationId;
private final byte[] mNonce;
private final byte[] mKeyMaterial;
@@ -56,8 +55,8 @@
* {@link android.security.keystore.AndroidKeyStoreKey} for an example of a key that does
* not expose its key material.
*/
- public static WrappedKey fromSecretKey(PlatformEncryptionKey wrappingKey, SecretKey key)
- throws InvalidKeyException, KeyStoreException {
+ public static WrappedKey fromSecretKey(
+ SecretKey wrappingKey, SecretKey key) throws InvalidKeyException, KeyStoreException {
if (key.getEncoded() == null) {
throw new InvalidKeyException(
"key does not expose encoded material. It cannot be wrapped.");
@@ -71,7 +70,7 @@
"Android does not support AES/GCM/NoPadding. This should never happen.");
}
- cipher.init(Cipher.WRAP_MODE, wrappingKey.getKey());
+ cipher.init(Cipher.WRAP_MODE, wrappingKey);
byte[] encryptedKeyMaterial;
try {
encryptedKeyMaterial = cipher.wrap(key);
@@ -91,10 +90,7 @@
}
}
- return new WrappedKey(
- /*nonce=*/ cipher.getIV(),
- /*keyMaterial=*/ encryptedKeyMaterial,
- /*platformKeyGenerationId=*/ wrappingKey.getGenerationId());
+ return new WrappedKey(/*mNonce=*/ cipher.getIV(), /*mKeyMaterial=*/ encryptedKeyMaterial);
}
/**
@@ -102,14 +98,12 @@
*
* @param nonce The nonce with which the key material was encrypted.
* @param keyMaterial The encrypted bytes of the key material.
- * @param platformKeyGenerationId The generation ID of the key used to wrap this key.
*
* @hide
*/
- public WrappedKey(byte[] nonce, byte[] keyMaterial, int platformKeyGenerationId) {
+ public WrappedKey(byte[] nonce, byte[] keyMaterial) {
mNonce = nonce;
mKeyMaterial = keyMaterial;
- mPlatformKeyGenerationId = platformKeyGenerationId;
}
/**
@@ -130,13 +124,15 @@
return mKeyMaterial;
}
+
/**
* Returns the generation ID of the platform key, with which this key was wrapped.
*
* @hide
*/
public int getPlatformKeyGenerationId() {
- return mPlatformKeyGenerationId;
+ // TODO(robertberry) Implement. See ag/3362855.
+ return 1;
}
/**
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
new file mode 100644
index 0000000..79bf5aa
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2017 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.locksettings.recoverablekeystore.storage;
+
+import android.annotation.Nullable;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.util.Log;
+
+import com.android.server.locksettings.recoverablekeystore.WrappedKey;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Database of recoverable key information.
+ *
+ * @hide
+ */
+public class RecoverableKeyStoreDb {
+ private static final String TAG = "RecoverableKeyStoreDb";
+ private static final int IDLE_TIMEOUT_SECONDS = 30;
+
+ private final RecoverableKeyStoreDbHelper mKeyStoreDbHelper;
+
+ /**
+ * A new instance, storing the database in the user directory of {@code context}.
+ *
+ * @hide
+ */
+ public static RecoverableKeyStoreDb newInstance(Context context) {
+ RecoverableKeyStoreDbHelper helper = new RecoverableKeyStoreDbHelper(context);
+ helper.setWriteAheadLoggingEnabled(true);
+ helper.setIdleConnectionTimeout(IDLE_TIMEOUT_SECONDS);
+ return new RecoverableKeyStoreDb(helper);
+ }
+
+ private RecoverableKeyStoreDb(RecoverableKeyStoreDbHelper keyStoreDbHelper) {
+ this.mKeyStoreDbHelper = keyStoreDbHelper;
+ }
+
+ /**
+ * Inserts a key into the database.
+ *
+ * @param uid Uid of the application to whom the key belongs.
+ * @param alias The alias of the key in the AndroidKeyStore.
+ * @param wrappedKey The wrapped bytes of the key.
+ * @param generationId The generation ID of the platform key that wrapped the key.
+ * @return The primary key of the inserted row, or -1 if failed.
+ *
+ * @hide
+ */
+ public long insertKey(int uid, String alias, WrappedKey wrappedKey, int generationId) {
+ SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(KeysEntry.COLUMN_NAME_UID, uid);
+ values.put(KeysEntry.COLUMN_NAME_ALIAS, alias);
+ values.put(KeysEntry.COLUMN_NAME_NONCE, wrappedKey.getNonce());
+ values.put(KeysEntry.COLUMN_NAME_WRAPPED_KEY, wrappedKey.getKeyMaterial());
+ values.put(KeysEntry.COLUMN_NAME_LAST_SYNCED_AT, -1);
+ values.put(KeysEntry.COLUMN_NAME_GENERATION_ID, generationId);
+ return db.replace(KeysEntry.TABLE_NAME, /*nullColumnHack=*/ null, values);
+ }
+
+ /**
+ * Gets the key with {@code alias} for the app with {@code uid}.
+ *
+ * @hide
+ */
+ @Nullable public WrappedKey getKey(int uid, String alias) {
+ SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
+ String[] projection = {
+ KeysEntry._ID,
+ KeysEntry.COLUMN_NAME_NONCE,
+ KeysEntry.COLUMN_NAME_WRAPPED_KEY,
+ KeysEntry.COLUMN_NAME_GENERATION_ID};
+ String selection =
+ KeysEntry.COLUMN_NAME_UID + " = ? AND "
+ + KeysEntry.COLUMN_NAME_ALIAS + " = ?";
+ String[] selectionArguments = { Integer.toString(uid), alias };
+
+ try (
+ Cursor cursor = db.query(
+ KeysEntry.TABLE_NAME,
+ projection,
+ selection,
+ selectionArguments,
+ /*groupBy=*/ null,
+ /*having=*/ null,
+ /*orderBy=*/ null)
+ ) {
+ int count = cursor.getCount();
+ if (count == 0) {
+ return null;
+ }
+ if (count > 1) {
+ Log.wtf(TAG,
+ String.format(Locale.US,
+ "%d WrappedKey entries found for uid=%d alias='%s'. "
+ + "Should only ever be 0 or 1.", count, uid, alias));
+ return null;
+ }
+ cursor.moveToFirst();
+ byte[] nonce = cursor.getBlob(
+ cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_NONCE));
+ byte[] keyMaterial = cursor.getBlob(
+ cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY));
+ return new WrappedKey(nonce, keyMaterial);
+ }
+ }
+
+ /**
+ * Returns all keys for the given {@code uid} and {@code platformKeyGenerationId}.
+ *
+ * @param uid User id of the profile to which all the keys are associated.
+ * @param platformKeyGenerationId The generation ID of the platform key that wrapped these keys.
+ * (i.e., this should be the most recent generation ID, as older platform keys are not
+ * usable.)
+ *
+ * @hide
+ */
+ public Map<String, WrappedKey> getAllKeys(int uid, int platformKeyGenerationId) {
+ SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
+ String[] projection = {
+ KeysEntry._ID,
+ KeysEntry.COLUMN_NAME_NONCE,
+ KeysEntry.COLUMN_NAME_WRAPPED_KEY,
+ KeysEntry.COLUMN_NAME_ALIAS};
+ String selection =
+ KeysEntry.COLUMN_NAME_UID + " = ? AND "
+ + KeysEntry.COLUMN_NAME_GENERATION_ID + " = ?";
+ String[] selectionArguments = {
+ Integer.toString(uid), Integer.toString(platformKeyGenerationId) };
+
+ try (
+ Cursor cursor = db.query(
+ KeysEntry.TABLE_NAME,
+ projection,
+ selection,
+ selectionArguments,
+ /*groupBy=*/ null,
+ /*having=*/ null,
+ /*orderBy=*/ null)
+ ) {
+ HashMap<String, WrappedKey> keys = new HashMap<>();
+ while (cursor.moveToNext()) {
+ byte[] nonce = cursor.getBlob(
+ cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_NONCE));
+ byte[] keyMaterial = cursor.getBlob(
+ cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY));
+ String alias = cursor.getString(
+ cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_ALIAS));
+ keys.put(alias, new WrappedKey(nonce, keyMaterial));
+ }
+ return keys;
+ }
+ }
+
+ /**
+ * Closes all open connections to the database.
+ */
+ public void close() {
+ mKeyStoreDbHelper.close();
+ }
+
+ // TODO: Add method for updating the 'last synced' time.
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
new file mode 100644
index 0000000..c54d0a6
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017 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.locksettings.recoverablekeystore.storage;
+
+import android.provider.BaseColumns;
+
+/**
+ * Contract for recoverable key database. Describes the tables present.
+ */
+class RecoverableKeyStoreDbContract {
+ /**
+ * Table holding wrapped keys, and information about when they were last synced.
+ */
+ static class KeysEntry implements BaseColumns {
+ static final String TABLE_NAME = "keys";
+
+ /**
+ * The uid of the application that generated the key.
+ */
+ static final String COLUMN_NAME_UID = "uid";
+
+ /**
+ * The alias of the key, as set in AndroidKeyStore.
+ */
+ static final String COLUMN_NAME_ALIAS = "alias";
+
+ /**
+ * Nonce with which the key was encrypted.
+ */
+ static final String COLUMN_NAME_NONCE = "nonce";
+
+ /**
+ * Encrypted bytes of the key.
+ */
+ static final String COLUMN_NAME_WRAPPED_KEY = "wrapped_key";
+
+ /**
+ * Generation ID of the platform key that was used to encrypt this key.
+ */
+ static final String COLUMN_NAME_GENERATION_ID = "platform_key_generation_id";
+
+ /**
+ * Timestamp of when this key was last synced with remote storage, or -1 if never synced.
+ */
+ static final String COLUMN_NAME_LAST_SYNCED_AT = "last_synced_at";
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
new file mode 100644
index 0000000..e3783c4
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
@@ -0,0 +1,43 @@
+package com.android.server.locksettings.recoverablekeystore.storage;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry;
+
+/**
+ * Helper for creating the recoverable key database.
+ */
+class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper {
+ private static final int DATABASE_VERSION = 1;
+ private static final String DATABASE_NAME = "recoverablekeystore.db";
+
+ private static final String SQL_CREATE_ENTRIES =
+ "CREATE TABLE " + KeysEntry.TABLE_NAME + "( "
+ + KeysEntry._ID + " INTEGER PRIMARY KEY,"
+ + KeysEntry.COLUMN_NAME_UID + " INTEGER UNIQUE,"
+ + KeysEntry.COLUMN_NAME_ALIAS + " TEXT UNIQUE,"
+ + KeysEntry.COLUMN_NAME_NONCE + " BLOB,"
+ + KeysEntry.COLUMN_NAME_WRAPPED_KEY + " BLOB,"
+ + KeysEntry.COLUMN_NAME_GENERATION_ID + " INTEGER,"
+ + KeysEntry.COLUMN_NAME_LAST_SYNCED_AT + " INTEGER)";
+
+ private static final String SQL_DELETE_ENTRIES =
+ "DROP TABLE IF EXISTS " + KeysEntry.TABLE_NAME;
+
+ RecoverableKeyStoreDbHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL(SQL_CREATE_ENTRIES);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ db.execSQL(SQL_DELETE_ENTRIES);
+ onCreate(db);
+ }
+}
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 0b089fb..384efdd 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -21,6 +21,9 @@
import android.annotation.NonNull;
import android.app.ActivityManager;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -96,21 +99,23 @@
private final ArrayMap<IBinder, ClientRecord> mAllClientRecords =
new ArrayMap<IBinder, ClientRecord>();
private int mCurrentUserId = -1;
- private boolean mGlobalBluetoothA2dpOn = false;
private final IAudioService mAudioService;
private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
private final Handler mHandler = new Handler();
- private final AudioRoutesInfo mAudioRoutesInfo = new AudioRoutesInfo();
private final IntArray mActivePlayerMinPriorityQueue = new IntArray();
private final IntArray mActivePlayerUidMinPriorityQueue = new IntArray();
+ private final BroadcastReceiver mReceiver = new MediaRouterServiceBroadcastReceiver();
+ BluetoothDevice mBluetoothDevice;
+ int mAudioRouteMainType = AudioRoutesInfo.MAIN_SPEAKER;
+ boolean mGlobalBluetoothA2dpOn = false;
+
public MediaRouterService(Context context) {
mContext = context;
Watchdog.getInstance().addMonitor(this);
mAudioService = IAudioService.Stub.asInterface(
ServiceManager.getService(Context.AUDIO_SERVICE));
-
mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
mAudioPlayerStateMonitor.registerListener(
new AudioPlayerStateMonitor.OnAudioPlayerActiveStateChangedListener() {
@@ -170,44 +175,30 @@
@Override
public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
synchronized (mLock) {
- if (newRoutes.mainType != mAudioRoutesInfo.mainType) {
+ if (newRoutes.mainType != mAudioRouteMainType) {
if ((newRoutes.mainType & (AudioRoutesInfo.MAIN_HEADSET
| AudioRoutesInfo.MAIN_HEADPHONES
| AudioRoutesInfo.MAIN_USB)) == 0) {
// headset was plugged out.
- mGlobalBluetoothA2dpOn = newRoutes.bluetoothName != null;
+ mGlobalBluetoothA2dpOn = mBluetoothDevice != null;
} else {
// headset was plugged in.
mGlobalBluetoothA2dpOn = false;
}
- mAudioRoutesInfo.mainType = newRoutes.mainType;
+ mAudioRouteMainType = newRoutes.mainType;
}
- if (!TextUtils.equals(
- newRoutes.bluetoothName, mAudioRoutesInfo.bluetoothName)) {
- if (newRoutes.bluetoothName == null) {
- // BT was disconnected.
- mGlobalBluetoothA2dpOn = false;
- } else {
- // BT was connected or changed.
- mGlobalBluetoothA2dpOn = true;
- }
- mAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName;
- }
- // Although a Bluetooth device is connected before a new audio playback is
- // started, dispatchAudioRoutChanged() can be called after
- // onAudioPlayerActiveStateChanged(). That causes restoreBluetoothA2dp()
- // is called before mGlobalBluetoothA2dpOn is updated.
- // Calling restoreBluetoothA2dp() here could prevent that.
- restoreBluetoothA2dp();
+ // The new audio routes info could be delivered with several seconds delay.
+ // In order to avoid such delay, Bluetooth device info will be updated
+ // via MediaRouterServiceBroadcastReceiver.
}
}
});
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException in the audio service.");
}
- synchronized (mLock) {
- mGlobalBluetoothA2dpOn = (audioRoutes != null && audioRoutes.bluetoothName != null);
- }
+
+ IntentFilter intentFilter = new IntentFilter(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+ context.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null);
}
public void systemRunning() {
@@ -415,14 +406,12 @@
void restoreBluetoothA2dp() {
try {
- boolean btConnected = false;
boolean a2dpOn = false;
synchronized (mLock) {
- btConnected = mAudioRoutesInfo.bluetoothName != null;
a2dpOn = mGlobalBluetoothA2dpOn;
}
// We don't need to change a2dp status when bluetooth is not connected.
- if (btConnected) {
+ if (mBluetoothDevice != null) {
Slog.v(TAG, "restoreBluetoothA2dp(" + a2dpOn + ")");
mAudioService.setBluetoothA2dpOn(a2dpOn);
}
@@ -661,6 +650,25 @@
return false;
}
+ final class MediaRouterServiceBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) {
+ int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
+ BluetoothProfile.STATE_DISCONNECTED);
+ if (state == BluetoothProfile.STATE_DISCONNECTED) {
+ mGlobalBluetoothA2dpOn = false;
+ mBluetoothDevice = null;
+ } else if (state == BluetoothProfile.STATE_CONNECTED) {
+ mGlobalBluetoothA2dpOn = true;
+ mBluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ // To ensure that BT A2DP is on, call restoreBluetoothA2dp().
+ restoreBluetoothA2dp();
+ }
+ }
+ }
+ }
+
/**
* Information about a particular client of the media router.
* The contents of this object is guarded by mLock.
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
index c13c779..298a988 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
@@ -48,24 +48,24 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public class RecoverableKeyGeneratorTest {
- private static final int TEST_GENERATION_ID = 3;
private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
private static final String KEY_ALGORITHM = "AES";
private static final String TEST_ALIAS = "karlin";
private static final String WRAPPING_KEY_ALIAS = "RecoverableKeyGeneratorTestWrappingKey";
- @Mock RecoverableKeyStorage mRecoverableKeyStorage;
+ @Mock
+ RecoverableKeyStorage mRecoverableKeyStorage;
@Captor ArgumentCaptor<KeyProtection> mKeyProtectionArgumentCaptor;
- private PlatformEncryptionKey mPlatformKey;
+ private AndroidKeyStoreSecretKey mPlatformKey;
private SecretKey mKeyHandle;
private RecoverableKeyGenerator mRecoverableKeyGenerator;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mPlatformKey = new PlatformEncryptionKey(TEST_GENERATION_ID, generateAndroidKeyStoreKey());
+ mPlatformKey = generateAndroidKeyStoreKey();
mKeyHandle = generateKey();
mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(
mPlatformKey, mRecoverableKeyStorage);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java
index c056160..8371fe5 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java
@@ -61,8 +61,7 @@
@Test
public void fromSecretKey_createsWrappedKeyThatCanBeUnwrapped() throws Exception {
- PlatformEncryptionKey wrappingKey = new PlatformEncryptionKey(
- GENERATION_ID, generateAndroidKeyStoreKey());
+ SecretKey wrappingKey = generateAndroidKeyStoreKey();
SecretKey rawKey = generateKey();
WrappedKey wrappedKey = WrappedKey.fromSecretKey(wrappingKey, rawKey);
@@ -70,7 +69,7 @@
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(
Cipher.UNWRAP_MODE,
- wrappingKey.getKey(),
+ wrappingKey,
new GCMParameterSpec(GCM_TAG_LENGTH_BITS, wrappedKey.getNonce()));
SecretKey unwrappedKey = (SecretKey) cipher.unwrap(
wrappedKey.getKeyMaterial(), KEY_ALGORITHM, Cipher.SECRET_KEY);
@@ -78,28 +77,15 @@
}
@Test
- public void fromSecretKey_returnsAKeyWithTheGenerationIdOfTheWrappingKey() throws Exception {
- PlatformEncryptionKey wrappingKey = new PlatformEncryptionKey(
- GENERATION_ID, generateAndroidKeyStoreKey());
- SecretKey rawKey = generateKey();
-
- WrappedKey wrappedKey = WrappedKey.fromSecretKey(wrappingKey, rawKey);
-
- assertEquals(GENERATION_ID, wrappedKey.getPlatformKeyGenerationId());
- }
-
- @Test
public void decryptWrappedKeys_decryptsWrappedKeys() throws Exception {
String alias = "karlin";
- AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey();
+ PlatformDecryptionKey platformKey = generatePlatformDecryptionKey();
SecretKey appKey = generateKey();
- WrappedKey wrappedKey = WrappedKey.fromSecretKey(
- new PlatformEncryptionKey(GENERATION_ID, platformKey), appKey);
+ WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey.getKey(), appKey);
HashMap<String, WrappedKey> keysByAlias = new HashMap<>();
keysByAlias.put(alias, wrappedKey);
- Map<String, SecretKey> unwrappedKeys = WrappedKey.unwrapKeys(
- new PlatformDecryptionKey(GENERATION_ID, platformKey), keysByAlias);
+ Map<String, SecretKey> unwrappedKeys = WrappedKey.unwrapKeys(platformKey, keysByAlias);
assertEquals(1, unwrappedKeys.size());
assertTrue(unwrappedKeys.containsKey(alias));
@@ -109,31 +95,26 @@
@Test
public void decryptWrappedKeys_doesNotDieIfSomeKeysAreUnwrappable() throws Exception {
String alias = "karlin";
- AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey();
SecretKey appKey = generateKey();
- WrappedKey wrappedKey = WrappedKey.fromSecretKey(
- new PlatformEncryptionKey(GENERATION_ID, platformKey), appKey);
+ WrappedKey wrappedKey = WrappedKey.fromSecretKey(generateKey(), appKey);
HashMap<String, WrappedKey> keysByAlias = new HashMap<>();
keysByAlias.put(alias, wrappedKey);
Map<String, SecretKey> unwrappedKeys = WrappedKey.unwrapKeys(
- new PlatformDecryptionKey(GENERATION_ID, platformKey), keysByAlias);
+ generatePlatformDecryptionKey(), keysByAlias);
assertEquals(0, unwrappedKeys.size());
}
@Test
public void decryptWrappedKeys_throwsIfPlatformKeyGenerationIdDoesNotMatch() throws Exception {
- AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey();
- WrappedKey wrappedKey = WrappedKey.fromSecretKey(
- new PlatformEncryptionKey(GENERATION_ID, platformKey), generateKey());
+ WrappedKey wrappedKey = WrappedKey.fromSecretKey(generateKey(), generateKey());
HashMap<String, WrappedKey> keysByAlias = new HashMap<>();
keysByAlias.put("benji", wrappedKey);
try {
WrappedKey.unwrapKeys(
- new PlatformDecryptionKey(/*generationId=*/ 2, platformKey),
- keysByAlias);
+ generatePlatformDecryptionKey(/*generationId=*/ 2), keysByAlias);
fail("Should have thrown.");
} catch (BadPlatformKeyException e) {
assertEquals(
@@ -161,4 +142,12 @@
.build());
return (AndroidKeyStoreSecretKey) keyGenerator.generateKey();
}
+
+ private PlatformDecryptionKey generatePlatformDecryptionKey() throws Exception {
+ return generatePlatformDecryptionKey(GENERATION_ID);
+ }
+
+ private PlatformDecryptionKey generatePlatformDecryptionKey(int generationId) throws Exception {
+ return new PlatformDecryptionKey(generationId, generateAndroidKeyStoreKey());
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
new file mode 100644
index 0000000..5cb88dd
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2017 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.locksettings.recoverablekeystore.storage;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.locksettings.recoverablekeystore.WrappedKey;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RecoverableKeyStoreDbTest {
+ private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
+
+ private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
+ private File mDatabaseFile;
+
+ @Before
+ public void setUp() {
+ Context context = InstrumentationRegistry.getTargetContext();
+ mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
+ mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
+ }
+
+ @After
+ public void tearDown() {
+ mRecoverableKeyStoreDb.close();
+ mDatabaseFile.delete();
+ }
+
+ @Test
+ public void insertKey_replacesOldKey() {
+ int userId = 12;
+ String alias = "test";
+ WrappedKey oldWrappedKey = new WrappedKey(
+ getUtf8Bytes("nonce1"), getUtf8Bytes("keymaterial1"));
+ mRecoverableKeyStoreDb.insertKey(
+ userId, alias, oldWrappedKey, /*generationId=*/ 1);
+ byte[] nonce = getUtf8Bytes("nonce2");
+ byte[] keyMaterial = getUtf8Bytes("keymaterial2");
+ WrappedKey newWrappedKey = new WrappedKey(nonce, keyMaterial);
+
+ mRecoverableKeyStoreDb.insertKey(
+ userId, alias, newWrappedKey, /*generationId=*/ 2);
+
+ WrappedKey retrievedKey = mRecoverableKeyStoreDb.getKey(userId, alias);
+ assertArrayEquals(nonce, retrievedKey.getNonce());
+ assertArrayEquals(keyMaterial, retrievedKey.getKeyMaterial());
+ }
+
+ @Test
+ public void getKey_returnsNullIfNoKey() {
+ WrappedKey key = mRecoverableKeyStoreDb.getKey(
+ /*userId=*/ 1, /*alias=*/ "hello");
+
+ assertNull(key);
+ }
+
+ @Test
+ public void getKey_returnsInsertedKey() {
+ int userId = 12;
+ int generationId = 6;
+ String alias = "test";
+ byte[] nonce = getUtf8Bytes("nonce");
+ byte[] keyMaterial = getUtf8Bytes("keymaterial");
+ WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial);
+ mRecoverableKeyStoreDb.insertKey(userId, alias, wrappedKey, generationId);
+
+ WrappedKey retrievedKey = mRecoverableKeyStoreDb.getKey(userId, alias);
+
+ assertArrayEquals(nonce, retrievedKey.getNonce());
+ assertArrayEquals(keyMaterial, retrievedKey.getKeyMaterial());
+ }
+
+ @Test
+ public void getAllKeys_getsKeysWithUserIdAndGenerationId() {
+ int userId = 12;
+ int generationId = 6;
+ String alias = "test";
+ byte[] nonce = getUtf8Bytes("nonce");
+ byte[] keyMaterial = getUtf8Bytes("keymaterial");
+ WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial);
+ mRecoverableKeyStoreDb.insertKey(userId, alias, wrappedKey, generationId);
+
+ Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys(userId, generationId);
+
+ assertEquals(1, keys.size());
+ assertTrue(keys.containsKey(alias));
+ WrappedKey retrievedKey = keys.get(alias);
+ assertArrayEquals(nonce, retrievedKey.getNonce());
+ assertArrayEquals(keyMaterial, retrievedKey.getKeyMaterial());
+ }
+
+ @Test
+ public void getAllKeys_doesNotReturnKeysWithBadGenerationId() {
+ int userId = 12;
+ WrappedKey wrappedKey = new WrappedKey(
+ getUtf8Bytes("nonce"), getUtf8Bytes("keymaterial"));
+ mRecoverableKeyStoreDb.insertKey(
+ userId, /*alias=*/ "test", wrappedKey, /*generationId=*/ 5);
+
+ Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys(
+ userId, /*generationId=*/ 7);
+
+ assertTrue(keys.isEmpty());
+ }
+
+ @Test
+ public void getAllKeys_doesNotReturnKeysWithBadUserId() {
+ int generationId = 12;
+ WrappedKey wrappedKey = new WrappedKey(
+ getUtf8Bytes("nonce"), getUtf8Bytes("keymaterial"));
+ mRecoverableKeyStoreDb.insertKey(
+ /*userId=*/ 1, /*alias=*/ "test", wrappedKey, generationId);
+
+ Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys(
+ /*userId=*/ 2, generationId);
+
+ assertTrue(keys.isEmpty());
+ }
+
+ private static byte[] getUtf8Bytes(String s) {
+ return s.getBytes(StandardCharsets.UTF_8);
+ }
+}