Merge "Implements onNanoAppAborted callback"
diff --git a/api/current.txt b/api/current.txt
index 9bdcdad..5df52b9 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -37692,11 +37692,8 @@
     field public static final android.os.Parcelable.Creator<android.service.autofill.EditDistanceScorer> CREATOR;
   }
 
-  public final class FieldClassification implements android.os.Parcelable {
-    method public int describeContents();
+  public final class FieldClassification {
     method public java.util.List<android.service.autofill.FieldClassification.Match> getMatches();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.service.autofill.FieldClassification> CREATOR;
   }
 
   public static final class FieldClassification.Match {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 89df421..4bb4c50 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2676,6 +2676,7 @@
      * @see UserManager#DISALLOW_UNIFIED_PASSWORD
      */
     public boolean isUsingUnifiedPassword(@NonNull ComponentName admin) {
+        throwIfParentInstance("isUsingUnifiedPassword");
         if (mService != null) {
             try {
                 return mService.isUsingUnifiedPassword(admin);
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&mdash;activities that handle the
+ * {@link Intent#ACTION_MAIN} action and the {@link Intent#CATEGORY_LAUNCHER} category&mdash;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&mdash;shortcuts whose {@link ShortcutInfo#isDeclaredInManifest()} method
+ *   returns {@code true}&mdash;followed by dynamic shortcuts&mdash;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 &lt;shortcut&gt;} 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&mdash;activities that handle the {@code MAIN} action and the
- * {@code LAUNCHER} category&mdash;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/service/autofill/FieldClassification.java b/core/java/android/service/autofill/FieldClassification.java
index b28c6f8..001b291 100644
--- a/core/java/android/service/autofill/FieldClassification.java
+++ b/core/java/android/service/autofill/FieldClassification.java
@@ -20,31 +20,39 @@
 
 import android.annotation.NonNull;
 import android.os.Parcel;
-import android.os.Parcelable;
 import android.view.autofill.Helper;
 
 import com.android.internal.util.Preconditions;
 
-import com.google.android.collect.Lists;
-
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 
 /**
  * Represents the <a href="AutofillService.html#FieldClassification">field classification</a>
  * results for a given field.
  */
-// TODO(b/70291841): let caller handle Parcelable...
-public final class FieldClassification implements Parcelable {
+public final class FieldClassification {
 
-    private final Match mMatch;
+    private final ArrayList<Match> mMatches;
 
     /** @hide */
-    public FieldClassification(@NonNull Match match) {
-        mMatch = Preconditions.checkNotNull(match);
+    public FieldClassification(@NonNull ArrayList<Match> matches) {
+        mMatches = Preconditions.checkNotNull(matches);
+        Collections.sort(mMatches, new Comparator<Match>() {
+            @Override
+            public int compare(Match o1, Match o2) {
+                if (o1.mScore > o2.mScore) return -1;
+                if (o1.mScore < o2.mScore) return 1;
+                return 0;
+            }}
+        );
     }
 
     /**
-     * Gets the {@link Match matches} with the highest {@link Match#getScore() scores}.
+     * Gets the {@link Match matches} with the highest {@link Match#getScore() scores} (sorted in
+     * descending order).
      *
      * <p><b>Note:</b> There's no guarantee of how many matches will be returned. In fact,
      * the Android System might return just the top match to minimize the impact of field
@@ -52,43 +60,48 @@
      */
     @NonNull
     public List<Match> getMatches() {
-        return Lists.newArrayList(mMatch);
+        return mMatches;
     }
 
     @Override
     public String toString() {
         if (!sDebug) return super.toString();
 
-        return "FieldClassification: " + mMatch;
+        return "FieldClassification: " + mMatches;
     }
 
-    /////////////////////////////////////
-    // Parcelable "contract" methods. //
-    /////////////////////////////////////
-
-    @Override
-    public int describeContents() {
-        return 0;
+    private void writeToParcel(Parcel parcel) {
+        parcel.writeInt(mMatches.size());
+        for (int i = 0; i < mMatches.size(); i++) {
+            mMatches.get(i).writeToParcel(parcel);
+        }
     }
 
-    @Override
-    public void writeToParcel(Parcel parcel, int flags) {
-        mMatch.writeToParcel(parcel);
-    }
-
-    public static final Parcelable.Creator<FieldClassification> CREATOR =
-            new Parcelable.Creator<FieldClassification>() {
-
-        @Override
-        public FieldClassification createFromParcel(Parcel parcel) {
-            return new FieldClassification(Match.readFromParcel(parcel));
+    private static FieldClassification readFromParcel(Parcel parcel) {
+        final int size = parcel.readInt();
+        final ArrayList<Match> matches = new ArrayList<>();
+        for (int i = 0; i < size; i++) {
+            matches.add(i, Match.readFromParcel(parcel));
         }
 
-        @Override
-        public FieldClassification[] newArray(int size) {
-            return new FieldClassification[size];
+        return new FieldClassification(matches);
+    }
+
+    static FieldClassification[] readArrayFromParcel(Parcel parcel) {
+        final int length = parcel.readInt();
+        final FieldClassification[] fcs = new FieldClassification[length];
+        for (int i = 0; i < length; i++) {
+            fcs[i] = readFromParcel(parcel);
         }
-    };
+        return fcs;
+    }
+
+    static void writeArrayToParcel(@NonNull Parcel parcel, @NonNull FieldClassification[] fcs) {
+        parcel.writeInt(fcs.length);
+        for (int i = 0; i < fcs.length; i++) {
+            fcs[i].writeToParcel(parcel);
+        }
+    }
 
     /**
      * Represents the score of a {@link UserData} entry for the field.
@@ -151,23 +164,5 @@
         private static Match readFromParcel(@NonNull Parcel parcel) {
             return new Match(parcel.readString(), parcel.readFloat());
         }
-
-        /** @hide */
-        public static Match[] readArrayFromParcel(@NonNull Parcel parcel) {
-            final int length = parcel.readInt();
-            final Match[] matches = new Match[length];
-            for (int i = 0; i < length; i++) {
-                matches[i] = readFromParcel(parcel);
-            }
-            return matches;
-        }
-
-        /** @hide */
-        public static void writeArrayToParcel(@NonNull Parcel parcel, @NonNull Match[] matches) {
-            parcel.writeInt(matches.length);
-            for (int i = 0; i < matches.length; i++) {
-                matches[i].writeToParcel(parcel);
-            }
-        }
     }
 }
diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java
index 07fab61..df62446 100644
--- a/core/java/android/service/autofill/FillEventHistory.java
+++ b/core/java/android/service/autofill/FillEventHistory.java
@@ -25,7 +25,6 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.service.autofill.FieldClassification.Match;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
@@ -157,7 +156,8 @@
                 final AutofillId[] detectedFields = event.mDetectedFieldIds;
                 parcel.writeParcelableArray(detectedFields, flags);
                 if (detectedFields != null) {
-                    Match.writeArrayToParcel(parcel, event.mDetectedMatches);
+                    FieldClassification.writeArrayToParcel(parcel,
+                            event.mDetectedFieldClassifications);
                 }
             }
         }
@@ -251,7 +251,7 @@
         @Nullable private final ArrayList<ArrayList<String>> mManuallyFilledDatasetIds;
 
         @Nullable private final AutofillId[] mDetectedFieldIds;
-        @Nullable private final Match[] mDetectedMatches;
+        @Nullable private final FieldClassification[] mDetectedFieldClassifications;
 
         /**
          * Returns the type of the event.
@@ -370,11 +370,11 @@
             final ArrayMap<AutofillId, FieldClassification> map = new ArrayMap<>(size);
             for (int i = 0; i < size; i++) {
                 final AutofillId id = mDetectedFieldIds[i];
-                final Match match = mDetectedMatches[i];
+                final FieldClassification fc = mDetectedFieldClassifications[i];
                 if (sVerbose) {
-                    Log.v(TAG, "getFieldsClassification[" + i + "]: id=" + id + ", match=" + match);
+                    Log.v(TAG, "getFieldsClassification[" + i + "]: id=" + id + ", fc=" + fc);
                 }
-                map.put(id, new FieldClassification(match));
+                map.put(id, fc);
             }
             return map;
         }
@@ -455,7 +455,7 @@
          * and belonged to datasets.
          * @param manuallyFilledDatasetIds The ids of datasets that had values matching the
          * respective entry on {@code manuallyFilledFieldIds}.
-         * @param detectedMatches the field classification matches.
+         * @param detectedFieldClassifications the field classification matches.
          *
          * @throws IllegalArgumentException If the length of {@code changedFieldIds} and
          * {@code changedDatasetIds} doesn't match.
@@ -471,7 +471,8 @@
                 @Nullable ArrayList<String> changedDatasetIds,
                 @Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
                 @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
-                @Nullable AutofillId[] detectedFieldIds, @Nullable Match[] detectedMatches) {
+                @Nullable AutofillId[] detectedFieldIds,
+                @Nullable FieldClassification[] detectedFieldClassifications) {
             mEventType = Preconditions.checkArgumentInRange(eventType, 0, TYPE_CONTEXT_COMMITTED,
                     "eventType");
             mDatasetId = datasetId;
@@ -496,7 +497,7 @@
             mManuallyFilledDatasetIds = manuallyFilledDatasetIds;
 
             mDetectedFieldIds = detectedFieldIds;
-            mDetectedMatches = detectedMatches;
+            mDetectedFieldClassifications = detectedFieldClassifications;
         }
 
         @Override
@@ -510,7 +511,8 @@
                     + ", manuallyFilledFieldIds=" + mManuallyFilledFieldIds
                     + ", manuallyFilledDatasetIds=" + mManuallyFilledDatasetIds
                     + ", detectedFieldIds=" + Arrays.toString(mDetectedFieldIds)
-                    + ", detectedMaches =" + Arrays.toString(mDetectedMatches)
+                    + ", detectedFieldClassifications ="
+                        + Arrays.toString(mDetectedFieldClassifications)
                     + "]";
         }
     }
@@ -548,15 +550,16 @@
                         }
                         final AutofillId[] detectedFieldIds = parcel.readParcelableArray(null,
                                 AutofillId.class);
-                        final Match[] detectedMatches = (detectedFieldIds != null)
-                                ? Match.readArrayFromParcel(parcel)
+                        final FieldClassification[] detectedFieldClassifications =
+                                (detectedFieldIds != null)
+                                ? FieldClassification.readArrayFromParcel(parcel)
                                 : null;
 
                         selection.addEvent(new Event(eventType, datasetId, clientState,
                                 selectedDatasetIds, ignoredDatasets,
                                 changedFieldIds, changedDatasetIds,
                                 manuallyFilledFieldIds, manuallyFilledDatasetIds,
-                                detectedFieldIds, detectedMatches));
+                                detectedFieldIds, detectedFieldClassifications));
                     }
                     return selection;
                 }
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/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 4bf3c5a..3361824 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -52,6 +52,7 @@
 import android.provider.Settings;
 import android.service.autofill.AutofillService;
 import android.service.autofill.AutofillServiceInfo;
+import android.service.autofill.FieldClassification;
 import android.service.autofill.FieldClassification.Match;
 import android.service.autofill.FillEventHistory;
 import android.service.autofill.FillEventHistory.Event;
@@ -81,6 +82,7 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Random;
 
 /**
@@ -720,37 +722,45 @@
             @Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
             @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
             @Nullable ArrayList<AutofillId> detectedFieldIdsList,
-            @Nullable ArrayList<Match> detectedMatchesList,
+            @Nullable ArrayList<FieldClassification> detectedFieldClassificationsList,
             @NonNull String appPackageName) {
-
         synchronized (mLock) {
             if (isValidEventLocked("logDatasetNotSelected()", sessionId)) {
                 AutofillId[] detectedFieldsIds = null;
-                Match[] detectedMatches = null;
+                FieldClassification[] detectedFieldClassifications = null;
                 if (detectedFieldIdsList != null) {
                     detectedFieldsIds = new AutofillId[detectedFieldIdsList.size()];
                     detectedFieldIdsList.toArray(detectedFieldsIds);
-                    detectedMatches = new Match[detectedMatchesList.size()];
-                    detectedMatchesList.toArray(detectedMatches);
+                    detectedFieldClassifications =
+                            new FieldClassification[detectedFieldClassificationsList.size()];
+                    detectedFieldClassificationsList.toArray(detectedFieldClassifications);
 
-                    final int size = detectedMatchesList.size();
+                    final int numberFields = detectedFieldsIds.length;
+                    int totalSize = 0;
                     float totalScore = 0;
-                    for (int i = 0; i < size; i++) {
-                        totalScore += detectedMatches[i].getScore();
+                    for (int i = 0; i < numberFields; i++) {
+                        final FieldClassification fc = detectedFieldClassifications[i];
+                        final List<Match> matches = fc.getMatches();
+                        final int size = matches.size();
+                        totalSize += size;
+                        for (int j = 0; j < size; j++) {
+                            totalScore += matches.get(j).getScore();
+                        }
                     }
-                    final int averageScore = (int) ((totalScore * 100) / size);
-                    mMetricsLogger.write(
-                            Helper.newLogMaker(MetricsEvent.AUTOFILL_FIELD_CLASSIFICATION_MATCHES,
-                                    appPackageName, getServicePackageName())
-                            .setCounterValue(size)
-                            .addTaggedData(MetricsEvent.FIELD_AUTOFILL_MATCH_SCORE, averageScore));
 
+                    final int averageScore = (int) ((totalScore * 100) / totalSize);
+                    mMetricsLogger.write(Helper
+                            .newLogMaker(MetricsEvent.AUTOFILL_FIELD_CLASSIFICATION_MATCHES,
+                                    appPackageName, getServicePackageName())
+                            .setCounterValue(numberFields)
+                            .addTaggedData(MetricsEvent.FIELD_AUTOFILL_MATCH_SCORE,
+                                    averageScore));
                 }
                 mEventHistory.addEvent(new Event(Event.TYPE_CONTEXT_COMMITTED, null,
                         clientState, selectedDatasets, ignoredDatasets,
                         changedFieldIds, changedDatasetIds,
                         manuallyFilledFieldIds, manuallyFilledDatasetIds,
-                        detectedFieldsIds, detectedMatches));
+                        detectedFieldsIds, detectedFieldClassifications));
             }
         }
     }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index ceae93c..7b85a6c 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -66,6 +66,7 @@
 import android.service.autofill.UserData;
 import android.service.autofill.ValueFinder;
 import android.service.autofill.EditDistanceScorer;
+import android.service.autofill.FieldClassification;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.LocalLog;
@@ -961,15 +962,15 @@
         final UserData userData = mService.getUserData();
 
         final ArrayList<AutofillId> detectedFieldIds;
-        final ArrayList<Match> detectedMatches;
+        final ArrayList<FieldClassification> detectedFieldClassifications;
 
         if (userData != null) {
             final int maxFieldsSize = UserData.getMaxFieldClassificationIdsSize();
             detectedFieldIds = new ArrayList<>(maxFieldsSize);
-            detectedMatches = new ArrayList<>(maxFieldsSize);
+            detectedFieldClassifications = new ArrayList<>(maxFieldsSize);
         } else {
             detectedFieldIds = null;
-            detectedMatches = null;
+            detectedFieldClassifications = null;
         }
 
         for (int i = 0; i < mViewStates.size(); i++) {
@@ -1078,8 +1079,8 @@
 
                     // Sets field classification score for field
                     if (userData!= null) {
-                        setScore(detectedFieldIds, detectedMatches, userData, viewState.id,
-                                currentValue);
+                        setScore(detectedFieldIds, detectedFieldClassifications, userData,
+                                viewState.id, currentValue);
                     }
                 } // else
             } // else
@@ -1093,7 +1094,7 @@
                     + ", changedDatasetIds=" + changedDatasetIds
                     + ", manuallyFilledIds=" + manuallyFilledIds
                     + ", detectedFieldIds=" + detectedFieldIds
-                    + ", detectedMatches=" + detectedMatches
+                    + ", detectedFieldClassifications=" + detectedFieldClassifications
                     );
         }
 
@@ -1116,16 +1117,17 @@
         mService.logContextCommitted(id, mClientState, mSelectedDatasetIds, ignoredDatasets,
                 changedFieldIds, changedDatasetIds,
                 manuallyFilledFieldIds, manuallyFilledDatasetIds,
-                detectedFieldIds, detectedMatches, mComponentName.getPackageName());
+                detectedFieldIds, detectedFieldClassifications, mComponentName.getPackageName());
     }
 
     /**
-     * Adds the top score match to {@code detectedFieldsIds} and {@code detectedMatches} for
+     * Adds the matches to {@code detectedFieldsIds} and {@code detectedFieldClassifications} for
      * {@code fieldId} based on its {@code currentValue} and {@code userData}.
      */
     private static void setScore(@NonNull ArrayList<AutofillId> detectedFieldIds,
-            @NonNull ArrayList<Match> detectedMatches, @NonNull UserData userData,
-            @NonNull AutofillId fieldId, @NonNull AutofillValue currentValue) {
+            @NonNull ArrayList<FieldClassification> detectedFieldClassifications,
+            @NonNull UserData userData, @NonNull AutofillId fieldId,
+            @NonNull AutofillValue currentValue) {
 
         final String[] userValues = userData.getValues();
         final String[] remoteIds = userData.getRemoteIds();
@@ -1138,23 +1140,26 @@
                     + valuesLength + ", ids.length = " + idsLength);
             return;
         }
-        String remoteId = null;
-        float topScore = 0;
+
+        ArrayList<Match> matches = null;
         for (int i = 0; i < userValues.length; i++) {
+            String remoteId = remoteIds[i];
             final String value = userValues[i];
             final float score = userData.getScorer().getScore(currentValue, value);
-            if (score > topScore) {
-                topScore = score;
-                remoteId = remoteIds[i];
+            if (score > 0) {
+                if (sVerbose) {
+                    Slog.v(TAG, "adding score " + score + " at index " + i + " and id " + fieldId);
+                }
+                if (matches == null) {
+                    matches = new ArrayList<>(userValues.length);
+                }
+                matches.add(new Match(remoteId, score));
             }
+            else if (sVerbose) Slog.v(TAG, "skipping score 0 at index " + i + " and id " + fieldId);
         }
-
-        if (remoteId != null && topScore > 0) {
-            if (sVerbose) Slog.v(TAG, "setScores(): top score for #" + fieldId + " is " + topScore);
+        if (matches != null) {
             detectedFieldIds.add(fieldId);
-            detectedMatches.add(new Match(remoteId, topScore));
-        } else if (sVerbose) {
-            Slog.v(TAG, "setScores(): no top score for #" + fieldId + ": " + topScore);
+            detectedFieldClassifications.add(new FieldClassification(matches));
         }
     }
 
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 56bc19d..7f88663 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/KeySyncUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
index e4d2b953..37aeb3a 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
@@ -16,6 +16,8 @@
 
 package com.android.server.locksettings.recoverablekeystore;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.nio.charset.StandardCharsets;
 import java.security.InvalidKeyException;
 import java.security.MessageDigest;
@@ -25,6 +27,7 @@
 import java.util.HashMap;
 import java.util.Map;
 
+import javax.crypto.AEADBadTagException;
 import javax.crypto.KeyGenerator;
 import javax.crypto.SecretKey;
 
@@ -45,9 +48,13 @@
             "V1 locally_encrypted_recovery_key".getBytes(StandardCharsets.UTF_8);
     private static final byte[] ENCRYPTED_APPLICATION_KEY_HEADER =
             "V1 encrypted_application_key".getBytes(StandardCharsets.UTF_8);
+    private static final byte[] RECOVERY_CLAIM_HEADER =
+            "V1 KF_claim".getBytes(StandardCharsets.UTF_8);
 
     private static final byte[] THM_KF_HASH_PREFIX = "THM_KF_hash".getBytes(StandardCharsets.UTF_8);
 
+    private static final int KEY_CLAIMANT_LENGTH_BYTES = 16;
+
     /**
      * Encrypts the recovery key using both the lock screen hash and the remote storage's public
      * key.
@@ -121,7 +128,7 @@
      */
     public static SecretKey generateRecoveryKey() throws NoSuchAlgorithmException {
         KeyGenerator keyGenerator = KeyGenerator.getInstance(RECOVERY_KEY_ALGORITHM);
-        keyGenerator.init(RECOVERY_KEY_SIZE_BITS, SecureRandom.getInstanceStrong());
+        keyGenerator.init(RECOVERY_KEY_SIZE_BITS, new SecureRandom());
         return keyGenerator.generateKey();
     }
 
@@ -153,13 +160,100 @@
     }
 
     /**
-     * Returns a new array, the contents of which are the concatenation of {@code a} and {@code b}.
+     * Returns a random 16-byte key claimant.
+     *
+     * @hide
      */
-    private static byte[] concat(byte[] a, byte[] b) {
-        byte[] result = new byte[a.length + b.length];
-        System.arraycopy(a, 0, result, 0, a.length);
-        System.arraycopy(b, 0, result, a.length, b.length);
-        return result;
+    public static byte[] generateKeyClaimant() {
+        SecureRandom secureRandom = new SecureRandom();
+        byte[] key = new byte[KEY_CLAIMANT_LENGTH_BYTES];
+        secureRandom.nextBytes(key);
+        return key;
+    }
+
+    /**
+     * Encrypts a claim to recover a remote recovery key.
+     *
+     * @param publicKey The public key of the remote server.
+     * @param vaultParams Associated vault parameters.
+     * @param challenge The challenge issued by the server.
+     * @param thmKfHash The THM hash of the lock screen.
+     * @param keyClaimant The random key claimant.
+     * @return The encrypted recovery claim, to be sent to the remote server.
+     * @throws NoSuchAlgorithmException if any SecureBox algorithm is not present.
+     * @throws InvalidKeyException if the {@code publicKey} could not be used to encrypt.
+     *
+     * @hide
+     */
+    public static byte[] encryptRecoveryClaim(
+            PublicKey publicKey,
+            byte[] vaultParams,
+            byte[] challenge,
+            byte[] thmKfHash,
+            byte[] keyClaimant) throws NoSuchAlgorithmException, InvalidKeyException {
+        return SecureBox.encrypt(
+                publicKey,
+                /*sharedSecret=*/ null,
+                /*header=*/ concat(RECOVERY_CLAIM_HEADER, vaultParams, challenge),
+                /*payload=*/ concat(thmKfHash, keyClaimant));
+    }
+
+    /**
+     * Decrypts a recovery key, after having retrieved it from a remote server.
+     *
+     * @param lskfHash The lock screen hash associated with the key.
+     * @param encryptedRecoveryKey The encrypted key.
+     * @return The raw key material.
+     * @throws NoSuchAlgorithmException if any SecureBox algorithm is unavailable.
+     * @throws AEADBadTagException if the message has been tampered with or was encrypted with a
+     *     different key.
+     */
+    public static byte[] decryptRecoveryKey(byte[] lskfHash, byte[] encryptedRecoveryKey)
+            throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException {
+        return SecureBox.decrypt(
+                /*ourPrivateKey=*/ null,
+                /*sharedSecret=*/ lskfHash,
+                /*header=*/ LOCALLY_ENCRYPTED_RECOVERY_KEY_HEADER,
+                /*encryptedPayload=*/ encryptedRecoveryKey);
+    }
+
+    /**
+     * Decrypts an application key, using the recovery key.
+     *
+     * @param recoveryKey The recovery key - used to wrap all application keys.
+     * @param encryptedApplicationKey The application key to unwrap.
+     * @return The raw key material of the application key.
+     * @throws NoSuchAlgorithmException if any SecureBox algorithm is unavailable.
+     * @throws AEADBadTagException if the message has been tampered with or was encrypted with a
+     *     different key.
+     */
+    public static byte[] decryptApplicationKey(byte[] recoveryKey, byte[] encryptedApplicationKey)
+            throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException {
+        return SecureBox.decrypt(
+                /*ourPrivateKey=*/ null,
+                /*sharedSecret=*/ recoveryKey,
+                /*header=*/ ENCRYPTED_APPLICATION_KEY_HEADER,
+                /*encryptedPayload=*/ encryptedApplicationKey);
+    }
+
+    /**
+     * Returns the concatenation of all the given {@code arrays}.
+     */
+    @VisibleForTesting
+    static byte[] concat(byte[]... arrays) {
+        int length = 0;
+        for (byte[] array : arrays) {
+            length += array.length;
+        }
+
+        byte[] concatenated = new byte[length];
+        int pos = 0;
+        for (byte[] array : arrays) {
+            System.arraycopy(array, /*srcPos=*/ 0, concatenated, pos, array.length);
+            pos += array.length;
+        }
+
+        return concatenated;
     }
 
     // Statics only
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
index 457fdc14..742cb45 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
@@ -16,10 +16,15 @@
 
 package com.android.server.locksettings.recoverablekeystore;
 
+import android.annotation.Nullable;
+
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
 import java.security.PublicKey;
 
+import javax.crypto.AEADBadTagException;
+
 /**
  * TODO(b/69056040) Add implementation of SecureBox. This is a placeholder so KeySyncUtils compiles.
  *
@@ -32,8 +37,25 @@
      * @hide
      */
     public static byte[] encrypt(
-            PublicKey theirPublicKey, byte[] sharedSecret, byte[] header, byte[] payload)
+            @Nullable PublicKey theirPublicKey,
+            @Nullable byte[] sharedSecret,
+            @Nullable byte[] header,
+            @Nullable byte[] payload)
             throws NoSuchAlgorithmException, InvalidKeyException {
         throw new UnsupportedOperationException("Needs to be implemented.");
     }
+
+    /**
+     * TODO(b/69056040) Add implementation of decrypt.
+     *
+     * @hide
+     */
+    public static byte[] decrypt(
+            @Nullable PrivateKey ourPrivateKey,
+            @Nullable byte[] sharedSecret,
+            @Nullable byte[] header,
+            byte[] encryptedPayload)
+            throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException {
+        throw new UnsupportedOperationException("Needs to be implemented.");
+    }
 }
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/KeySyncUtilsTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
index c918e8c..ac3abed 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
@@ -37,6 +37,7 @@
 public class KeySyncUtilsTest {
     private static final int RECOVERY_KEY_LENGTH_BITS = 256;
     private static final int THM_KF_HASH_SIZE = 256;
+    private static final int KEY_CLAIMANT_LENGTH_BYTES = 16;
     private static final String SHA_256_ALGORITHM = "SHA-256";
 
     @Test
@@ -70,6 +71,32 @@
         assertFalse(Arrays.equals(a.getEncoded(), b.getEncoded()));
     }
 
+    @Test
+    public void generateKeyClaimant_returns16Bytes() throws Exception {
+        byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
+
+        assertEquals(KEY_CLAIMANT_LENGTH_BYTES, keyClaimant.length);
+    }
+
+    @Test
+    public void generateKeyClaimant_generatesANewClaimantEachTime() {
+        byte[] a = KeySyncUtils.generateKeyClaimant();
+        byte[] b = KeySyncUtils.generateKeyClaimant();
+
+        assertFalse(Arrays.equals(a, b));
+    }
+
+    @Test
+    public void concat_concatenatesArrays() {
+        assertArrayEquals(
+                utf8Bytes("hello, world!"),
+                KeySyncUtils.concat(
+                        utf8Bytes("hello"),
+                        utf8Bytes(", "),
+                        utf8Bytes("world"),
+                        utf8Bytes("!")));
+    }
+
     private static byte[] utf8Bytes(String s) {
         return s.getBytes(StandardCharsets.UTF_8);
     }
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);
+    }
+}