Merge "DND qs tile primary text is always Do not disturb"
diff --git a/Android.bp b/Android.bp
index 8db952e..04e1dd6 100644
--- a/Android.bp
+++ b/Android.bp
@@ -178,6 +178,8 @@
         "core/java/android/hardware/location/IContextHubClientCallback.aidl",
         "core/java/android/hardware/location/IContextHubService.aidl",
         "core/java/android/hardware/location/IContextHubTransactionCallback.aidl",
+        "core/java/android/hardware/radio/IAnnouncementListener.aidl",
+        "core/java/android/hardware/radio/ICloseHandle.aidl",
         "core/java/android/hardware/radio/IRadioService.aidl",
         "core/java/android/hardware/radio/ITuner.aidl",
         "core/java/android/hardware/radio/ITunerCallback.aidl",
diff --git a/api/current.txt b/api/current.txt
index b117d3e..5e91789 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -72,6 +72,7 @@
     field public static final java.lang.String DUMP = "android.permission.DUMP";
     field public static final java.lang.String EXPAND_STATUS_BAR = "android.permission.EXPAND_STATUS_BAR";
     field public static final java.lang.String FACTORY_TEST = "android.permission.FACTORY_TEST";
+    field public static final java.lang.String FOREGROUND_SERVICE = "android.permission.FOREGROUND_SERVICE";
     field public static final java.lang.String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS";
     field public static final java.lang.String GET_ACCOUNTS_PRIVILEGED = "android.permission.GET_ACCOUNTS_PRIVILEGED";
     field public static final java.lang.String GET_PACKAGE_SIZE = "android.permission.GET_PACKAGE_SIZE";
@@ -3764,6 +3765,7 @@
     method public final void requestShowKeyboardShortcuts();
     method public deprecated boolean requestVisibleBehind(boolean);
     method public final boolean requestWindowFeature(int);
+    method public final <T extends android.view.View> T requireViewById(int);
     method public final void runOnUiThread(java.lang.Runnable);
     method public void setActionBar(android.widget.Toolbar);
     method public void setContentTransitionManager(android.transition.TransitionManager);
@@ -4458,6 +4460,7 @@
     method public void openOptionsMenu();
     method public void registerForContextMenu(android.view.View);
     method public final boolean requestWindowFeature(int);
+    method public final <T extends android.view.View> T requireViewById(int);
     method public void setCancelMessage(android.os.Message);
     method public void setCancelable(boolean);
     method public void setCanceledOnTouchOutside(boolean);
@@ -5693,6 +5696,7 @@
     method public final void setInterruptionFilter(int);
     method public void setNotificationPolicy(android.app.NotificationManager.Policy);
     method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule);
+    field public static final java.lang.String ACTION_APP_BLOCK_STATE_CHANGED = "android.app.action.APP_BLOCK_STATE_CHANGED";
     field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
     field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED";
     field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED";
@@ -6344,6 +6348,7 @@
     method public void onReceive(android.content.Context, android.content.Intent);
     method public void onSecurityLogsAvailable(android.content.Context, android.content.Intent);
     method public void onSystemUpdatePending(android.content.Context, android.content.Intent, long);
+    method public void onTransferAffiliatedProfileOwnershipComplete(android.content.Context, android.os.UserHandle);
     method public void onTransferOwnershipComplete(android.content.Context, android.os.PersistableBundle);
     method public void onUserAdded(android.content.Context, android.content.Intent, android.os.UserHandle);
     method public void onUserRemoved(android.content.Context, android.content.Intent, android.os.UserHandle);
@@ -6365,7 +6370,7 @@
     field public static final java.lang.String DEVICE_ADMIN_META_DATA = "android.app.device_admin";
     field public static final java.lang.String EXTRA_DISABLE_WARNING = "android.app.extra.DISABLE_WARNING";
     field public static final java.lang.String EXTRA_LOCK_TASK_PACKAGE = "android.app.extra.LOCK_TASK_PACKAGE";
-    field public static final java.lang.String EXTRA_TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE = "android.app.extra.TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE";
+    field public static final java.lang.String EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE = "android.app.extra.TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE";
     field public static final java.lang.String SUPPORT_TRANSFER_OWNERSHIP_META_DATA = "android.app.support_transfer_ownership";
   }
 
@@ -6453,6 +6458,7 @@
     method public boolean getStorageEncryption(android.content.ComponentName);
     method public int getStorageEncryptionStatus();
     method public android.app.admin.SystemUpdatePolicy getSystemUpdatePolicy();
+    method public android.os.PersistableBundle getTransferOwnershipBundle();
     method public java.util.List<android.os.PersistableBundle> getTrustAgentConfiguration(android.content.ComponentName, android.content.ComponentName);
     method public android.os.Bundle getUserRestrictions(android.content.ComponentName);
     method public java.lang.String getWifiMacAddress(android.content.ComponentName);
@@ -38454,6 +38460,7 @@
     method public void onWindowFocusChanged(boolean);
     method public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback);
     method public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int);
+    method public final <T extends android.view.View> T requireViewById(int);
     method public void setContentView(int);
     method public void setContentView(android.view.View);
     method public void setContentView(android.view.View, android.view.ViewGroup.LayoutParams);
@@ -46941,6 +46948,7 @@
     method public boolean requestRectangleOnScreen(android.graphics.Rect);
     method public boolean requestRectangleOnScreen(android.graphics.Rect, boolean);
     method public final void requestUnbufferedDispatch(android.view.MotionEvent);
+    method public final <T extends android.view.View> T requireViewById(int);
     method public static int resolveSize(int, int);
     method public static int resolveSizeAndState(int, int, int);
     method public boolean restoreDefaultFocus();
@@ -47976,6 +47984,7 @@
     method public abstract boolean performPanelShortcut(int, int, android.view.KeyEvent, int);
     method public final void removeOnFrameMetricsAvailableListener(android.view.Window.OnFrameMetricsAvailableListener);
     method public boolean requestFeature(int);
+    method public final <T extends android.view.View> T requireViewById(int);
     method public abstract void restoreHierarchyState(android.os.Bundle);
     method public abstract android.os.Bundle saveHierarchyState();
     method public void setAllowEnterTransitionOverlap(boolean);
@@ -50395,6 +50404,7 @@
     method public android.graphics.Bitmap getFavicon();
     method public android.webkit.WebView.HitTestResult getHitTestResult();
     method public deprecated java.lang.String[] getHttpAuthUsernamePassword(java.lang.String, java.lang.String);
+    method public android.os.Looper getLooper();
     method public java.lang.String getOriginalUrl();
     method public int getProgress();
     method public boolean getRendererPriorityWaivedWhenNotVisible();
diff --git a/api/system-current.txt b/api/system-current.txt
index 14320c4..8678c5e 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -382,7 +382,6 @@
     method public java.lang.CharSequence getDeviceOwnerOrganizationName();
     method public java.util.List<java.lang.String> getPermittedAccessibilityServices(int);
     method public java.util.List<java.lang.String> getPermittedInputMethodsForCurrentUser();
-    method public java.lang.CharSequence getPrintingDisabledReason();
     method public android.content.ComponentName getProfileOwner() throws java.lang.IllegalArgumentException;
     method public java.lang.String getProfileOwnerNameAsUser(int) throws java.lang.IllegalArgumentException;
     method public int getUserProvisioningState();
@@ -1738,6 +1737,27 @@
 
 package android.hardware.radio {
 
+  public final class Announcement implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.hardware.radio.ProgramSelector getSelector();
+    method public int getType();
+    method public java.util.Map<java.lang.String, java.lang.String> getVendorInfo();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.hardware.radio.Announcement> CREATOR;
+    field public static final int TYPE_EMERGENCY = 1; // 0x1
+    field public static final int TYPE_EVENT = 6; // 0x6
+    field public static final int TYPE_MISC = 8; // 0x8
+    field public static final int TYPE_NEWS = 5; // 0x5
+    field public static final int TYPE_SPORT = 7; // 0x7
+    field public static final int TYPE_TRAFFIC = 3; // 0x3
+    field public static final int TYPE_WARNING = 2; // 0x2
+    field public static final int TYPE_WEATHER = 4; // 0x4
+  }
+
+  public static abstract interface Announcement.OnListUpdatedListener {
+    method public abstract void onListUpdated(java.util.Collection<android.hardware.radio.Announcement>);
+  }
+
   public final class ProgramList implements java.lang.AutoCloseable {
     method public void addOnCompleteListener(java.util.concurrent.Executor, android.hardware.radio.ProgramList.OnCompleteListener);
     method public void addOnCompleteListener(android.hardware.radio.ProgramList.OnCompleteListener);
@@ -1782,6 +1802,7 @@
     method public deprecated int getProgramType();
     method public android.hardware.radio.ProgramSelector.Identifier[] getSecondaryIds();
     method public deprecated long[] getVendorIds();
+    method public android.hardware.radio.ProgramSelector withSecondaryPreferred(android.hardware.radio.ProgramSelector.Identifier);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.hardware.radio.ProgramSelector> CREATOR;
     field public static final int IDENTIFIER_TYPE_AMFM_FREQUENCY = 1; // 0x1
@@ -1832,8 +1853,11 @@
   }
 
   public class RadioManager {
+    method public void addAnnouncementListener(java.util.Set<java.lang.Integer>, android.hardware.radio.Announcement.OnListUpdatedListener);
+    method public void addAnnouncementListener(java.util.concurrent.Executor, java.util.Set<java.lang.Integer>, android.hardware.radio.Announcement.OnListUpdatedListener);
     method public int listModules(java.util.List<android.hardware.radio.RadioManager.ModuleProperties>);
     method public android.hardware.radio.RadioTuner openTuner(int, android.hardware.radio.RadioManager.BandConfig, boolean, android.hardware.radio.RadioTuner.Callback, android.os.Handler);
+    method public void removeAnnouncementListener(android.hardware.radio.Announcement.OnListUpdatedListener);
     field public static final int BAND_AM = 0; // 0x0
     field public static final int BAND_AM_HD = 3; // 0x3
     field public static final int BAND_FM = 1; // 0x1
@@ -1963,12 +1987,15 @@
   public static class RadioManager.ProgramInfo implements android.os.Parcelable {
     method public int describeContents();
     method public deprecated int getChannel();
+    method public android.hardware.radio.ProgramSelector.Identifier getLogicallyTunedTo();
     method public android.hardware.radio.RadioMetadata getMetadata();
+    method public android.hardware.radio.ProgramSelector.Identifier getPhysicallyTunedTo();
+    method public java.util.Collection<android.hardware.radio.ProgramSelector.Identifier> getRelatedContent();
     method public android.hardware.radio.ProgramSelector getSelector();
     method public int getSignalStrength();
     method public deprecated int getSubChannel();
     method public java.util.Map<java.lang.String, java.lang.String> getVendorInfo();
-    method public boolean isDigital();
+    method public deprecated boolean isDigital();
     method public boolean isLive();
     method public boolean isMuted();
     method public boolean isStereo();
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 7a7a2f6..2526400 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -92,10 +92,20 @@
 
 void StatsLogProcessor::mapIsolatedUidToHostUidIfNecessaryLocked(LogEvent* event) const {
     std::vector<Field> uidFields;
-    findFields(
-        event->getFieldValueMap(),
-        buildAttributionUidFieldMatcher(event->GetTagId(), Position::ANY),
-        &uidFields);
+    if (android::util::kAtomsWithAttributionChain.find(event->GetTagId()) !=
+        android::util::kAtomsWithAttributionChain.end()) {
+        findFields(
+            event->getFieldValueMap(),
+            buildAttributionUidFieldMatcher(event->GetTagId(), Position::ANY),
+            &uidFields);
+    } else if (android::util::kAtomsWithUidField.find(event->GetTagId()) !=
+               android::util::kAtomsWithUidField.end()) {
+        findFields(
+            event->getFieldValueMap(),
+            buildSimpleAtomFieldMatcher(event->GetTagId(), 1 /* uid is always the 1st field. */),
+            &uidFields);
+    }
+
     for (size_t i = 0; i < uidFields.size(); ++i) {
         DimensionsValue* value = event->findFieldValueOrNull(uidFields[i]);
         if (value != nullptr && value->value_case() == DimensionsValue::ValueCase::kValueInt) {
@@ -201,6 +211,10 @@
 
 void StatsLogProcessor::onDumpReport(const ConfigKey& key, vector<uint8_t>* outData) {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
+    onDumpReportLocked(key, outData);
+}
+
+void StatsLogProcessor::onDumpReportLocked(const ConfigKey& key, vector<uint8_t>* outData) {
     auto it = mMetricsManagers.find(key);
     if (it == mMetricsManagers.end()) {
         ALOGW("Config source %s does not exist", key.ToString().c_str());
@@ -309,7 +323,7 @@
     for (auto& pair : mMetricsManagers) {
         const ConfigKey& key = pair.first;
         vector<uint8_t> data;
-        onDumpReport(key, &data);
+        onDumpReportLocked(key, &data);
         // TODO: Add a guardrail to prevent accumulation of file on disk.
         string file_name = StringPrintf("%s/%d-%lld-%ld", STATS_DATA_DIR, key.GetUid(),
                                         (long long)key.GetId(), time(nullptr));
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 301e3a5..fb85aa8 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -75,6 +75,8 @@
 
     sp<AnomalyMonitor> mAnomalyMonitor;
 
+    void onDumpReportLocked(const ConfigKey& key, vector<uint8_t>* outData);
+
     /* Check if we should send a broadcast if approaching memory limits and if we're over, we
      * actually delete the data. */
     void flushIfNecessaryLocked(uint64_t timestampNs, const ConfigKey& key,
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 7f0ebb4..77b156f8 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -104,7 +104,7 @@
         CpuTimePerUidFreq cpu_time_per_uid_freq = 10010;
         WifiActivityEnergyInfo wifi_activity_energy_info = 10011;
         ModemActivityInfo modem_activity_info = 10012;
-        MemoryStat memory_stat = 10013;
+        ProcessMemoryStat process_memory_stat = 10013;
         CpuSuspendTime cpu_suspend_time = 10014;
         CpuIdleTime cpu_idle_time = 10015;
         CpuActiveTime cpu_active_time = 10016;
@@ -1233,12 +1233,12 @@
 /*
  * Logs the memory stats for a process
  */
-message MemoryStat {
+message ProcessMemoryStat {
     // The uid if available. -1 means not available.
     optional int32 uid = 1;
 
-    // The app package name.
-    optional string pkg_name = 2;
+    // The process name.
+    optional string process_name = 2;
 
     // # of page-faults
     optional int64 pgfault = 3;
@@ -1259,19 +1259,20 @@
     // The uid if available. -1 means not available.
     optional int32 uid = 1;
 
-    // The app package name.
-    optional string pkg_name = 2;
+    // The process name.
+    optional string process_name = 2;
 
     // oom adj score.
     optional int32 oom_score = 3;
 
-    // Used as start/stop boundaries for the event
-    enum State {
-        UNKNOWN = 0;
-        START = 1;
-        END = 2;
-    }
-    optional State state = 4;
+    // # of page-faults
+    optional int64 pgfault = 4;
+
+    // # of major page-faults
+    optional int64 pgmajfault = 5;
+
+    // RSS+CACHE(+SWAP)
+    optional int64 usage_in_bytes = 6;
 }
 
 /*
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.cpp b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
index afa26f6..ea6586c 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.cpp
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
@@ -154,7 +154,7 @@
             }
         }
         nonSlicedConditionCache[mIndex] = ConditionState::kUnknown;
-        ALOGD("CombinationPredicate %lld sliced may changed? %d", (long long)mConditionId,
+        VLOG("CombinationPredicate %lld sliced may changed? %d", (long long)mConditionId,
             conditionChangedCache[mIndex] == true);
     }
 }
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index fdfa32e..3d6984c 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -58,14 +58,14 @@
     /**
      * Get the timestamp associated with this event.
      */
-    uint64_t GetTimestampNs() const { return mTimestampNs; }
+    inline uint64_t GetTimestampNs() const { return mTimestampNs; }
 
     /**
      * Get the tag for this event.
      */
-    int GetTagId() const { return mTagId; }
+    inline int GetTagId() const { return mTagId; }
 
-    uint32_t GetUid() const {
+    inline uint32_t GetUid() const {
         return mLogUid;
     }
 
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index ef27210..0455f6a 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -112,6 +112,9 @@
 void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
                                              ProtoOutputStream* protoOutput) {
     flushIfNeededLocked(dumpTimeNs);
+    if (mPastBuckets.empty()) {
+        return;
+    }
 
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 58dd464..e26fe56 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -168,6 +168,9 @@
 void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
                                                 ProtoOutputStream* protoOutput) {
     flushIfNeededLocked(dumpTimeNs);
+    if (mPastBuckets.empty()) {
+        return;
+    }
 
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
index 821d8ea4..25c86d0 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -102,6 +102,9 @@
 
 void EventMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
                                              ProtoOutputStream* protoOutput) {
+    if (mProto->size() <= 0) {
+        return;
+    }
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)dumpTimeNs);
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index 17305e3..24dc5b0 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -125,6 +125,9 @@
     VLOG("gauge metric %lld report now...", (long long)mMetricId);
 
     flushIfNeededLocked(dumpTimeNs);
+    if (mPastBuckets.empty()) {
+        return;
+    }
 
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index e985873..ae0c673 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -138,6 +138,9 @@
                                              ProtoOutputStream* protoOutput) {
     VLOG("metric %lld dump report now...", (long long)mMetricId);
     flushIfNeededLocked(dumpTimeNs);
+    if (mPastBuckets.empty()) {
+        return;
+    }
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
     long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_VALUE_METRICS);
diff --git a/cmds/uiautomator/instrumentation/Android.mk b/cmds/uiautomator/instrumentation/Android.mk
index ed99f3e..e887539 100644
--- a/cmds/uiautomator/instrumentation/Android.mk
+++ b/cmds/uiautomator/instrumentation/Android.mk
@@ -21,7 +21,7 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_SRC_FILES := $(call all-java-files-under, testrunner-src) \
     $(call all-java-files-under, ../library/core-src)
-LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base.stubs
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 LOCAL_STATIC_JAVA_LIBRARIES := junit
 LOCAL_MODULE := uiautomator-instrumentation
 # TODO: change this to 18 when it's available
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 0a5b848..cd029c0 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -17,6 +17,7 @@
 package android.app;
 
 import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
+
 import static java.lang.Character.MIN_VALUE;
 
 import android.annotation.CallSuper;
@@ -2618,6 +2619,7 @@
      * @param id the ID to search for
      * @return a view with given ID if found, or {@code null} otherwise
      * @see View#findViewById(int)
+     * @see Activity#requireViewById(int)
      */
     @Nullable
     public <T extends View> T findViewById(@IdRes int id) {
@@ -2625,6 +2627,30 @@
     }
 
     /**
+     * Finds a view that was  identified by the {@code android:id} XML attribute that was processed
+     * in {@link #onCreate}, or throws an IllegalArgumentException if the ID is invalid, or there is
+     * no matching view in the hierarchy.
+     * <p>
+     * <strong>Note:</strong> In most cases -- depending on compiler support --
+     * the resulting view is automatically cast to the target class type. If
+     * the target class type is unconstrained, an explicit cast may be
+     * necessary.
+     *
+     * @param id the ID to search for
+     * @return a view with given ID
+     * @see View#requireViewById(int)
+     * @see Activity#findViewById(int)
+     */
+    @NonNull
+    public final <T extends View> T requireViewById(@IdRes int id) {
+        T view = findViewById(id);
+        if (view == null) {
+            throw new IllegalArgumentException("ID does not reference a View inside this Activity");
+        }
+        return view;
+    }
+
+    /**
      * Retrieve a reference to this activity's ActionBar.
      *
      * @return The Activity's ActionBar, or null if it does not have one.
@@ -4671,6 +4697,7 @@
      * their launch had come from the original activity.
      * @param intent The Intent to start.
      * @param options ActivityOptions or null.
+     * @param permissionToken Token received from the system that permits this call to be made.
      * @param ignoreTargetSecurity If true, the activity manager will not check whether the
      * caller it is doing the start is, is actually allowed to start the target activity.
      * If you set this to true, you must set an explicit component in the Intent and do any
@@ -4679,7 +4706,7 @@
      * @hide
      */
     public void startActivityAsCaller(Intent intent, @Nullable Bundle options,
-            boolean ignoreTargetSecurity, int userId) {
+            IBinder permissionToken, boolean ignoreTargetSecurity, int userId) {
         if (mParent != null) {
             throw new RuntimeException("Can't be called from a child");
         }
@@ -4687,7 +4714,7 @@
         Instrumentation.ActivityResult ar =
                 mInstrumentation.execStartActivityAsCaller(
                         this, mMainThread.getApplicationThread(), mToken, this,
-                        intent, -1, options, ignoreTargetSecurity, userId);
+                        intent, -1, options, permissionToken, ignoreTargetSecurity, userId);
         if (ar != null) {
             mMainThread.sendActivityResult(
                 mToken, mEmbeddedID, -1, ar.getResultCode(),
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 4554584..b5a9412 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -443,6 +443,31 @@
      */
     public static final int INTENT_SENDER_FOREGROUND_SERVICE = 5;
 
+    /**
+     * Extra included on intents that are delegating the call to
+     * ActivityManager#startActivityAsCaller to another app.  This token is necessary for that call
+     * to succeed.  Type is IBinder.
+     * @hide
+     */
+    public static final String EXTRA_PERMISSION_TOKEN = "android.app.extra.PERMISSION_TOKEN";
+
+    /**
+     * Extra included on intents that contain an EXTRA_INTENT, with options that the contained
+     * intent may want to be started with.  Type is Bundle.
+     * TODO: remove once the ChooserActivity moves to systemui
+     * @hide
+     */
+    public static final String EXTRA_OPTIONS = "android.app.extra.OPTIONS";
+
+    /**
+     * Extra included on intents that contain an EXTRA_INTENT, use this boolean value for the
+     * parameter of the same name when starting the contained intent.
+     * TODO: remove once the ChooserActivity moves to systemui
+     * @hide
+     */
+    public static final String EXTRA_IGNORE_TARGET_SECURITY =
+            "android.app.extra.EXTRA_IGNORE_TARGET_SECURITY";
+
     /** @hide User operation call: success! */
     public static final int USER_OP_SUCCESS = 0;
 
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index b162cb1..2b648ea 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -16,10 +16,6 @@
 
 package android.app;
 
-import com.android.internal.R;
-import com.android.internal.app.WindowDecorActionBar;
-import com.android.internal.policy.PhoneWindow;
-
 import android.annotation.CallSuper;
 import android.annotation.DrawableRes;
 import android.annotation.IdRes;
@@ -32,8 +28,8 @@
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.DialogInterface;
-import android.content.res.Configuration;
 import android.content.pm.ApplicationInfo;
+import android.content.res.Configuration;
 import android.content.res.ResourceId;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
@@ -62,6 +58,10 @@
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
 
+import com.android.internal.R;
+import com.android.internal.app.WindowDecorActionBar;
+import com.android.internal.policy.PhoneWindow;
+
 import java.lang.ref.WeakReference;
 
 /**
@@ -512,6 +512,7 @@
      * @param id the ID to search for
      * @return a view with given ID if found, or {@code null} otherwise
      * @see View#findViewById(int)
+     * @see Dialog#requireViewById(int)
      */
     @Nullable
     public <T extends View> T findViewById(@IdRes int id) {
@@ -519,6 +520,30 @@
     }
 
     /**
+     * Finds the first descendant view with the given ID or throws an IllegalArgumentException if
+     * the ID is invalid (< 0), there is no matching view in the hierarchy, or the dialog has not
+     * yet been fully created (for example, via {@link #show()} or {@link #create()}).
+     * <p>
+     * <strong>Note:</strong> In most cases -- depending on compiler support --
+     * the resulting view is automatically cast to the target class type. If
+     * the target class type is unconstrained, an explicit cast may be
+     * necessary.
+     *
+     * @param id the ID to search for
+     * @return a view with given ID
+     * @see View#requireViewById(int)
+     * @see Dialog#findViewById(int)
+     */
+    @NonNull
+    public final <T extends View> T requireViewById(@IdRes int id) {
+        T view = findViewById(id);
+        if (view == null) {
+            throw new IllegalArgumentException("ID does not reference a View inside this Dialog");
+        }
+        return view;
+    }
+
+    /**
      * Set the screen content from a layout resource.  The resource will be
      * inflated, adding all top-level views to the screen.
      * 
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 04ee77d..5f5d834 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -438,10 +438,11 @@
     boolean isTopOfTask(in IBinder token);
     void notifyLaunchTaskBehindComplete(in IBinder token);
     void notifyEnterAnimationComplete(in IBinder token);
+    IBinder requestStartActivityPermissionToken(in IBinder delegatorToken);
     int startActivityAsCaller(in IApplicationThread caller, in String callingPackage,
             in Intent intent, in String resolvedType, in IBinder resultTo, in String resultWho,
             int requestCode, int flags, in ProfilerInfo profilerInfo, in Bundle options,
-            boolean ignoreTargetSecurity, int userId);
+            in IBinder permissionToken, boolean ignoreTargetSecurity, int userId);
     int addAppTask(in IBinder activityToken, in Intent intent,
             in ActivityManager.TaskDescription description, in Bitmap thumbnail);
     Point getAppTaskThumbnailSize();
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index c5a58f2..3c38a4e 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1874,8 +1874,8 @@
      */
     public ActivityResult execStartActivityAsCaller(
             Context who, IBinder contextThread, IBinder token, Activity target,
-            Intent intent, int requestCode, Bundle options, boolean ignoreTargetSecurity,
-            int userId) {
+            Intent intent, int requestCode, Bundle options, IBinder permissionToken,
+            boolean ignoreTargetSecurity, int userId) {
         IApplicationThread whoThread = (IApplicationThread) contextThread;
         if (mActivityMonitors != null) {
             synchronized (mSync) {
@@ -1906,7 +1906,8 @@
                 .startActivityAsCaller(whoThread, who.getBasePackageName(), intent,
                         intent.resolveTypeIfNeeded(who.getContentResolver()),
                         token, target != null ? target.mEmbeddedID : null,
-                        requestCode, 0, null, options, ignoreTargetSecurity, userId);
+                        requestCode, 0, null, options, permissionToken,
+                        ignoreTargetSecurity, userId);
             checkStartActivityResult(result, intent);
         } catch (RemoteException e) {
             throw new RuntimeException("Failure from system", e);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 45bed5d..49c03ab 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -93,6 +93,18 @@
     private static boolean localLOGV = false;
 
     /**
+     * Intent that is broadcast when an application is blocked or unblocked.
+     *
+     * This broadcast is only sent to the app whose block state has changed.
+     *
+     * Input: nothing
+     * Output: nothing
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_APP_BLOCK_STATE_CHANGED =
+            "android.app.action.APP_BLOCK_STATE_CHANGED";
+
+    /**
      * Intent that is broadcast when a {@link NotificationChannel} is blocked
      * (when {@link NotificationChannel#getImportance()} is {@link #IMPORTANCE_NONE}) or unblocked
      * (when {@link NotificationChannel#getImportance()} is anything other than
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 256c479..ea0fd75 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -471,14 +471,6 @@
      * {@link #onStart} and returns either {@link #START_STICKY}
      * or {@link #START_STICKY_COMPATIBILITY}.
      * 
-     * <p>If you need your application to run on platform versions prior to API
-     * level 5, you can use the following model to handle the older {@link #onStart}
-     * callback in that case.  The <code>handleCommand</code> method is implemented by
-     * you as appropriate:
-     * 
-     * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/ForegroundService.java
-     *   start_compatibility}
-     *
      * <p class="caution">Note that the system calls this on your
      * service's main thread.  A service's main thread is the same
      * thread where UI operations take place for Activities running in the
@@ -687,6 +679,10 @@
      * {@link #startService(Intent)} first to tell the system it should keep the service running,
      * and then use this method to tell it to keep it running harder.</p>
      *
+     * <p>Apps targeting API {@link android.os.Build.VERSION_CODES#P} or later must request
+     * the permission {@link android.Manifest.permission#FOREGROUND_SERVICE} in order to use
+     * this API.</p>
+     *
      * @param id The identifier for this notification as per
      * {@link NotificationManager#notify(int, Notification)
      * NotificationManager.notify(int, Notification)}; must not be 0.
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index ffb3aff..28e845a 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -483,19 +483,28 @@
             "android.app.action.TRANSFER_OWNERSHIP_COMPLETE";
 
     /**
+     * Broadcast action: notify the device owner that the ownership of one of its affiliated
+     * profiles is transferred.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE =
+            "android.app.action.AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE";
+
+    /**
      * A {@link android.os.Parcelable} extra of type {@link android.os.PersistableBundle} that
      * allows a mobile device management application to pass data to the management application
      * instance after owner transfer.
      *
-     * <p>
-     * If the transfer is successful, the new device owner receives the data in
+     * <p>If the transfer is successful, the new owner receives the data in
      * {@link DeviceAdminReceiver#onTransferOwnershipComplete(Context, PersistableBundle)}.
      * The bundle is not changed during the ownership transfer.
      *
      * @see DevicePolicyManager#transferOwnership(ComponentName, ComponentName, PersistableBundle)
      */
-    public static final String EXTRA_TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE =
-            "android.app.extra.TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE";
+    public static final String EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE =
+            "android.app.extra.TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE";
 
     /**
      * Name under which a device administration component indicates whether it supports transfer of
@@ -994,6 +1003,26 @@
     }
 
     /**
+     * Called on the device owner when the ownership of one of its affiliated profiles is
+     * transferred.
+     *
+     * <p>This can be used when transferring both device and profile ownership when using
+     * work profile on a fully managed device. The process would look like this:
+     * <ol>
+     * <li>Transfer profile ownership</li>
+     * <li>The device owner gets notified with this callback</li>
+     * <li>Transfer device ownership</li>
+     * <li>Both profile and device ownerships have been transferred</li>
+     * </ol>
+     *
+     * @param context the running context as per {@link #onReceive}
+     * @param user the {@link UserHandle} of the affiliated user
+     * @see DevicePolicyManager#transferOwnership(ComponentName, ComponentName, PersistableBundle)
+     */
+    public void onTransferAffiliatedProfileOwnershipComplete(Context context, UserHandle user) {
+    }
+
+    /**
      * Intercept standard device administrator broadcasts.  Implementations
      * should not override this method; it is better to implement the
      * convenience callbacks for each action.
@@ -1063,8 +1092,11 @@
             onUserSwitched(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER));
         } else if (ACTION_TRANSFER_OWNERSHIP_COMPLETE.equals(action)) {
             PersistableBundle bundle =
-                    intent.getParcelableExtra(EXTRA_TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE);
+                    intent.getParcelableExtra(EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE);
             onTransferOwnershipComplete(context, bundle);
+        } else if (ACTION_AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE.equals(action)) {
+            onTransferAffiliatedProfileOwnershipComplete(context,
+                    intent.getParcelableExtra(Intent.EXTRA_USER));
         }
     }
 }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 52870b3..8f76032 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -7037,14 +7037,14 @@
      * task. From {@link android.os.Build.VERSION_CODES#M} removing packages from the lock task
      * package list results in locked tasks belonging to those packages to be finished.
      * <p>
-     * This function can only be called by the device owner or by a profile owner of a user/profile
-     * that is affiliated with the device. See {@link #isAffiliatedUser}. Any packages
-     * set via this method will be cleared if the user becomes unaffiliated.
+     * This function can only be called by the device owner, a profile owner of an affiliated user
+     * or profile, or the profile owner when no device owner is set. See {@link #isAffiliatedUser}.
+     * Any package set via this method will be cleared if the user becomes unaffiliated.
      *
      * @param packages The list of packages allowed to enter lock task mode
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
-     * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of
-     * an affiliated user or profile.
+     * @throws SecurityException if {@code admin} is not the device owner, the profile owner of an
+     * affiliated user or profile, or the profile owner when no device owner is set.
      * @see #isAffiliatedUser
      * @see Activity#startLockTask()
      * @see DeviceAdminReceiver#onLockTaskModeEntering(Context, Intent, String)
@@ -7066,8 +7066,8 @@
     /**
      * Returns the list of packages allowed to start the lock task mode.
      *
-     * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of
-     * an affiliated user or profile.
+     * @throws SecurityException if {@code admin} is not the device owner, the profile owner of an
+     * affiliated user or profile, or the profile owner when no device owner is set.
      * @see #isAffiliatedUser
      * @see #setLockTaskPackages
      */
@@ -7107,9 +7107,9 @@
      * is in LockTask mode. If this method is not called, none of the features listed here will be
      * enabled.
      * <p>
-     * This function can only be called by the device owner or by a profile owner of a user/profile
-     * that is affiliated with the device. See {@link #isAffiliatedUser}. Any features
-     * set via this method will be cleared if the user becomes unaffiliated.
+     * This function can only be called by the device owner, a profile owner of an affiliated user
+     * or profile, or the profile owner when no device owner is set. See {@link #isAffiliatedUser}.
+     * Any features set via this method will be cleared if the user becomes unaffiliated.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param flags Bitfield of feature flags:
@@ -7120,9 +7120,10 @@
      *              {@link #LOCK_TASK_FEATURE_RECENTS},
      *              {@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS},
      *              {@link #LOCK_TASK_FEATURE_KEYGUARD}
-     * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of
-     * an affiliated user or profile.
+     * @throws SecurityException if {@code admin} is not the device owner, the profile owner of an
+     * affiliated user or profile, or the profile owner when no device owner is set.
      * @see #isAffiliatedUser
+     * @throws SecurityException if {@code admin} is not the device owner or the profile owner.
      */
     public void setLockTaskFeatures(@NonNull ComponentName admin, @LockTaskFeature int flags) {
         throwIfParentInstance("setLockTaskFeatures");
@@ -7140,8 +7141,8 @@
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @return bitfield of flags. See {@link #setLockTaskFeatures(ComponentName, int)} for a list.
-     * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of
-     * an affiliated user or profile.
+     * @throws SecurityException if {@code admin} is not the device owner, the profile owner of an
+     * affiliated user or profile, or the profile owner when no device owner is set.
      * @see #isAffiliatedUser
      * @see #setLockTaskFeatures
      */
@@ -9143,9 +9144,13 @@
      *     <li>A profile owner can only be transferred to a new profile owner</li>
      * </ul>
      *
-     * <p>Use the {@code bundle} parameter to pass data to the new administrator. The parameters
+     * <p>Use the {@code bundle} parameter to pass data to the new administrator. The data
      * will be received in the
-     * {@link DeviceAdminReceiver#onTransferOwnershipComplete(Context, PersistableBundle)} callback.
+     * {@link DeviceAdminReceiver#onTransferOwnershipComplete(Context, PersistableBundle)}
+     * callback of the new administrator.
+     *
+     * <p>The transfer has failed if the original administrator is still the corresponding owner
+     * after calling this method.
      *
      * <p>The incoming target administrator must have the
      * {@link DeviceAdminReceiver#SUPPORT_TRANSFER_OWNERSHIP_META_DATA} <code>meta-data</code> tag
@@ -9156,11 +9161,11 @@
      * @param target which {@link DeviceAdminReceiver} we want the new administrator to be
      * @param bundle data to be sent to the new administrator
      * @throws SecurityException if {@code admin} is not a device owner nor a profile owner
-     * @throws IllegalArgumentException if {@code admin} or {@code target} is {@code null},
-     * both are components in the same package or {@code target} is not an active admin
+     * @throws IllegalArgumentException if {@code admin} or {@code target} is {@code null}, they
+     * are components in the same package or {@code target} is not an active admin
      */
     public void transferOwnership(@NonNull ComponentName admin, @NonNull ComponentName target,
-            PersistableBundle bundle) {
+            @Nullable PersistableBundle bundle) {
         throwIfParentInstance("transferOwnership");
         try {
             mService.transferOwnership(admin, target, bundle);
@@ -9285,22 +9290,6 @@
     }
 
     /**
-     * Returns error message to be displayed when printing is disabled.
-     *
-     * Used only by PrintService.
-     * @return Localized error message.
-     * @hide
-     */
-    @SystemApi
-    public CharSequence getPrintingDisabledReason() {
-        try {
-            return mService.getPrintingDisabledReason();
-        } catch (RemoteException re) {
-            throw re.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Called by device owner to add an override APN.
      *
      * @param admin which {@link DeviceAdminReceiver} this request is associated with
@@ -9433,4 +9422,24 @@
         }
         return false;
     }
+
+    /**
+     * Returns the data passed from the current administrator to the new administrator during an
+     * ownership transfer. This is the same {@code bundle} passed in
+     * {@link #transferOwnership(ComponentName, ComponentName, PersistableBundle)}.
+     *
+     * <p>Returns <code>null</code> if no ownership transfer was started for the calling user.
+     *
+     * @see #transferOwnership
+     * @see DeviceAdminReceiver#onTransferOwnershipComplete(Context, PersistableBundle)
+     */
+    @Nullable
+    public PersistableBundle getTransferOwnershipBundle() {
+        throwIfParentInstance("getTransferOwnershipBundle");
+        try {
+            return mService.getTransferOwnershipBundle();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 531bef0..ebaf464 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -132,4 +132,13 @@
      * @param userId The user in question
      */
     public abstract boolean canUserHaveUntrustedCredentialReset(@UserIdInt int userId);
+
+    /**
+     * Return text of error message if printing is disabled.
+     * Called by Print Service when printing is disabled by PO or DO when printing is attempted.
+     *
+     * @param userId The user in question
+     * @return localized error message
+     */
+    public abstract CharSequence getPrintingDisabledReasonForUser(@UserIdInt int userId);
 }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index a5ca4cf..daee6b4 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -391,7 +391,9 @@
     boolean isLogoutEnabled();
 
     List<String> getDisallowedSystemApps(in ComponentName admin, int userId, String provisioningAction);
+
     void transferOwnership(in ComponentName admin, in ComponentName target, in PersistableBundle bundle);
+    PersistableBundle getTransferOwnershipBundle();
 
     void setStartUserSessionMessage(in ComponentName admin, in CharSequence startUserSessionMessage);
     void setEndUserSessionMessage(in ComponentName admin, in CharSequence endUserSessionMessage);
@@ -400,7 +402,6 @@
 
     void setPrintingEnabled(in ComponentName admin, boolean enabled);
     boolean isPrintingEnabled();
-    CharSequence getPrintingDisabledReason();
 
     List<String> setMeteredDataDisabled(in ComponentName admin, in List<String> packageNames);
     List<String> getMeteredDataDisabled(in ComponentName admin);
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 7bdf72f..746a090 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1591,6 +1591,13 @@
     /**
      * @hide
      */
+    public boolean isAllowedToUseHiddenApi() {
+        return isSystemApp();
+    }
+
+    /**
+     * @hide
+     */
     @Override
     public Drawable loadDefaultIcon(PackageManager pm) {
         if ((flags & FLAG_EXTERNAL_STORAGE) != 0
diff --git a/core/java/android/content/pm/dex/ArtManager.java b/core/java/android/content/pm/dex/ArtManager.java
index 201cd8d..aa9c46e6 100644
--- a/core/java/android/content/pm/dex/ArtManager.java
+++ b/core/java/android/content/pm/dex/ArtManager.java
@@ -153,4 +153,14 @@
             return true;
         }
     }
+
+    /**
+     * Return the profile name for the given split. If {@code splitName} is null the
+     * method returns the profile name for the base apk.
+     *
+     * @hide
+     */
+    public static String getProfileName(String splitName) {
+        return splitName == null ? "primary.prof" : splitName + ".split.prof";
+    }
 }
diff --git a/core/java/android/hardware/radio/Announcement.aidl b/core/java/android/hardware/radio/Announcement.aidl
new file mode 100644
index 0000000..eeb5951
--- /dev/null
+++ b/core/java/android/hardware/radio/Announcement.aidl
@@ -0,0 +1,20 @@
+/**
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio;
+
+/** @hide */
+parcelable Announcement;
diff --git a/core/java/android/hardware/radio/Announcement.java b/core/java/android/hardware/radio/Announcement.java
new file mode 100644
index 0000000..166fe60
--- /dev/null
+++ b/core/java/android/hardware/radio/Announcement.java
@@ -0,0 +1,133 @@
+/**
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+@SystemApi
+public final class Announcement implements Parcelable {
+
+    /** DAB alarm, RDS emergency program type (PTY 31). */
+    public static final int TYPE_EMERGENCY = 1;
+    /** DAB warning. */
+    public static final int TYPE_WARNING = 2;
+    /** DAB road traffic, RDS TA, HD Radio transportation. */
+    public static final int TYPE_TRAFFIC = 3;
+    /** Weather. */
+    public static final int TYPE_WEATHER = 4;
+    /** News. */
+    public static final int TYPE_NEWS = 5;
+    /** DAB event, special event. */
+    public static final int TYPE_EVENT = 6;
+    /** DAB sport report, RDS sports. */
+    public static final int TYPE_SPORT = 7;
+    /** All others. */
+    public static final int TYPE_MISC = 8;
+    /** @hide */
+    @IntDef(prefix = { "TYPE_" }, value = {
+        TYPE_EMERGENCY,
+        TYPE_WARNING,
+        TYPE_TRAFFIC,
+        TYPE_WEATHER,
+        TYPE_NEWS,
+        TYPE_EVENT,
+        TYPE_SPORT,
+        TYPE_MISC,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Type {}
+
+    /**
+     * Listener of announcement list events.
+     */
+    public interface OnListUpdatedListener {
+        /**
+         * An event called whenever a list of active announcements change.
+         *
+         * The entire list is sent each time a new announcement appears or any ends broadcasting.
+         *
+         * @param activeAnnouncements a full list of active announcements
+         */
+        void onListUpdated(Collection<Announcement> activeAnnouncements);
+    }
+
+    @NonNull private final ProgramSelector mSelector;
+    @Type private final int mType;
+    @NonNull private final Map<String, String> mVendorInfo;
+
+    /** @hide */
+    public Announcement(@NonNull ProgramSelector selector, @Type int type,
+            @NonNull Map<String, String> vendorInfo) {
+        mSelector = Objects.requireNonNull(selector);
+        mType = Objects.requireNonNull(type);
+        mVendorInfo = Objects.requireNonNull(vendorInfo);
+    }
+
+    private Announcement(@NonNull Parcel in) {
+        mSelector = in.readTypedObject(ProgramSelector.CREATOR);
+        mType = in.readInt();
+        mVendorInfo = Utils.readStringMap(in);
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeTypedObject(mSelector, 0);
+        dest.writeInt(mType);
+        Utils.writeStringMap(dest, mVendorInfo);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Parcelable.Creator<Announcement> CREATOR =
+            new Parcelable.Creator<Announcement>() {
+        public Announcement createFromParcel(Parcel in) {
+            return new Announcement(in);
+        }
+
+        public Announcement[] newArray(int size) {
+            return new Announcement[size];
+        }
+    };
+
+    public @NonNull ProgramSelector getSelector() {
+        return mSelector;
+    }
+
+    public @Type int getType() {
+        return mType;
+    }
+
+    public @NonNull Map<String, String> getVendorInfo() {
+        return mVendorInfo;
+    }
+}
diff --git a/core/java/android/hardware/radio/IAnnouncementListener.aidl b/core/java/android/hardware/radio/IAnnouncementListener.aidl
new file mode 100644
index 0000000..b4d974a
--- /dev/null
+++ b/core/java/android/hardware/radio/IAnnouncementListener.aidl
@@ -0,0 +1,24 @@
+/**
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio;
+
+import android.hardware.radio.Announcement;
+
+/** {@hide} */
+oneway interface IAnnouncementListener {
+    void onListUpdated(in List<Announcement> activeAnnouncements);
+}
diff --git a/core/java/android/hardware/radio/ICloseHandle.aidl b/core/java/android/hardware/radio/ICloseHandle.aidl
new file mode 100644
index 0000000..576c03b
--- /dev/null
+++ b/core/java/android/hardware/radio/ICloseHandle.aidl
@@ -0,0 +1,22 @@
+/**
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio;
+
+/** {@hide} */
+interface ICloseHandle {
+    void close();
+}
diff --git a/core/java/android/hardware/radio/IRadioService.aidl b/core/java/android/hardware/radio/IRadioService.aidl
index c43fd26..9349cf7 100644
--- a/core/java/android/hardware/radio/IRadioService.aidl
+++ b/core/java/android/hardware/radio/IRadioService.aidl
@@ -16,6 +16,8 @@
 
 package android.hardware.radio;
 
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
 import android.hardware.radio.ITuner;
 import android.hardware.radio.ITunerCallback;
 import android.hardware.radio.RadioManager;
@@ -30,4 +32,7 @@
 
     ITuner openTuner(int moduleId, in RadioManager.BandConfig bandConfig, boolean withAudio,
             in ITunerCallback callback);
+
+    ICloseHandle addAnnouncementListener(in int[] enabledTypes,
+            in IAnnouncementListener listener);
 }
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index 0cf7605..0294a29 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -27,6 +27,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
 import java.util.stream.Stream;
@@ -363,6 +364,38 @@
     }
 
     /**
+     * Creates an equivalent ProgramSelector with a given secondary identifier preferred.
+     *
+     * Used to point to a specific physical identifier for technologies that may broadcast the same
+     * program on different channels. For example, with a DAB program broadcasted over multiple
+     * ensembles, the radio hardware may select the one with the strongest signal. The UI may select
+     * preferred ensemble though, so the radio hardware may try to use it in the first place.
+     *
+     * This is a best-effort hint for the tuner, not a guaranteed behavior.
+     *
+     * Setting the given secondary identifier as preferred means filtering out other secondary
+     * identifiers of its type and adding it to the list.
+     *
+     * @param preferred preferred secondary identifier
+     * @return a new ProgramSelector with a given secondary identifier preferred
+     */
+    public @NonNull ProgramSelector withSecondaryPreferred(@NonNull Identifier preferred) {
+        int preferredType = preferred.getType();
+        Identifier[] secondaryIds = Stream.concat(
+            // remove other identifiers of that type
+            Arrays.stream(mSecondaryIds).filter(id -> id.getType() != preferredType),
+            // add preferred identifier instead
+            Stream.of(preferred)).toArray(Identifier[]::new);
+
+        return new ProgramSelector(
+            mProgramType,
+            mPrimaryId,
+            secondaryIds,
+            mVendorIds
+        );
+    }
+
+    /**
      * Builds new ProgramSelector for AM/FM frequency.
      *
      * @param band the band.
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 56668ac..b00f603 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -17,8 +17,10 @@
 package android.hardware.radio;
 
 import android.Manifest;
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
@@ -32,14 +34,19 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.internal.util.Preconditions;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.Executor;
 import java.util.stream.Collectors;
 
 /**
@@ -1380,35 +1387,44 @@
         };
     }
 
-    /** Radio program information returned by
-     * {@link RadioTuner#getProgramInformation(RadioManager.ProgramInfo[])} */
+    /** Radio program information. */
     public static class ProgramInfo implements Parcelable {
 
-        // sourced from hardware/interfaces/broadcastradio/1.1/types.hal
+        // sourced from hardware/interfaces/broadcastradio/2.0/types.hal
         private static final int FLAG_LIVE = 1 << 0;
         private static final int FLAG_MUTED = 1 << 1;
         private static final int FLAG_TRAFFIC_PROGRAM = 1 << 2;
         private static final int FLAG_TRAFFIC_ANNOUNCEMENT = 1 << 3;
+        private static final int FLAG_TUNED = 1 << 4;
+        private static final int FLAG_STEREO = 1 << 5;
 
         @NonNull private final ProgramSelector mSelector;
-        private final boolean mTuned;  // TODO(b/69958777): replace with mFlags
-        private final boolean mStereo;
-        private final boolean mDigital;
-        private final int mFlags;
-        private final int mSignalStrength;
-        private final RadioMetadata mMetadata;
+        @Nullable private final ProgramSelector.Identifier mLogicallyTunedTo;
+        @Nullable private final ProgramSelector.Identifier mPhysicallyTunedTo;
+        @NonNull private final Collection<ProgramSelector.Identifier> mRelatedContent;
+        private final int mInfoFlags;
+        private final int mSignalQuality;
+        @Nullable private final RadioMetadata mMetadata;
         @NonNull private final Map<String, String> mVendorInfo;
 
         /** @hide */
-        public ProgramInfo(@NonNull ProgramSelector selector, boolean tuned, boolean stereo,
-                boolean digital, int signalStrength, RadioMetadata metadata, int flags,
-                Map<String, String> vendorInfo) {
-            mSelector = selector;
-            mTuned = tuned;
-            mStereo = stereo;
-            mDigital = digital;
-            mFlags = flags;
-            mSignalStrength = signalStrength;
+        public ProgramInfo(@NonNull ProgramSelector selector,
+                @Nullable ProgramSelector.Identifier logicallyTunedTo,
+                @Nullable ProgramSelector.Identifier physicallyTunedTo,
+                @Nullable Collection<ProgramSelector.Identifier> relatedContent,
+                int infoFlags, int signalQuality, @Nullable RadioMetadata metadata,
+                @Nullable Map<String, String> vendorInfo) {
+            mSelector = Objects.requireNonNull(selector);
+            mLogicallyTunedTo = logicallyTunedTo;
+            mPhysicallyTunedTo = physicallyTunedTo;
+            if (relatedContent == null) {
+                mRelatedContent = Collections.emptyList();
+            } else {
+                Preconditions.checkCollectionElementsNotNull(relatedContent, "relatedContent");
+                mRelatedContent = relatedContent;
+            }
+            mInfoFlags = infoFlags;
+            mSignalQuality = signalQuality;
             mMetadata = metadata;
             mVendorInfo = (vendorInfo == null) ? new HashMap<>() : vendorInfo;
         }
@@ -1422,6 +1438,51 @@
             return mSelector;
         }
 
+        /**
+         * Identifier currently used for program selection.
+         *
+         * This identifier can be used to determine which technology is
+         * currently being used for reception.
+         *
+         * Some program selectors contain tuning information for different radio
+         * technologies (i.e. FM RDS and DAB). For example, user may tune using
+         * a ProgramSelector with RDS_PI primary identifier, but the tuner hardware
+         * may choose to use DAB technology to make actual tuning. This identifier
+         * must reflect that.
+         */
+        public @Nullable ProgramSelector.Identifier getLogicallyTunedTo() {
+            return mLogicallyTunedTo;
+        }
+
+        /**
+         * Identifier currently used by hardware to physically tune to a channel.
+         *
+         * Some radio technologies broadcast the same program on multiple channels,
+         * i.e. with RDS AF the same program may be broadcasted on multiple
+         * alternative frequencies; the same DAB program may be broadcast on
+         * multiple ensembles. This identifier points to the channel to which the
+         * radio hardware is physically tuned to.
+         */
+        public @Nullable ProgramSelector.Identifier getPhysicallyTunedTo() {
+            return mPhysicallyTunedTo;
+        }
+
+        /**
+         * Primary identifiers of related contents.
+         *
+         * Some radio technologies provide pointers to other programs that carry
+         * related content (i.e. DAB soft-links). This field is a list of pointers
+         * to other programs on the program list.
+         *
+         * Please note, that these identifiers does not have to exist on the program
+         * list - i.e. DAB tuner may provide information on FM RDS alternatives
+         * despite not supporting FM RDS. If the system has multiple tuners, another
+         * one may have it on its list.
+         */
+        public @Nullable Collection<ProgramSelector.Identifier> getRelatedContent() {
+            return mRelatedContent;
+        }
+
         /** Main channel expressed in units according to band type.
          * Currently all defined band types express channels as frequency in kHz
          * @return the program channel
@@ -1456,19 +1517,28 @@
          * @return {@code true} if currently tuned, {@code false} otherwise.
          */
         public boolean isTuned() {
-            return mTuned;
+            return (mInfoFlags & FLAG_TUNED) != 0;
         }
+
         /** {@code true} if the received program is stereo
          * @return {@code true} if stereo, {@code false} otherwise.
          */
         public boolean isStereo() {
-            return mStereo;
+            return (mInfoFlags & FLAG_STEREO) != 0;
         }
+
         /** {@code true} if the received program is digital (e.g HD radio)
          * @return {@code true} if digital, {@code false} otherwise.
+         * @deprecated Use {@link getLogicallyTunedTo()} instead.
          */
+        @Deprecated
         public boolean isDigital() {
-            return mDigital;
+            ProgramSelector.Identifier id = mLogicallyTunedTo;
+            if (id == null) id = mSelector.getPrimaryId();
+
+            int type = id.getType();
+            return (type != ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY
+                && type != ProgramSelector.IDENTIFIER_TYPE_RDS_PI);
         }
 
         /**
@@ -1477,7 +1547,7 @@
          * usually targetted at reduced latency.
          */
         public boolean isLive() {
-            return (mFlags & FLAG_LIVE) != 0;
+            return (mInfoFlags & FLAG_LIVE) != 0;
         }
 
         /**
@@ -1487,7 +1557,7 @@
          * It does NOT mean the user has muted audio.
          */
         public boolean isMuted() {
-            return (mFlags & FLAG_MUTED) != 0;
+            return (mInfoFlags & FLAG_MUTED) != 0;
         }
 
         /**
@@ -1495,7 +1565,7 @@
          * regularily.
          */
         public boolean isTrafficProgram() {
-            return (mFlags & FLAG_TRAFFIC_PROGRAM) != 0;
+            return (mInfoFlags & FLAG_TRAFFIC_PROGRAM) != 0;
         }
 
         /**
@@ -1503,15 +1573,18 @@
          * at the very moment.
          */
         public boolean isTrafficAnnouncementActive() {
-            return (mFlags & FLAG_TRAFFIC_ANNOUNCEMENT) != 0;
+            return (mInfoFlags & FLAG_TRAFFIC_ANNOUNCEMENT) != 0;
         }
 
-        /** Signal strength indicator from 0 (no signal) to 100 (excellent)
-         * @return the signal strength indication.
+        /**
+         * Signal quality (as opposed to the name) indication from 0 (no signal)
+         * to 100 (excellent)
+         * @return the signal quality indication.
          */
         public int getSignalStrength() {
-            return mSignalStrength;
+            return mSignalQuality;
         }
+
         /** Metadata currently received from this station.
          * null if no metadata have been received
          * @return current meta data received from this program.
@@ -1535,17 +1608,13 @@
         }
 
         private ProgramInfo(Parcel in) {
-            mSelector = in.readParcelable(null);
-            mTuned = in.readByte() == 1;
-            mStereo = in.readByte() == 1;
-            mDigital = in.readByte() == 1;
-            mSignalStrength = in.readInt();
-            if (in.readByte() == 1) {
-                mMetadata = RadioMetadata.CREATOR.createFromParcel(in);
-            } else {
-                mMetadata = null;
-            }
-            mFlags = in.readInt();
+            mSelector = Objects.requireNonNull(in.readTypedObject(ProgramSelector.CREATOR));
+            mLogicallyTunedTo = in.readTypedObject(ProgramSelector.Identifier.CREATOR);
+            mPhysicallyTunedTo = in.readTypedObject(ProgramSelector.Identifier.CREATOR);
+            mRelatedContent = in.createTypedArrayList(ProgramSelector.Identifier.CREATOR);
+            mInfoFlags = in.readInt();
+            mSignalQuality = in.readInt();
+            mMetadata = in.readTypedObject(RadioMetadata.CREATOR);
             mVendorInfo = Utils.readStringMap(in);
         }
 
@@ -1562,18 +1631,13 @@
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeParcelable(mSelector, 0);
-            dest.writeByte((byte)(mTuned ? 1 : 0));
-            dest.writeByte((byte)(mStereo ? 1 : 0));
-            dest.writeByte((byte)(mDigital ? 1 : 0));
-            dest.writeInt(mSignalStrength);
-            if (mMetadata == null) {
-                dest.writeByte((byte)0);
-            } else {
-                dest.writeByte((byte)1);
-                mMetadata.writeToParcel(dest, flags);
-            }
-            dest.writeInt(mFlags);
+            dest.writeTypedObject(mSelector, flags);
+            dest.writeTypedObject(mLogicallyTunedTo, flags);
+            dest.writeTypedObject(mPhysicallyTunedTo, flags);
+            Utils.writeTypedCollection(dest, mRelatedContent);
+            dest.writeInt(mInfoFlags);
+            dest.writeInt(mSignalQuality);
+            dest.writeTypedObject(mMetadata, flags);
             Utils.writeStringMap(dest, mVendorInfo);
         }
 
@@ -1584,52 +1648,38 @@
 
         @Override
         public String toString() {
-            return "ProgramInfo [mSelector=" + mSelector
-                    + ", mTuned=" + mTuned + ", mStereo=" + mStereo + ", mDigital=" + mDigital
-                    + ", mFlags=" + mFlags + ", mSignalStrength=" + mSignalStrength
-                    + ((mMetadata == null) ? "" : (", mMetadata=" + mMetadata.toString()))
+            return "ProgramInfo"
+                    + " [selector=" + mSelector
+                    + ", logicallyTunedTo=" + Objects.toString(mLogicallyTunedTo)
+                    + ", physicallyTunedTo=" + Objects.toString(mPhysicallyTunedTo)
+                    + ", relatedContent=" + mRelatedContent.size()
+                    + ", infoFlags=" + mInfoFlags
+                    + ", mSignalQuality=" + mSignalQuality
+                    + ", mMetadata=" + Objects.toString(mMetadata)
                     + "]";
         }
 
         @Override
         public int hashCode() {
-            final int prime = 31;
-            int result = 1;
-            result = prime * result + mSelector.hashCode();
-            result = prime * result + (mTuned ? 1 : 0);
-            result = prime * result + (mStereo ? 1 : 0);
-            result = prime * result + (mDigital ? 1 : 0);
-            result = prime * result + mFlags;
-            result = prime * result + mSignalStrength;
-            result = prime * result + ((mMetadata == null) ? 0 : mMetadata.hashCode());
-            result = prime * result + mVendorInfo.hashCode();
-            return result;
+            return Objects.hash(mSelector, mLogicallyTunedTo, mPhysicallyTunedTo,
+                mRelatedContent, mInfoFlags, mSignalQuality, mMetadata, mVendorInfo);
         }
 
         @Override
         public boolean equals(Object obj) {
-            if (this == obj)
-                return true;
-            if (!(obj instanceof ProgramInfo))
-                return false;
+            if (this == obj) return true;
+            if (!(obj instanceof ProgramInfo)) return false;
             ProgramInfo other = (ProgramInfo) obj;
-            if (!mSelector.equals(other.getSelector())) return false;
-            if (mTuned != other.isTuned())
-                return false;
-            if (mStereo != other.isStereo())
-                return false;
-            if (mDigital != other.isDigital())
-                return false;
-            if (mFlags != other.mFlags)
-                return false;
-            if (mSignalStrength != other.getSignalStrength())
-                return false;
-            if (mMetadata == null) {
-                if (other.getMetadata() != null)
-                    return false;
-            } else if (!mMetadata.equals(other.getMetadata()))
-                return false;
-            if (!mVendorInfo.equals(other.mVendorInfo)) return false;
+
+            if (!Objects.equals(mSelector, other.mSelector)) return false;
+            if (!Objects.equals(mLogicallyTunedTo, other.mLogicallyTunedTo)) return false;
+            if (!Objects.equals(mPhysicallyTunedTo, other.mPhysicallyTunedTo)) return false;
+            if (!Objects.equals(mRelatedContent, other.mRelatedContent)) return false;
+            if (mInfoFlags != other.mInfoFlags) return false;
+            if (mSignalQuality != other.mSignalQuality) return false;
+            if (!Objects.equals(mMetadata, other.mMetadata)) return false;
+            if (!Objects.equals(mVendorInfo, other.mVendorInfo)) return false;
+
             return true;
         }
     }
@@ -1713,6 +1763,68 @@
                 config != null ? config.getType() : BAND_INVALID);
     }
 
+    private final Map<Announcement.OnListUpdatedListener, ICloseHandle> mAnnouncementListeners =
+            new HashMap<>();
+
+    /**
+     * Adds new announcement listener.
+     *
+     * @param enabledAnnouncementTypes a set of announcement types to listen to
+     * @param listener announcement listener
+     */
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+    public void addAnnouncementListener(@NonNull Set<Integer> enabledAnnouncementTypes,
+            @NonNull Announcement.OnListUpdatedListener listener) {
+        addAnnouncementListener(cmd -> cmd.run(), enabledAnnouncementTypes, listener);
+    }
+
+    /**
+     * Adds new announcement listener with executor.
+     *
+     * @param executor the executor
+     * @param enabledAnnouncementTypes a set of announcement types to listen to
+     * @param listener announcement listener
+     */
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+    public void addAnnouncementListener(@NonNull @CallbackExecutor Executor executor,
+            @NonNull Set<Integer> enabledAnnouncementTypes,
+            @NonNull Announcement.OnListUpdatedListener listener) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(listener);
+        int[] types = enabledAnnouncementTypes.stream().mapToInt(Integer::intValue).toArray();
+        IAnnouncementListener listenerIface = new IAnnouncementListener.Stub() {
+            public void onListUpdated(List<Announcement> activeAnnouncements) {
+                executor.execute(() -> listener.onListUpdated(activeAnnouncements));
+            }
+        };
+        synchronized (mAnnouncementListeners) {
+            ICloseHandle closeHandle = null;
+            try {
+                closeHandle = mService.addAnnouncementListener(types, listenerIface);
+            } catch (RemoteException ex) {
+                ex.rethrowFromSystemServer();
+            }
+            Objects.requireNonNull(closeHandle);
+            ICloseHandle oldCloseHandle = mAnnouncementListeners.put(listener, closeHandle);
+            if (oldCloseHandle != null) Utils.close(oldCloseHandle);
+        }
+    }
+
+    /**
+     * Removes previously registered announcement listener.
+     *
+     * @param listener announcement listener, previously registered with
+     *        {@link addAnnouncementListener}
+     */
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+    public void removeAnnouncementListener(@NonNull Announcement.OnListUpdatedListener listener) {
+        Objects.requireNonNull(listener);
+        synchronized (mAnnouncementListeners) {
+            ICloseHandle closeHandle = mAnnouncementListeners.remove(listener);
+            if (closeHandle != null) Utils.close(closeHandle);
+        }
+    }
+
     @NonNull private final Context mContext;
     @NonNull private final IRadioService mService;
 
diff --git a/core/java/android/hardware/radio/Utils.java b/core/java/android/hardware/radio/Utils.java
index 09bf8fe..f1b5897 100644
--- a/core/java/android/hardware/radio/Utils.java
+++ b/core/java/android/hardware/radio/Utils.java
@@ -20,7 +20,10 @@
 import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.RemoteException;
 
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -28,6 +31,8 @@
 import java.util.Set;
 
 final class Utils {
+    private static final String TAG = "BroadcastRadio.utils";
+
     static void writeStringMap(@NonNull Parcel dest, @Nullable Map<String, String> map) {
         if (map == null) {
             dest.writeInt(0);
@@ -89,4 +94,25 @@
             }
         });
     }
+
+    static <T extends Parcelable> void writeTypedCollection(@NonNull Parcel dest,
+            @Nullable Collection<T> coll) {
+        ArrayList<T> list = null;
+        if (coll != null) {
+            if (coll instanceof ArrayList) {
+                list = (ArrayList) coll;
+            } else {
+                list = new ArrayList<>(coll);
+            }
+        }
+        dest.writeTypedList(list);
+    }
+
+    static void close(ICloseHandle handle) {
+        try {
+            handle.close();
+        } catch (RemoteException ex) {
+            ex.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 48f5684..fc78861 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -894,6 +894,14 @@
 
         /**
          * P.
+         *
+         * <p>Applications targeting this or a later release will get these
+         * new changes in behavior:</p>
+         * <ul>
+         * <li>{@link android.app.Service#startForeground Service.startForeground} requires
+         * that apps hold the permission
+         * {@link android.Manifest.permission#FOREGROUND_SERVICE}.</li>
+         * </ul>
          */
         public static final int P = CUR_DEVELOPMENT; // STOPSHIP Replace with the real version.
     }
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index b1794a6..62731e8 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -292,6 +292,16 @@
     }
 
     /** {@hide} */
+    public static File getDataVendorCeDirectory(int userId) {
+        return buildPath(getDataDirectory(), "vendor_ce", String.valueOf(userId));
+    }
+
+    /** {@hide} */
+    public static File getDataVendorDeDirectory(int userId) {
+        return buildPath(getDataDirectory(), "vendor_de", String.valueOf(userId));
+    }
+
+    /** {@hide} */
     public static File getProfileSnapshotPath(String packageName, String codePath) {
         return buildPath(buildPath(getDataDirectory(), "misc", "profiles", "ref", packageName,
                 "primary.prof.snapshot"));
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4b9f589..daf6bd5 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7602,6 +7602,13 @@
         public static final String BACKUP_MANAGER_CONSTANTS = "backup_manager_constants";
 
         /**
+         * Flag to set if the system should predictively attempt to re-enable Bluetooth while
+         * the user is driving.
+         * @hide
+         */
+        public static final String BLUETOOTH_ON_WHILE_DRIVING = "bluetooth_on_while_driving";
+
+        /**
          * This are the settings to be backed up.
          *
          * NOTE: Settings are backed up and restored in the order they appear
@@ -11906,6 +11913,19 @@
          * @hide
          */
         public static final String SHOW_FIRST_CRASH_DIALOG = "show_first_crash_dialog";
+
+        /**
+         * If nonzero, crash dialogs will show an option to restart the app.
+         * @hide
+         */
+        public static final String SHOW_RESTART_IN_CRASH_DIALOG = "show_restart_in_crash_dialog";
+
+        /**
+         * If nonzero, crash dialogs will show an option to mute all future crash dialogs for
+         * this app.
+         * @hide
+         */
+        public static final String SHOW_MUTE_IN_CRASH_DIALOG = "show_mute_in_crash_dialog";
     }
 
     /**
diff --git a/core/java/android/provider/SettingsValidators.java b/core/java/android/provider/SettingsValidators.java
index 84c9e88..5885b6b 100644
--- a/core/java/android/provider/SettingsValidators.java
+++ b/core/java/android/provider/SettingsValidators.java
@@ -100,7 +100,7 @@
             String[] subparts = value.split("\\.");
             boolean isValidPackageName = true;
             for (String subpart : subparts) {
-                isValidPackageName |= isSubpartValidForPackageName(subpart);
+                isValidPackageName &= isSubpartValidForPackageName(subpart);
                 if (!isValidPackageName) break;
             }
             return isValidPackageName;
@@ -110,7 +110,7 @@
             if (subpart.length() == 0) return false;
             boolean isValidSubpart = Character.isLetter(subpart.charAt(0));
             for (int i = 1; i < subpart.length(); i++) {
-                isValidSubpart |= (Character.isLetterOrDigit(subpart.charAt(i))
+                isValidSubpart &= (Character.isLetterOrDigit(subpart.charAt(i))
                                 || (subpart.charAt(i) == '_'));
                 if (!isValidSubpart) break;
             }
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 2a245d0..99e2c62 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -17,6 +17,7 @@
 
 import android.annotation.IdRes;
 import android.annotation.LayoutRes;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
@@ -54,7 +55,6 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.List;
 
 /**
  * Extend this class to implement a custom dream (available to the user as a "Daydream").
@@ -458,8 +458,16 @@
      * was processed in {@link #onCreate}.
      *
      * <p>Note: Requires a window, do not call before {@link #onAttachedToWindow()}</p>
+     * <p>
+     * <strong>Note:</strong> In most cases -- depending on compiler support --
+     * the resulting view is automatically cast to the target class type. If
+     * the target class type is unconstrained, an explicit cast may be
+     * necessary.
      *
+     * @param id the ID to search for
      * @return The view if found or null otherwise.
+     * @see View#findViewById(int)
+     * @see DreamService#requireViewById(int)
      */
     @Nullable
     public <T extends View> T findViewById(@IdRes int id) {
@@ -467,6 +475,33 @@
     }
 
     /**
+     * Finds a view that was identified by the id attribute from the XML that was processed in
+     * {@link #onCreate}, or throws an IllegalArgumentException if the ID is invalid or there is no
+     * matching view in the hierarchy.
+     *
+     * <p>Note: Requires a window, do not call before {@link #onAttachedToWindow()}</p>
+     * <p>
+     * <strong>Note:</strong> In most cases -- depending on compiler support --
+     * the resulting view is automatically cast to the target class type. If
+     * the target class type is unconstrained, an explicit cast may be
+     * necessary.
+     *
+     * @param id the ID to search for
+     * @return a view with given ID
+     * @see View#requireViewById(int)
+     * @see DreamService#findViewById(int)
+     */
+    @NonNull
+    public final <T extends View> T requireViewById(@IdRes int id) {
+        T view = findViewById(id);
+        if (view == null) {
+            throw new IllegalArgumentException(
+                    "ID does not reference a View inside this DreamService");
+        }
+        return view;
+    }
+
+    /**
      * Marks this dream as interactive to receive input events.
      *
      * <p>Non-interactive dreams (default) will dismiss on the first input event.</p>
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 70d6486..80d7ef5 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -928,8 +928,6 @@
         }
     }
 
-    // The parameters that are not changed in the method are marked as final to make the code
-    // easier to understand.
     private int out(final CharSequence text, final int start, final int end, int above, int below,
             int top, int bottom, int v, final float spacingmult, final float spacingadd,
             final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
@@ -958,21 +956,29 @@
             mLineDirections = grow;
         }
 
-        lines[off + START] = start;
-        lines[off + TOP] = v;
+        if (chooseHt != null) {
+            fm.ascent = above;
+            fm.descent = below;
+            fm.top = top;
+            fm.bottom = bottom;
 
-        // Information about hyphenation, tabs, and directions are needed for determining
-        // ellipsization, so the values should be assigned before ellipsization.
+            for (int i = 0; i < chooseHt.length; i++) {
+                if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
+                    ((LineHeightSpan.WithDensity) chooseHt[i])
+                            .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
+                } else {
+                    chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
+                }
+            }
 
-        // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
-        // one bit for start field
-        lines[off + TAB] |= flags & TAB_MASK;
-        lines[off + HYPHEN] = flags;
-        lines[off + DIR] |= dir << DIR_SHIFT;
-        mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart);
+            above = fm.ascent;
+            below = fm.descent;
+            top = fm.top;
+            bottom = fm.bottom;
+        }
 
-        final boolean firstLine = (j == 0);
-        final boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
+        boolean firstLine = (j == 0);
+        boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
 
         if (ellipsize != null) {
             // If there is only one line, then do any type of ellipsis except when it is MARQUEE
@@ -985,9 +991,9 @@
                     (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
                             ellipsize == TextUtils.TruncateAt.END);
             if (doEllipsis) {
-                calculateEllipsis(text, start, end, widths, widthStart,
-                        ellipsisWidth - getTotalInsets(j), ellipsize, j,
-                        textWidth, paint, forceEllipsis, dir);
+                calculateEllipsis(start, end, widths, widthStart,
+                        ellipsisWidth, ellipsize, j,
+                        textWidth, paint, forceEllipsis);
             }
         }
 
@@ -1006,28 +1012,6 @@
             }
         }
 
-        if (chooseHt != null) {
-            fm.ascent = above;
-            fm.descent = below;
-            fm.top = top;
-            fm.bottom = bottom;
-
-            for (int i = 0; i < chooseHt.length; i++) {
-                if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
-                    ((LineHeightSpan.WithDensity) chooseHt[i])
-                        .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
-
-                } else {
-                    chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
-                }
-            }
-
-            above = fm.ascent;
-            below = fm.descent;
-            top = fm.top;
-            bottom = fm.bottom;
-        }
-
         if (firstLine) {
             if (trackPad) {
                 mTopPadding = top - above;
@@ -1038,6 +1022,8 @@
             }
         }
 
+        int extra;
+
         if (lastLine) {
             if (trackPad) {
                 mBottomPadding = bottom - below;
@@ -1048,9 +1034,8 @@
             }
         }
 
-        final int extra;
         if (needMultiply && (addLastLineLineSpacing || !lastLine)) {
-            final double ex = (below - above) * (spacingmult - 1) + spacingadd;
+            double ex = (below - above) * (spacingmult - 1) + spacingadd;
             if (ex >= 0) {
                 extra = (int)(ex + EXTRA_ROUNDING);
             } else {
@@ -1060,6 +1045,8 @@
             extra = 0;
         }
 
+        lines[off + START] = start;
+        lines[off + TOP] = v;
         lines[off + DESCENT] = below + extra;
         lines[off + EXTRA] = extra;
 
@@ -1067,7 +1054,7 @@
         // store the height as if it was ellipsized
         if (!mEllipsized && currentLineIsTheLastVisibleOne) {
             // below calculation as if it was the last line
-            final int maxLineBelow = includePad ? bottom : below;
+            int maxLineBelow = includePad ? bottom : below;
             // similar to the calculation of v below, without the extra.
             mMaxLineHeight = v + (maxLineBelow - above);
         }
@@ -1076,13 +1063,23 @@
         lines[off + mColumns + START] = end;
         lines[off + mColumns + TOP] = v;
 
+        // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
+        // one bit for start field
+        lines[off + TAB] |= flags & TAB_MASK;
+        lines[off + HYPHEN] = flags;
+        lines[off + DIR] |= dir << DIR_SHIFT;
+        mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart);
+
         mLineCount++;
         return v;
     }
 
-    private void calculateEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths,
-            int widthStart, float avail, TextUtils.TruncateAt where, int line, float textWidth,
-            TextPaint paint, boolean forceEllipsis, int dir) {
+    private void calculateEllipsis(int lineStart, int lineEnd,
+                                   float[] widths, int widthStart,
+                                   float avail, TextUtils.TruncateAt where,
+                                   int line, float textWidth, TextPaint paint,
+                                   boolean forceEllipsis) {
+        avail -= getTotalInsets(line);
         if (textWidth <= avail && !forceEllipsis) {
             // Everything fits!
             mLines[mColumns * line + ELLIPSIS_START] = 0;
@@ -1090,53 +1087,11 @@
             return;
         }
 
-        float tempAvail = avail;
-        int numberOfTries = 0;
-        boolean lineFits = false;
-        mWorkPaint.set(paint);
-        do {
-            final float ellipsizedWidth = guessEllipsis(text, lineStart, lineEnd, widths,
-                    widthStart, tempAvail, where, line, mWorkPaint, forceEllipsis, dir);
-            if (ellipsizedWidth <= avail) {
-                lineFits = true;
-            } else {
-                numberOfTries++;
-                if (numberOfTries > 10) {
-                    // If the text still doesn't fit after ten tries, assume it will never fit and
-                    // ellipsize it all.
-                    mLines[mColumns * line + ELLIPSIS_START] = 0;
-                    mLines[mColumns * line + ELLIPSIS_COUNT] = lineEnd - lineStart;
-                    lineFits = true;
-                } else {
-                    // Some side effect of ellipsization has caused the text to go over the
-                    // available width. Let's make the available width shorter by exactly that
-                    // amount and retry.
-                    tempAvail -= ellipsizedWidth - avail;
-                }
-            }
-        } while (!lineFits);
-        mEllipsized = true;
-    }
+        float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
+        int ellipsisStart = 0;
+        int ellipsisCount = 0;
+        int len = lineEnd - lineStart;
 
-    // Returns the width of the ellipsized line which in some rare cases can actually be larger
-    // than 'avail' (due to kerning or other context-based effect of replacement of text by
-    // ellipsis). If all the line needs to ellipsized away, or it's an invalud hyphenation mode,
-    // returns 0 so the caller can stop iterating.
-    //
-    // This method temporarily modifies the TextPaint passed to it, so the TextPaint passed to it
-    // should not be accessed while the method is running.
-    private float guessEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths,
-            int widthStart, float avail, TextUtils.TruncateAt where, int line,
-            TextPaint paint, boolean forceEllipsis, int dir) {
-        final int savedHyphenEdit = paint.getHyphenEdit();
-        paint.setHyphenEdit(0);
-        final float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
-        final int ellipsisStart;
-        final int ellipsisCount;
-        final int len = lineEnd - lineStart;
-        final int offset = lineStart - widthStart;
-
-        int hyphen = getHyphen(line);
         // We only support start ellipsis on a single line
         if (where == TextUtils.TruncateAt.START) {
             if (mMaximumVisibleLineCount == 1) {
@@ -1144,9 +1099,9 @@
                 int i;
 
                 for (i = len; i > 0; i--) {
-                    final float w = widths[i - 1 + offset];
+                    float w = widths[i - 1 + lineStart - widthStart];
                     if (w + sum + ellipsisWidth > avail) {
-                        while (i < len && widths[i + offset] == 0.0f) {
+                        while (i < len && widths[i + lineStart - widthStart] == 0.0f) {
                             i++;
                         }
                         break;
@@ -1157,13 +1112,9 @@
 
                 ellipsisStart = 0;
                 ellipsisCount = i;
-                // Strip the potential hyphenation at beginning of line.
-                hyphen &= ~Paint.HYPHENEDIT_MASK_START_OF_LINE;
             } else {
-                ellipsisStart = 0;
-                ellipsisCount = 0;
                 if (Log.isLoggable(TAG, Log.WARN)) {
-                    Log.w(TAG, "Start ellipsis only supported with one line");
+                    Log.w(TAG, "Start Ellipsis only supported with one line");
                 }
             }
         } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
@@ -1172,7 +1123,7 @@
             int i;
 
             for (i = 0; i < len; i++) {
-                final float w = widths[i + offset];
+                float w = widths[i + lineStart - widthStart];
 
                 if (w + sum + ellipsisWidth > avail) {
                     break;
@@ -1181,27 +1132,24 @@
                 sum += w;
             }
 
-            if (forceEllipsis && i == len && len > 0) {
+            ellipsisStart = i;
+            ellipsisCount = len - i;
+            if (forceEllipsis && ellipsisCount == 0 && len > 0) {
                 ellipsisStart = len - 1;
                 ellipsisCount = 1;
-            } else {
-                ellipsisStart = i;
-                ellipsisCount = len - i;
             }
-            // Strip the potential hyphenation at end of line.
-            hyphen &= ~Paint.HYPHENEDIT_MASK_END_OF_LINE;
-        } else { // where = TextUtils.TruncateAt.MIDDLE
-            // We only support middle ellipsis on a single line.
+        } else {
+            // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
             if (mMaximumVisibleLineCount == 1) {
                 float lsum = 0, rsum = 0;
                 int left = 0, right = len;
 
-                final float ravail = (avail - ellipsisWidth) / 2;
+                float ravail = (avail - ellipsisWidth) / 2;
                 for (right = len; right > 0; right--) {
-                    final float w = widths[right - 1 + offset];
+                    float w = widths[right - 1 + lineStart - widthStart];
 
                     if (w + rsum > ravail) {
-                        while (right < len && widths[right + offset] == 0.0f) {
+                        while (right < len && widths[right + lineStart - widthStart] == 0.0f) {
                             right++;
                         }
                         break;
@@ -1209,9 +1157,9 @@
                     rsum += w;
                 }
 
-                final float lavail = avail - ellipsisWidth - rsum;
+                float lavail = avail - ellipsisWidth - rsum;
                 for (left = 0; left < right; left++) {
-                    final float w = widths[left + offset];
+                    float w = widths[left + lineStart - widthStart];
 
                     if (w + lsum > lavail) {
                         break;
@@ -1223,53 +1171,14 @@
                 ellipsisStart = left;
                 ellipsisCount = right - left;
             } else {
-                ellipsisStart = 0;
-                ellipsisCount = 0;
                 if (Log.isLoggable(TAG, Log.WARN)) {
-                    Log.w(TAG, "Middle ellipsis only supported with one line");
+                    Log.w(TAG, "Middle Ellipsis only supported with one line");
                 }
             }
         }
+        mEllipsized = true;
         mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
         mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
-
-        if (ellipsisStart == 0 && (ellipsisCount == 0 || ellipsisCount == len)) {
-            // Unsupported ellipsization mode or all text is ellipsized away. Return 0.
-            return 0.0f;
-        }
-
-        final boolean isSpanned = text instanceof Spanned;
-        final Ellipsizer ellipsizedText = isSpanned
-                        ? new SpannedEllipsizer(text)
-                        : new Ellipsizer(text);
-        ellipsizedText.mLayout = this;
-        ellipsizedText.mMethod = where;
-
-        final boolean hasTabs = getLineContainsTab(line);
-        final TabStops tabStops;
-        if (hasTabs && isSpanned) {
-            final TabStopSpan[] tabs = getParagraphSpans((Spanned) ellipsizedText, lineStart,
-                    lineEnd, TabStopSpan.class);
-            if (tabs.length == 0) {
-                tabStops = null;
-            } else {
-                tabStops = new TabStops(TAB_INCREMENT, tabs);
-            }
-        } else {
-            tabStops = null;
-        }
-        paint.setHyphenEdit(hyphen);
-        final TextLine textline = TextLine.obtain();
-        textline.set(paint, ellipsizedText, lineStart, lineEnd, dir, getLineDirections(line),
-                hasTabs, tabStops);
-        // Since TextLine.metric() returns negative values for RTL text, multiplication by dir
-        // converts it to an actual width. Note that we don't want to use the absolute value,
-        // since we may actually have glyphs with negative advances, which by definition always
-        // fit.
-        final float ellipsizedWidth = textline.metrics(null) * dir;
-        TextLine.recycle(textline);
-        paint.setHyphenEdit(savedHyphenEdit);
-        return ellipsizedWidth;
     }
 
     private float getTotalInsets(int line) {
@@ -1509,8 +1418,6 @@
      */
     private int mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT;
 
-    private TextPaint mWorkPaint = new TextPaint();
-
     private static final int COLUMNS_NORMAL = 5;
     private static final int COLUMNS_ELLIPSIZE = 7;
     private static final int START = 0;
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 409e514..af0eebf 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -88,8 +88,8 @@
 
     /** {@hide} */
     @NonNull
-    public static String getEllipsisString(@NonNull TruncateAt method) {
-        return (method == TruncateAt.END_SMALL) ? ELLIPSIS_TWO_DOTS : ELLIPSIS_NORMAL;
+    public static String getEllipsisString(@NonNull TextUtils.TruncateAt method) {
+        return (method == TextUtils.TruncateAt.END_SMALL) ? ELLIPSIS_TWO_DOTS : ELLIPSIS_NORMAL;
     }
 
 
@@ -1194,11 +1194,9 @@
      * or, if it does not fit, a truncated
      * copy with ellipsis character added at the specified edge or center.
      */
-    @NonNull
-    public static CharSequence ellipsize(@NonNull CharSequence text,
-                                         @NonNull TextPaint p,
-                                         @FloatRange(from = 0.0) float avail,
-                                         @NonNull TruncateAt where) {
+    public static CharSequence ellipsize(CharSequence text,
+                                         TextPaint p,
+                                         float avail, TruncateAt where) {
         return ellipsize(text, p, avail, where, false, null);
     }
 
@@ -1214,11 +1212,9 @@
      * report the start and end of the ellipsized range.  TextDirection
      * is determined by the first strong directional character.
      */
-    @NonNull
-    public static CharSequence ellipsize(@NonNull CharSequence text,
-                                         @NonNull TextPaint paint,
-                                         @FloatRange(from = 0.0) float avail,
-                                         @NonNull TruncateAt where,
+    public static CharSequence ellipsize(CharSequence text,
+                                         TextPaint paint,
+                                         float avail, TruncateAt where,
                                          boolean preserveLength,
                                          @Nullable EllipsizeCallback callback) {
         return ellipsize(text, paint, avail, where, preserveLength, callback,
@@ -1239,19 +1235,16 @@
      *
      * @hide
      */
-    @NonNull
-    public static CharSequence ellipsize(@NonNull CharSequence text,
-            @NonNull TextPaint paint,
-            @FloatRange(from = 0.0) float avail,
-            @NonNull TruncateAt where,
+    public static CharSequence ellipsize(CharSequence text,
+            TextPaint paint,
+            float avail, TruncateAt where,
             boolean preserveLength,
             @Nullable EllipsizeCallback callback,
-            @NonNull TextDirectionHeuristic textDir,
-            @NonNull String ellipsis) {
+            TextDirectionHeuristic textDir, String ellipsis) {
 
-        final int len = text.length();
+        int len = text.length();
+
         MeasuredParagraph mt = null;
-        MeasuredParagraph resultMt = null;
         try {
             mt = MeasuredParagraph.buildForMeasurement(paint, text, 0, text.length(), textDir, mt);
             float width = mt.getWholeWidth();
@@ -1260,110 +1253,76 @@
                 if (callback != null) {
                     callback.ellipsized(0, 0);
                 }
+
                 return text;
             }
 
-            // First estimate of effective width of ellipsis.
-            float ellipsisWidth = paint.measureText(ellipsis);
-            int numberOfTries = 0;
-            boolean textFits = false;
-            int start, end;
-            CharSequence result;
-            do {
-                if (avail < ellipsisWidth) {
-                    // Even the ellipsis can't fit. So it all goes.
-                    start = 0;
-                    end = len;
-                } else {
-                    final float remainingWidth = avail - ellipsisWidth;
-                    if (where == TruncateAt.START) {
-                        start = 0;
-                        end = len - mt.breakText(len, false /* backwards */, remainingWidth);
-                    } else if (where == TruncateAt.END || where == TruncateAt.END_SMALL) {
-                        start = mt.breakText(len, true /* forwards */, remainingWidth);
-                        end = len;
-                    } else {
-                        end = len - mt.breakText(len, false /* backwards */, remainingWidth / 2);
-                        start = mt.breakText(end, true /* forwards */,
-                                remainingWidth - mt.measure(end, len));
-                    }
-                }
+            // XXX assumes ellipsis string does not require shaping and
+            // is unaffected by style
+            float ellipsiswid = paint.measureText(ellipsis);
+            avail -= ellipsiswid;
 
-                final char[] buf = mt.getChars();
-                final Spanned sp = text instanceof Spanned ? (Spanned) text : null;
-
-                final int removed = end - start;
-                final int remaining = len - removed;
-                if (preserveLength) {
-                    int pos = start;
-                    if (remaining > 0 && removed >= ellipsis.length()) {
-                        ellipsis.getChars(0, ellipsis.length(), buf, start);
-                        pos += ellipsis.length();
-                    } // else eliminate the ellipsis
-                    while (pos < end) {
-                        buf[pos++] = ELLIPSIS_FILLER;
-                    }
-                    final String s = new String(buf, 0, len);
-                    if (sp == null) {
-                        result = s;
-                    } else {
-                        final SpannableString ss = new SpannableString(s);
-                        copySpansFrom(sp, 0, len, Object.class, ss, 0);
-                        result = ss;
-                    }
-                } else {
-                    if (remaining == 0) {
-                        result = "";
-                    } else if (sp == null) {
-                        final StringBuilder sb = new StringBuilder(remaining + ellipsis.length());
-                        sb.append(buf, 0, start);
-                        sb.append(ellipsis);
-                        sb.append(buf, end, len - end);
-                        result = sb.toString();
-                    } else {
-                        final SpannableStringBuilder ssb = new SpannableStringBuilder();
-                        ssb.append(text, 0, start);
-                        ssb.append(ellipsis);
-                        ssb.append(text, end, len);
-                        result = ssb;
-                    }
-                }
-
-                if (remaining == 0) { // All text is gone.
-                    textFits = true;
-                } else {
-                    resultMt = MeasuredParagraph.buildForMeasurement(
-                            paint, result, 0, result.length(), textDir, resultMt);
-                    width = resultMt.getWholeWidth();
-                    if (width <= avail) {
-                        textFits = true;
-                    } else {
-                        numberOfTries++;
-                        if (numberOfTries > 10) {
-                            // If the text still doesn't fit after ten tries, assume it will never
-                            // fit and ellipsize it all. We do this by setting the width of the
-                            // ellipsis to be positive infinity, so we get to empty text in the next
-                            // round.
-                            ellipsisWidth = Float.POSITIVE_INFINITY;
-                        } else {
-                            // Adjust the width of the ellipsis by adding the amount 'width' is
-                            // still over.
-                            ellipsisWidth += width - avail;
-                        }
-                    }
-                }
-            } while (!textFits);
-            if (callback != null) {
-                callback.ellipsized(start, end);
+            int left = 0;
+            int right = len;
+            if (avail < 0) {
+                // it all goes
+            } else if (where == TruncateAt.START) {
+                right = len - mt.breakText(len, false, avail);
+            } else if (where == TruncateAt.END || where == TruncateAt.END_SMALL) {
+                left = mt.breakText(len, true, avail);
+            } else {
+                right = len - mt.breakText(len, false, avail / 2);
+                avail -= mt.measure(right, len);
+                left = mt.breakText(right, true, avail);
             }
-            return result;
+
+            if (callback != null) {
+                callback.ellipsized(left, right);
+            }
+
+            final char[] buf = mt.getChars();
+            Spanned sp = text instanceof Spanned ? (Spanned) text : null;
+
+            final int removed = right - left;
+            final int remaining = len - removed;
+            if (preserveLength) {
+                if (remaining > 0 && removed >= ellipsis.length()) {
+                    ellipsis.getChars(0, ellipsis.length(), buf, left);
+                    left += ellipsis.length();
+                } // else skip the ellipsis
+                for (int i = left; i < right; i++) {
+                    buf[i] = ELLIPSIS_FILLER;
+                }
+                String s = new String(buf, 0, len);
+                if (sp == null) {
+                    return s;
+                }
+                SpannableString ss = new SpannableString(s);
+                copySpansFrom(sp, 0, len, Object.class, ss, 0);
+                return ss;
+            }
+
+            if (remaining == 0) {
+                return "";
+            }
+
+            if (sp == null) {
+                StringBuilder sb = new StringBuilder(remaining + ellipsis.length());
+                sb.append(buf, 0, left);
+                sb.append(ellipsis);
+                sb.append(buf, right, len - right);
+                return sb.toString();
+            }
+
+            SpannableStringBuilder ssb = new SpannableStringBuilder();
+            ssb.append(text, 0, left);
+            ssb.append(ellipsis);
+            ssb.append(text, right, len);
+            return ssb;
         } finally {
             if (mt != null) {
                 mt.recycle();
             }
-            if (resultMt != null) {
-                resultMt.recycle();
-            }
         }
     }
 
@@ -1394,6 +1353,7 @@
      * @return the formatted CharSequence. If even the shortest sequence (e.g. {@code "A, 11 more"})
      *     doesn't fit, it will return an empty string.
      */
+
     public static CharSequence listEllipsize(@Nullable Context context,
             @Nullable List<CharSequence> elements, @NonNull String separator,
             @NonNull TextPaint paint, @FloatRange(from=0.0,fromInclusive=false) float avail,
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index b313514..25a177e 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -46,6 +46,7 @@
         DEFAULT_FLAGS.put("settings_zone_picker_v2", "true");
         DEFAULT_FLAGS.put("settings_suggestion_ui_v2", "false");
         DEFAULT_FLAGS.put("settings_about_phone_v2", "false");
+        DEFAULT_FLAGS.put("settings_bluetooth_while_driving", "false");
     }
 
     /**
diff --git a/core/java/android/util/apk/ApkVerityBuilder.java b/core/java/android/util/apk/ApkVerityBuilder.java
index a0d5e4c..5880c6a 100644
--- a/core/java/android/util/apk/ApkVerityBuilder.java
+++ b/core/java/android/util/apk/ApkVerityBuilder.java
@@ -68,31 +68,78 @@
     static ApkVerityResult generateApkVerity(RandomAccessFile apk,
             SignatureInfo signatureInfo, ByteBufferFactory bufferFactory)
             throws IOException, SecurityException, NoSuchAlgorithmException, DigestException {
+        long signingBlockSize =
+                signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset;
+        long dataSize = apk.length() - signingBlockSize - ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE;
+        int[] levelOffset = calculateVerityLevelOffset(dataSize);
+
+        ByteBuffer output = bufferFactory.create(
+                CHUNK_SIZE_BYTES +  // fsverity header + extensions + padding
+                levelOffset[levelOffset.length - 1]);  // Merkle tree size
+        output.order(ByteOrder.LITTLE_ENDIAN);
+
+        ByteBuffer header = slice(output, 0, FSVERITY_HEADER_SIZE_BYTES);
+        ByteBuffer extensions = slice(output, FSVERITY_HEADER_SIZE_BYTES, CHUNK_SIZE_BYTES);
+        ByteBuffer tree = slice(output, CHUNK_SIZE_BYTES, output.limit());
+        byte[] apkDigestBytes = new byte[DIGEST_SIZE_BYTES];
+        ByteBuffer apkDigest = ByteBuffer.wrap(apkDigestBytes);
+        apkDigest.order(ByteOrder.LITTLE_ENDIAN);
+
+        calculateFsveritySignatureInternal(apk, signatureInfo, tree, apkDigest, header, extensions);
+
+        output.rewind();
+        return new ApkVerityResult(output, apkDigestBytes);
+    }
+
+    /**
+     * Calculates the fsverity root hash for integrity measurement.  This needs to be consistent to
+     * what kernel returns.
+     */
+    static byte[] generateFsverityRootHash(RandomAccessFile apk, ByteBuffer apkDigest,
+            SignatureInfo signatureInfo)
+            throws NoSuchAlgorithmException, DigestException, IOException {
+        ByteBuffer verityBlock = ByteBuffer.allocate(CHUNK_SIZE_BYTES)
+                .order(ByteOrder.LITTLE_ENDIAN);
+        ByteBuffer header = slice(verityBlock, 0, FSVERITY_HEADER_SIZE_BYTES);
+        ByteBuffer extensions = slice(verityBlock, FSVERITY_HEADER_SIZE_BYTES, CHUNK_SIZE_BYTES);
+
+        calculateFsveritySignatureInternal(apk, signatureInfo, null, null, header, extensions);
+
+        MessageDigest md = MessageDigest.getInstance(JCA_DIGEST_ALGORITHM);
+        md.update(DEFAULT_SALT);
+        md.update(verityBlock);
+        md.update(apkDigest);
+        return md.digest();
+    }
+
+    private static void calculateFsveritySignatureInternal(
+            RandomAccessFile apk, SignatureInfo signatureInfo, ByteBuffer treeOutput,
+            ByteBuffer rootHashOutput, ByteBuffer headerOutput, ByteBuffer extensionsOutput)
+            throws IOException, NoSuchAlgorithmException, DigestException {
         assertSigningBlockAlignedAndHasFullPages(signatureInfo);
 
         long signingBlockSize =
                 signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset;
         long dataSize = apk.length() - signingBlockSize - ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE;
         int[] levelOffset = calculateVerityLevelOffset(dataSize);
-        ByteBuffer output = bufferFactory.create(
-                CHUNK_SIZE_BYTES +  // fsverity header + extensions + padding
-                levelOffset[levelOffset.length - 1] +  // Merkle tree size
-                FSVERITY_HEADER_SIZE_BYTES);  // second fsverity header (verbatim copy)
 
-        // Start generating the tree from the block boundary as the kernel will expect.
-        ByteBuffer treeOutput = slice(output, CHUNK_SIZE_BYTES,
-                output.limit() - FSVERITY_HEADER_SIZE_BYTES);
-        byte[] rootHash = generateApkVerityTree(apk, signatureInfo, DEFAULT_SALT, levelOffset,
-                treeOutput);
+        if (treeOutput != null) {
+            byte[] apkRootHash = generateApkVerityTree(apk, signatureInfo, DEFAULT_SALT,
+                    levelOffset, treeOutput);
+            if (rootHashOutput != null) {
+                rootHashOutput.put(apkRootHash);
+            }
+        }
 
-        ByteBuffer integrityHeader = generateFsverityHeader(apk.length(), DEFAULT_SALT);
-        output.put(integrityHeader);
-        output.put(generateFsverityExtensions());
+        if (headerOutput != null) {
+            generateFsverityHeader(headerOutput, apk.length(), levelOffset.length - 1,
+                    DEFAULT_SALT);
+        }
 
-        integrityHeader.rewind();
-        output.put(integrityHeader);
-        output.rewind();
-        return new ApkVerityResult(output, rootHash);
+        if (extensionsOutput != null) {
+            generateFsverityExtensions(extensionsOutput, signatureInfo.apkSigningBlockOffset,
+                    signingBlockSize, signatureInfo.eocdOffset);
+        }
     }
 
     /**
@@ -211,7 +258,7 @@
                     eocdCdOffsetFieldPosition - signatureInfo.centralDirOffset),
                 MMAP_REGION_SIZE_BYTES);
 
-        // 3. Fill up the rest of buffer with 0s.
+        // 3. Consume offset of Signing Block as an alternative EoCD.
         ByteBuffer alternativeCentralDirOffset = ByteBuffer.allocate(
                 ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE).order(ByteOrder.LITTLE_ENDIAN);
         alternativeCentralDirOffset.putInt(Math.toIntExact(signatureInfo.apkSigningBlockOffset));
@@ -259,36 +306,109 @@
         return rootHash;
     }
 
-    private static ByteBuffer generateFsverityHeader(long fileSize, byte[] salt) {
+    private static ByteBuffer generateFsverityHeader(ByteBuffer buffer, long fileSize, int depth,
+            byte[] salt) {
         if (salt.length != 8) {
             throw new IllegalArgumentException("salt is not 8 bytes long");
         }
 
-        ByteBuffer buffer = ByteBuffer.allocate(FSVERITY_HEADER_SIZE_BYTES);
-        buffer.order(ByteOrder.LITTLE_ENDIAN);
-
-        // TODO(b/30972906): insert a reference when there is a public one.
+        // TODO(b/30972906): update the reference when there is a better one in public.
         buffer.put("TrueBrew".getBytes());  // magic
+
         buffer.put((byte) 1);        // major version
         buffer.put((byte) 0);        // minor version
-        buffer.put((byte) 12);       // log2(block-size) == log2(4096)
-        buffer.put((byte) 7);        // log2(leaves-per-node) == log2(block-size / digest-size)
-                                     //                       == log2(4096 / 32)
-        buffer.putShort((short) 1);  // meta algorithm, 1: SHA-256 FIXME finalize constant
-        buffer.putShort((short) 1);  // data algorithm, 1: SHA-256 FIXME finalize constant
-        buffer.putInt(0x1);          // flags, 0x1: has extension, FIXME also hide it
-        buffer.putInt(0);            // reserved
-        buffer.putLong(fileSize);    // original i_size
-        buffer.put(salt);            // salt (8 bytes)
+        buffer.put((byte) 12);       // log2(block-size): log2(4096)
+        buffer.put((byte) 7);        // log2(leaves-per-node): log2(4096 / 32)
 
-        // TODO(b/30972906): Add extension.
+        buffer.putShort((short) 1);  // meta algorithm, SHA256_MODE == 1
+        buffer.putShort((short) 1);  // data algorithm, SHA256_MODE == 1
+
+        buffer.putInt(0x1);          // flags, 0x1: has extension
+        buffer.putInt(0);            // reserved
+
+        buffer.putLong(fileSize);    // original file size
+
+        buffer.put((byte) 0);        // auth block offset, disabled here
+        buffer.put(salt);            // salt (8 bytes)
+        // skip(buffer, 22);            // reserved
 
         buffer.rewind();
         return buffer;
     }
 
-    private static ByteBuffer generateFsverityExtensions() {
-        return ByteBuffer.allocate(64); // TODO(b/30972906): implement this.
+    private static ByteBuffer generateFsverityExtensions(ByteBuffer buffer, long signingBlockOffset,
+            long signingBlockSize, long eocdOffset) {
+        // Snapshot of the FSVerity structs (subject to change once upstreamed).
+        //
+        // struct fsverity_header_extension {
+        //   u8 extension_count;
+        //   u8 reserved[7];
+        // };
+        //
+        // struct fsverity_extension {
+        //   __le16 length;
+        //   u8 type;
+        //   u8 reserved[5];
+        // };
+        //
+        // struct fsverity_extension_elide {
+        //   __le64 offset;
+        //   __le64 length;
+        // }
+        //
+        // struct fsverity_extension_patch {
+        //   __le64 offset;
+        //   u8 length;
+        //   u8 reserved[7];
+        //   u8 databytes[];
+        // };
+
+        // struct fsverity_header_extension
+        buffer.put((byte) 2);        // extension count
+        skip(buffer, 3);             // reserved
+
+        final int kSizeOfFsverityExtensionHeader = 8;
+
+        {
+            // struct fsverity_extension #1
+            final int kSizeOfFsverityElidedExtension = 16;
+
+            buffer.putShort((short)  // total size of extension, padded to 64-bit alignment
+                    (kSizeOfFsverityExtensionHeader + kSizeOfFsverityElidedExtension));
+            buffer.put((byte) 0);    // ID of elide extension
+            skip(buffer, 5);         // reserved
+
+            // struct fsverity_extension_elide
+            buffer.putLong(signingBlockOffset);
+            buffer.putLong(signingBlockSize);
+        }
+
+        {
+            // struct fsverity_extension #2
+            final int kSizeOfFsverityPatchExtension =
+                    8 +  // offset size
+                    1 +  // size of length from offset (up to 255)
+                    7 +  // reserved
+                    ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE;
+            final int kPadding = (int) divideRoundup(kSizeOfFsverityPatchExtension % 8, 8);
+
+            buffer.putShort((short)  // total size of extension, padded to 64-bit alignment
+                    (kSizeOfFsverityExtensionHeader + kSizeOfFsverityPatchExtension + kPadding));
+            buffer.put((byte) 1);    // ID of patch extension
+            skip(buffer, 5);         // reserved
+
+            // struct fsverity_extension_patch
+            buffer.putLong(eocdOffset);                                 // offset
+            buffer.put((byte) ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE);  // length
+            skip(buffer, 7);                                            // reserved
+            buffer.putInt(Math.toIntExact(signingBlockOffset));         // databytes
+
+            // There are extra kPadding bytes of 0s here, included in the total size field of the
+            // extension header. The output ByteBuffer is assumed to be initialized to 0.
+        }
+
+        buffer.rewind();
+        return buffer;
     }
 
     /**
@@ -344,6 +464,11 @@
         return b.slice();
     }
 
+    /** Skip the {@code ByteBuffer} position by {@code bytes}. */
+    private static void skip(ByteBuffer buffer, int bytes) {
+        buffer.position(buffer.position() + bytes);
+    }
+
     /** Divides a number and round up to the closest integer. */
     private static long divideRoundup(long dividend, long divisor) {
         return (dividend + divisor - 1) / divisor;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 05770c3..a2ecfc4 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -22209,7 +22209,7 @@
      *
      * @param id the ID to search for
      * @return a view with given ID if found, or {@code null} otherwise
-     * @see View#findViewById(int)
+     * @see View#requireViewById(int)
      */
     @Nullable
     public final <T extends View> T findViewById(@IdRes int id) {
@@ -22220,6 +22220,29 @@
     }
 
     /**
+     * Finds the first descendant view with the given ID, the view itself if the ID matches
+     * {@link #getId()}, or throws an IllegalArgumentException if the ID is invalid or there is no
+     * matching view in the hierarchy.
+     * <p>
+     * <strong>Note:</strong> In most cases -- depending on compiler support --
+     * the resulting view is automatically cast to the target class type. If
+     * the target class type is unconstrained, an explicit cast may be
+     * necessary.
+     *
+     * @param id the ID to search for
+     * @return a view with given ID
+     * @see View#findViewById(int)
+     */
+    @NonNull
+    public final <T extends View> T requireViewById(@IdRes int id) {
+        T view = findViewById(id);
+        if (view == null) {
+            throw new IllegalArgumentException("ID does not reference a View inside this View");
+        }
+        return view;
+    }
+
+    /**
      * Finds a view by its unuque and stable accessibility id.
      *
      * @param accessibilityId The searched accessibility id.
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 95abea1..5bd0782 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -1339,9 +1339,9 @@
 
     /**
      * Finds a view that was identified by the {@code android:id} XML attribute
-     * that was processed in {@link android.app.Activity#onCreate}. This will
-     * implicitly call {@link #getDecorView} with all of the associated
-     * side-effects.
+     * that was processed in {@link android.app.Activity#onCreate}.
+     * <p>
+     * This will implicitly call {@link #getDecorView} with all of the associated side-effects.
      * <p>
      * <strong>Note:</strong> In most cases -- depending on compiler support --
      * the resulting view is automatically cast to the target class type. If
@@ -1351,11 +1351,35 @@
      * @param id the ID to search for
      * @return a view with given ID if found, or {@code null} otherwise
      * @see View#findViewById(int)
+     * @see Window#requireViewById(int)
      */
     @Nullable
     public <T extends View> T findViewById(@IdRes int id) {
         return getDecorView().findViewById(id);
     }
+    /**
+     * Finds a view that was identified by the {@code android:id} XML attribute
+     * that was processed in {@link android.app.Activity#onCreate}, or throws an
+     * IllegalArgumentException if the ID is invalid, or there is no matching view in the hierarchy.
+     * <p>
+     * <strong>Note:</strong> In most cases -- depending on compiler support --
+     * the resulting view is automatically cast to the target class type. If
+     * the target class type is unconstrained, an explicit cast may be
+     * necessary.
+     *
+     * @param id the ID to search for
+     * @return a view with given ID
+     * @see View#requireViewById(int)
+     * @see Window#findViewById(int)
+     */
+    @NonNull
+    public final <T extends View> T requireViewById(@IdRes int id) {
+        T view = findViewById(id);
+        if (view == null) {
+            throw new IllegalArgumentException("ID does not reference a View inside this Window");
+        }
+        return view;
+    }
 
     /**
      * Convenience for
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 00860a4..2c51ee9 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -663,6 +663,10 @@
         if (context == null) {
             throw new IllegalArgumentException("Invalid context argument");
         }
+        if (mWebViewThread == null) {
+            throw new RuntimeException(
+                "WebView cannot be initialized on a thread that has no Looper.");
+        }
         sEnforceThreadChecking = context.getApplicationInfo().targetSdkVersion >=
                 Build.VERSION_CODES.JELLY_BEAN_MR2;
         checkThread();
@@ -2422,6 +2426,14 @@
         return getFactory().getWebViewClassLoader();
     }
 
+    /**
+     * Returns the {@link Looper} corresponding to the thread on which WebView calls must be made.
+     */
+    @NonNull
+    public Looper getLooper() {
+        return mWebViewThread;
+    }
+
     //-------------------------------------------------------------------------
     // Interface for WebView providers
     //-------------------------------------------------------------------------
diff --git a/core/java/android/widget/MediaControlView2.java b/core/java/android/widget/MediaControlView2.java
index f0a2728..f1d633a 100644
--- a/core/java/android/widget/MediaControlView2.java
+++ b/core/java/android/widget/MediaControlView2.java
@@ -16,6 +16,7 @@
 
 package android.widget;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -28,6 +29,8 @@
 import android.view.MotionEvent;
 import android.view.View;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 /**
  * A View that contains the controls for MediaPlayer2.
  * It provides a wide range of UI including buttons such as "Play/Pause", "Rewind", "Fast Forward",
@@ -48,7 +51,23 @@
  * @hide
  */
 public class MediaControlView2 extends FrameLayout {
-    // TODO: should overflow button be included?
+    /** @hide */
+    @IntDef({
+            BUTTON_PLAY_PAUSE,
+            BUTTON_FFWD,
+            BUTTON_REW,
+            BUTTON_NEXT,
+            BUTTON_PREV,
+            BUTTON_SUBTITLE,
+            BUTTON_FULL_SCREEN,
+            BUTTON_OVERFLOW,
+            BUTTON_MUTE,
+            BUTTON_ASPECT_RATIO,
+            BUTTON_SETTINGS
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Button {}
+
     public static final int BUTTON_PLAY_PAUSE = 1;
     public static final int BUTTON_FFWD = 2;
     public static final int BUTTON_REW = 3;
@@ -129,48 +148,6 @@
     }
 
     /**
-     * Returns whether the media is currently playing or not.
-     */
-    public boolean isPlaying() {
-        return mProvider.isPlaying_impl();
-    }
-
-    /**
-     * Returns the current position of the media in milliseconds.
-     */
-    public int getCurrentPosition() {
-        return mProvider.getCurrentPosition_impl();
-    }
-
-    /**
-     * Returns the percentage of how much of the media is currently buffered in storage.
-     */
-    public int getBufferPercentage() {
-        return mProvider.getBufferPercentage_impl();
-    }
-
-    /**
-     * Returns whether the media can be paused or not.
-     */
-    public boolean canPause() {
-        return mProvider.canPause_impl();
-    }
-
-    /**
-     * Returns whether the media can be rewound or not.
-     */
-    public boolean canSeekBackward() {
-        return mProvider.canSeekBackward_impl();
-    }
-
-    /**
-     * Returns whether the media can be fast-forwarded or not.
-     */
-    public boolean canSeekForward() {
-        return mProvider.canSeekForward_impl();
-    }
-
-    /**
      * If the media selected has a subtitle track, calling this method will display the subtitle at
      * the bottom of the view. If a media has multiple subtitle tracks, this method will select the
      * first one of them.
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 6e0ba341..997d47f 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -841,7 +841,7 @@
         }
 
         @Override
-        public boolean startAsCaller(Activity activity, Bundle options, int userId) {
+        public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
             final Intent intent = getBaseIntentToSend();
             if (intent == null) {
                 return false;
@@ -860,8 +860,7 @@
             final boolean ignoreTargetSecurity = mSourceInfo != null
                     && mSourceInfo.getResolvedComponentName().getPackageName()
                     .equals(mChooserTarget.getComponentName().getPackageName());
-            activity.startActivityAsCaller(intent, options, ignoreTargetSecurity, userId);
-            return true;
+            return activity.startAsCallerImpl(intent, options, ignoreTargetSecurity, userId);
         }
 
         @Override
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index 398d087..86731bc 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -107,7 +107,7 @@
                             || ChooserActivity.class.getName().equals(ri.activityInfo.name));
 
             try {
-                startActivityAsCaller(newIntent, null, false, targetUserId);
+                startActivityAsCaller(newIntent, null, null, false, targetUserId);
             } catch (RuntimeException e) {
                 int launchedFromUid = -1;
                 String launchedFromPackage = "?";
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index ceb06f5..d6d4490 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -43,6 +43,7 @@
 import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.os.PatternMatcher;
 import android.os.RemoteException;
 import android.os.StrictMode;
@@ -857,6 +858,36 @@
         }
     }
 
+    public boolean startAsCallerImpl(Intent intent, Bundle options, boolean ignoreTargetSecurity,
+            int userId) {
+        // Pass intent to delegate chooser activity with permission token.
+        // TODO: This should move to a trampoline Activity in the system when the ChooserActivity
+        // moves into systemui
+        try {
+            // TODO: Once this is a small springboard activity, it can move off the UI process
+            // and we can move the request method to ActivityManagerInternal.
+            IBinder permissionToken = ActivityManager.getService()
+                    .requestStartActivityPermissionToken(getActivityToken());
+            final Intent chooserIntent = new Intent();
+            final ComponentName delegateActivity = ComponentName.unflattenFromString(
+                    Resources.getSystem().getString(R.string.config_chooserActivity));
+            chooserIntent.setClassName(delegateActivity.getPackageName(),
+                    delegateActivity.getClassName());
+            chooserIntent.putExtra(ActivityManager.EXTRA_PERMISSION_TOKEN, permissionToken);
+
+            // TODO: These extras will change as chooser activity moves into systemui
+            chooserIntent.putExtra(Intent.EXTRA_INTENT, intent);
+            chooserIntent.putExtra(ActivityManager.EXTRA_OPTIONS, options);
+            chooserIntent.putExtra(ActivityManager.EXTRA_IGNORE_TARGET_SECURITY,
+                    ignoreTargetSecurity);
+            chooserIntent.putExtra(Intent.EXTRA_USER_ID, userId);
+            startActivity(chooserIntent);
+        } catch (RemoteException e) {
+            Log.e(TAG, e.toString());
+        }
+        return true;
+    }
+
     public void onActivityStarted(TargetInfo cti) {
         // Do nothing
     }
@@ -1181,9 +1212,8 @@
         }
 
         @Override
-        public boolean startAsCaller(Activity activity, Bundle options, int userId) {
-            activity.startActivityAsCaller(mResolvedIntent, options, false, userId);
-            return true;
+        public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
+            return activity.startAsCallerImpl(mResolvedIntent, options, false, userId);
         }
 
         @Override
@@ -1242,7 +1272,7 @@
          * @param userId userId to start as or {@link UserHandle#USER_NULL} for activity's caller
          * @return true if the start completed successfully
          */
-        boolean startAsCaller(Activity activity, Bundle options, int userId);
+        boolean startAsCaller(ResolverActivity activity, Bundle options, int userId);
 
         /**
          * Start the activity referenced by this target as a given user.
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 039b66d..51f51c2 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -6211,8 +6211,20 @@
     }
 
     @Override public long getGpsBatteryDrainMaMs() {
-        //TODO: Add GPS power computation (b/67213967)
-        return 0;
+        final double opVolt = mPowerProfile.getAveragePower(
+            PowerProfile.POWER_GPS_OPERATING_VOLTAGE) / 1000.0;
+        if (opVolt == 0) {
+            return 0;
+        }
+        double energyUsedMaMs = 0.0;
+        final int which = STATS_SINCE_CHARGED;
+        final long rawRealtime = SystemClock.elapsedRealtime() * 1000;
+        for(int i=0; i < GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS; i++) {
+            energyUsedMaMs
+                += mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_SIGNAL_QUALITY_BASED, i)
+                * (getGpsSignalQualityTime(i, rawRealtime, which) / 1000);
+        }
+        return (long) energyUsedMaMs;
     }
 
     @Override public long getPhoneOnTime(long elapsedRealtimeUs, int which) {
diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java
index 240fc51..f4436d3 100644
--- a/core/java/com/android/internal/os/PowerProfile.java
+++ b/core/java/com/android/internal/os/PowerProfile.java
@@ -104,12 +104,18 @@
     public static final String POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE =
             "modem.controller.voltage";
 
-    /**
+     /**
      * Power consumption when GPS is on.
      */
     public static final String POWER_GPS_ON = "gps.on";
 
     /**
+     * GPS power parameters based on signal quality
+     */
+    public static final String POWER_GPS_SIGNAL_QUALITY_BASED = "gps.signalqualitybased";
+    public static final String POWER_GPS_OPERATING_VOLTAGE = "gps.voltage";
+
+    /**
      * Power consumption when Bluetooth driver is on.
      * @deprecated
      */
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 2671f29..5659470 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -98,6 +98,10 @@
 
     private static final String SOCKET_NAME_ARG = "--socket-name=";
 
+    /* Dexopt flag to disable hidden API access checks when dexopting SystemServer.
+     * Must be kept in sync with com.android.server.pm.Installer. */
+    private static final int DEXOPT_DISABLE_HIDDEN_API_CHECKS = 1 << 10;
+
     /**
      * Used to pre-load resources.
      */
@@ -565,7 +569,10 @@
             if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
                 final String packageName = "*";
                 final String outputPath = null;
-                final int dexFlags = 0;
+                // Dexopt with a flag which lifts restrictions on hidden API usage.
+                // Offending methods would otherwise be re-verified at runtime and
+                // we want to avoid the performance overhead of that.
+                final int dexFlags = DEXOPT_DISABLE_HIDDEN_API_CHECKS;
                 final String compilerFilter = systemServerFilter;
                 final String uuid = StorageManager.UUID_PRIVATE_INTERNAL;
                 final String seInfo = null;
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index f5af80a..ebb5f9f 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -35,6 +35,8 @@
     void animateCollapsePanels();
     void togglePanel();
 
+    void showChargingAnimation(int batteryLevel);
+
     /**
      * Notifies the status bar of a System UI visibility flag change.
      *
diff --git a/core/jni/android/graphics/AnimatedImageDrawable.cpp b/core/jni/android/graphics/AnimatedImageDrawable.cpp
index ec15cce..8b3ce66 100644
--- a/core/jni/android/graphics/AnimatedImageDrawable.cpp
+++ b/core/jni/android/graphics/AnimatedImageDrawable.cpp
@@ -18,6 +18,7 @@
 #include "ImageDecoder.h"
 #include "core_jni_helpers.h"
 
+#include <hwui/AnimatedImageDrawable.h>
 #include <hwui/Canvas.h>
 #include <SkAndroidCodec.h>
 #include <SkAnimatedImage.h>
@@ -27,10 +28,6 @@
 
 using namespace android;
 
-struct AnimatedImageDrawable {
-    sk_sp<SkAnimatedImage> mDrawable;
-    SkPaint                mPaint;
-};
 
 // Note: jpostProcess holds a handle to the ImageDecoder.
 static jlong AnimatedImageDrawable_nCreate(JNIEnv* env, jobject /*clazz*/,
@@ -65,20 +62,22 @@
         picture = recorder.finishRecordingAsPicture();
     }
 
-    std::unique_ptr<AnimatedImageDrawable> drawable(new AnimatedImageDrawable);
-    drawable->mDrawable = SkAnimatedImage::Make(std::move(imageDecoder->mCodec),
-                scaledSize, subset, std::move(picture));
-    if (!drawable->mDrawable) {
+
+    sk_sp<SkAnimatedImage> animatedImg = SkAnimatedImage::Make(std::move(imageDecoder->mCodec),
+                                                               scaledSize, subset,
+                                                               std::move(picture));
+    if (!animatedImg) {
         doThrowIOE(env, "Failed to create drawable");
         return 0;
     }
-    drawable->mDrawable->start();
 
+    sk_sp<AnimatedImageDrawable> drawable(new AnimatedImageDrawable(animatedImg));
+    drawable->start();
     return reinterpret_cast<jlong>(drawable.release());
 }
 
 static void AnimatedImageDrawable_destruct(AnimatedImageDrawable* drawable) {
-    delete drawable;
+    SkSafeUnref(drawable);
 }
 
 static jlong AnimatedImageDrawable_nGetNativeFinalizer(JNIEnv* /*env*/, jobject /*clazz*/) {
@@ -86,45 +85,43 @@
 }
 
 static jlong AnimatedImageDrawable_nDraw(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
-                                         jlong canvasPtr, jlong msecs) {
+                                         jlong canvasPtr) {
     auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
-    double timeToNextUpdate = drawable->mDrawable->update(msecs);
     auto* canvas = reinterpret_cast<Canvas*>(canvasPtr);
-    canvas->drawAnimatedImage(drawable->mDrawable.get(), 0, 0, &drawable->mPaint);
-    return (jlong) timeToNextUpdate;
+    return (jlong) canvas->drawAnimatedImage(drawable);
 }
 
 static void AnimatedImageDrawable_nSetAlpha(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
                                             jint alpha) {
     auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
-    drawable->mPaint.setAlpha(alpha);
+    drawable->setStagingAlpha(alpha);
 }
 
 static jlong AnimatedImageDrawable_nGetAlpha(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
     auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
-    return drawable->mPaint.getAlpha();
+    return drawable->getStagingAlpha();
 }
 
 static void AnimatedImageDrawable_nSetColorFilter(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
                                                   jlong nativeFilter) {
     auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
     auto* filter = reinterpret_cast<SkColorFilter*>(nativeFilter);
-    drawable->mPaint.setColorFilter(sk_ref_sp(filter));
+    drawable->setStagingColorFilter(sk_ref_sp(filter));
 }
 
 static jboolean AnimatedImageDrawable_nIsRunning(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
     auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
-    return drawable->mDrawable->isRunning();
+    return drawable->isRunning();
 }
 
 static void AnimatedImageDrawable_nStart(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
     auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
-    drawable->mDrawable->start();
+    drawable->start();
 }
 
 static void AnimatedImageDrawable_nStop(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
     auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
-    drawable->mDrawable->stop();
+    drawable->stop();
 }
 
 static long AnimatedImageDrawable_nNativeByteSize(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
@@ -136,7 +133,7 @@
 static const JNINativeMethod gAnimatedImageDrawableMethods[] = {
     { "nCreate",             "(JLandroid/graphics/ImageDecoder;IILandroid/graphics/Rect;)J", (void*) AnimatedImageDrawable_nCreate },
     { "nGetNativeFinalizer", "()J",                                                          (void*) AnimatedImageDrawable_nGetNativeFinalizer },
-    { "nDraw",               "(JJJ)J",                                                       (void*) AnimatedImageDrawable_nDraw },
+    { "nDraw",               "(JJ)J",                                                        (void*) AnimatedImageDrawable_nDraw },
     { "nSetAlpha",           "(JI)V",                                                        (void*) AnimatedImageDrawable_nSetAlpha },
     { "nGetAlpha",           "(J)I",                                                         (void*) AnimatedImageDrawable_nGetAlpha },
     { "nSetColorFilter",     "(JJ)V",                                                        (void*) AnimatedImageDrawable_nSetColorFilter },
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 2be9471..376a797 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -608,9 +608,10 @@
 }
 
 static jint
-android_media_AudioSystem_setLowRamDevice(JNIEnv *env, jobject clazz, jboolean isLowRamDevice)
+android_media_AudioSystem_setLowRamDevice(
+        JNIEnv *env, jobject clazz, jboolean isLowRamDevice, jlong totalMemory)
 {
-    return (jint) AudioSystem::setLowRamDevice((bool) isLowRamDevice);
+    return (jint) AudioSystem::setLowRamDevice((bool) isLowRamDevice, (int64_t) totalMemory);
 }
 
 static jint
@@ -1819,7 +1820,7 @@
     {"getPrimaryOutputSamplingRate", "()I", (void *)android_media_AudioSystem_getPrimaryOutputSamplingRate},
     {"getPrimaryOutputFrameCount",   "()I", (void *)android_media_AudioSystem_getPrimaryOutputFrameCount},
     {"getOutputLatency",    "(I)I",     (void *)android_media_AudioSystem_getOutputLatency},
-    {"setLowRamDevice",     "(Z)I",     (void *)android_media_AudioSystem_setLowRamDevice},
+    {"setLowRamDevice",     "(ZJ)I",    (void *)android_media_AudioSystem_setLowRamDevice},
     {"checkAudioFlinger",    "()I",     (void *)android_media_AudioSystem_checkAudioFlinger},
     {"listAudioPorts",      "(Ljava/util/ArrayList;[I)I",
                                                 (void *)android_media_AudioSystem_listAudioPorts},
diff --git a/core/proto/android/providers/settings.proto b/core/proto/android/providers/settings.proto
index 95eb889..fd28322 100644
--- a/core/proto/android/providers/settings.proto
+++ b/core/proto/android/providers/settings.proto
@@ -392,8 +392,10 @@
     optional SettingProto enable_smart_replies_in_notifications = 348;
     optional SettingProto show_first_crash_dialog = 349;
     optional SettingProto wifi_connected_mac_randomization_enabled = 350;
+    optional SettingProto show_restart_in_crash_dialog = 351;
+    optional SettingProto show_mute_in_crash_dialog = 352;
 
-    // Next tag = 351;
+    // Next tag = 353;
 }
 
 message SecureSettingsProto {
@@ -596,8 +598,9 @@
     optional SettingProto lockdown_in_power_menu = 194;
     optional SettingProto backup_manager_constants = 169;
     optional SettingProto show_first_crash_dialog_dev_option = 195;
+    optional SettingProto bluetooth_on_while_driving = 196;
 
-    // Next tag = 196
+    // Next tag = 197
 }
 
 message SystemSettingsProto {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 081c92c..f1a9bd4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -514,6 +514,7 @@
     <protected-broadcast android:name="android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED" />
     <protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED" />
     <protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED" />
+    <protected-broadcast android:name="android.app.action.APP_BLOCK_STATE_CHANGED" />
 
     <protected-broadcast android:name="android.permission.GET_APP_GRANTED_URI_PERMISSIONS" />
     <protected-broadcast android:name="android.permission.CLEAR_APP_GRANTED_URI_PERMISSIONS" />
@@ -577,6 +578,7 @@
     <!-- Added in P -->
     <protected-broadcast android:name="android.app.action.PROFILE_OWNER_CHANGED" />
     <protected-broadcast android:name="android.app.action.TRANSFER_OWNERSHIP_COMPLETE" />
+    <protected-broadcast android:name="android.app.action.AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE" />
     <protected-broadcast android:name="android.app.action.DATA_SHARING_RESTRICTION_CHANGED" />
 
     <!-- ====================================================================== -->
@@ -1933,6 +1935,12 @@
     <permission android:name="android.permission.START_ANY_ACTIVITY"
         android:protectionLevel="signature" />
 
+    <!-- Allows an application to start an activity as another app, provided that app has been
+         granted a permissionToken from the ActivityManagerService.
+         @hide -->
+    <permission android:name="android.permission.START_ACTIVITY_AS_CALLER"
+        android:protectionLevel="signature" />
+
     <!-- @deprecated The {@link android.app.ActivityManager#restartPackage}
         API is no longer supported. -->
     <permission android:name="android.permission.RESTART_PACKAGES"
@@ -3710,6 +3718,15 @@
     <permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"
         android:protectionLevel="signature|development|instant|appop" />
 
+    <!-- Allows a regular application to use {@link android.app.Service#startForeground
+         Service.startForeground}.
+         <p>Protection level: normal
+    -->
+    <permission android:name="android.permission.FOREGROUND_SERVICE"
+        android:description="@string/permdesc_foregroundService"
+        android:label="@string/permlab_foregroundService"
+        android:protectionLevel="normal|instant" />
+
     <!-- @hide Allows system components to access all app shortcuts. -->
     <permission android:name="android.permission.ACCESS_SHORTCUTS"
         android:protectionLevel="signature" />
diff --git a/core/res/res/drawable/ic_info_outline_24.xml b/core/res/res/drawable/ic_info_outline_24.xml
new file mode 100644
index 0000000..abba8cf
--- /dev/null
+++ b/core/res/res/drawable/ic_info_outline_24.xml
@@ -0,0 +1,25 @@
+<!--
+  Copyright (C) 2018 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
+</vector>
diff --git a/core/res/res/layout/app_error_dialog.xml b/core/res/res/layout/app_error_dialog.xml
index d78ce59..c3b149a 100644
--- a/core/res/res/layout/app_error_dialog.xml
+++ b/core/res/res/layout/app_error_dialog.xml
@@ -18,48 +18,50 @@
 */
 -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:paddingTop="@dimen/aerr_padding_list_top"
+    android:paddingBottom="@dimen/aerr_padding_list_bottom">
+
+    <Button
+        android:id="@+id/aerr_restart"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:orientation="vertical"
-        android:paddingTop="@dimen/aerr_padding_list_top"
-        android:paddingBottom="@dimen/aerr_padding_list_bottom">
-
+        android:text="@string/aerr_restart"
+        android:drawableStart="@drawable/ic_refresh"
+        style="@style/aerr_list_item" />
 
     <Button
-            android:id="@+id/aerr_restart"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:text="@string/aerr_restart"
-            android:drawableStart="@drawable/ic_refresh"
-            style="@style/aerr_list_item"
-    />
+        android:id="@+id/aerr_app_info"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/app_info"
+        android:drawableStart="@drawable/ic_info_outline_24"
+        style="@style/aerr_list_item" />
 
     <Button
-            android:id="@+id/aerr_close"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:text="@string/aerr_close_app"
-            android:drawableStart="@drawable/ic_close"
-            style="@style/aerr_list_item"
-    />
+        android:id="@+id/aerr_close"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/aerr_close_app"
+        android:drawableStart="@drawable/ic_close"
+        style="@style/aerr_list_item" />
 
     <Button
-            android:id="@+id/aerr_report"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:text="@string/aerr_report"
-            android:drawableStart="@drawable/ic_feedback"
-            style="@style/aerr_list_item"
-    />
+        android:id="@+id/aerr_report"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/aerr_report"
+        android:drawableStart="@drawable/ic_feedback"
+        style="@style/aerr_list_item" />
 
     <Button
-            android:id="@+id/aerr_mute"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:text="@string/aerr_mute"
-            android:drawableStart="@drawable/ic_eject_24dp"
-            style="@style/aerr_list_item"
-    />
-
+        android:id="@+id/aerr_mute"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/aerr_mute"
+        android:drawableStart="@drawable/ic_eject_24dp"
+        style="@style/aerr_list_item" />
 
 </LinearLayout>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index c623c9a..7e5a735 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2283,7 +2283,10 @@
          Can be customized for other product types -->
     <string name="config_chooseTypeAndAccountActivity" translatable="false"
             >android/android.accounts.ChooseTypeAndAccountActivity</string>
-
+    <!-- Name of the activity that will handle requests to the system to choose an activity for
+         the purposes of resolving an intent. -->
+    <string name="config_chooserActivity" translatable="false"
+            >com.android.systemui/com.android.systemui.chooser.ChooserActivity</string>
     <!-- Component name of a custom ResolverActivity (Intent resolver) to be used instead of
          the default framework version. If left empty, then the framework version will be used.
          Example: com.google.android.myapp/.resolver.MyResolverActivity  -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 4119cdc..71e963a 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -916,6 +916,11 @@
     <string name="permdesc_persistentActivity" product="default">Allows the app to make parts of itself persistent in memory.  This can limit memory available to other apps slowing down the phone.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_foregroundService">run foreground service</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_foregroundService">Allows the app to make use of foreground services.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_getPackageSize">measure app storage space</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_getPackageSize">Allows the app to retrieve its code, data, and cache sizes</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1711ec9..ee20873 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1067,6 +1067,7 @@
   <java-symbol type="string" name="owner_name" />
   <java-symbol type="string" name="config_chooseAccountActivity" />
   <java-symbol type="string" name="config_chooseTypeAndAccountActivity" />
+  <java-symbol type="string" name="config_chooserActivity" />
   <java-symbol type="string" name="config_customResolverActivity" />
   <java-symbol type="string" name="config_appsAuthorizedForSharedAccounts" />
   <java-symbol type="string" name="error_message_title" />
@@ -2652,6 +2653,7 @@
   <java-symbol type="id" name="aerr_report" />
   <java-symbol type="id" name="aerr_restart" />
   <java-symbol type="id" name="aerr_close" />
+  <java-symbol type="id" name="aerr_app_info" />
   <java-symbol type="id" name="aerr_mute" />
 
   <java-symbol type="string" name="status_bar_rotate" />
diff --git a/core/res/res/xml/power_profile.xml b/core/res/res/xml/power_profile.xml
index bc4b10f..d80c697 100644
--- a/core/res/res/xml/power_profile.xml
+++ b/core/res/res/xml/power_profile.xml
@@ -127,4 +127,11 @@
   </array>
   <item name="modem.controller.voltage">0</item>
 
+  <!-- GPS related values. Default is 0.-->
+  <array name="gps.signalqualitybased"> <!-- Strength 0 to 1 -->
+    <value>0</value>
+    <value>0</value>
+  </array>
+  <item name="gps.voltage">0</item>
+
 </device>
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index e094772..3e38010 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -51,6 +51,7 @@
     <uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" />
     <uses-permission android:name="android.permission.DELETE_CACHE_FILES" />
     <uses-permission android:name="android.permission.DOWNLOAD_CACHE_NON_PURGEABLE" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
     <uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.INJECT_EVENTS" />
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 09ac1d8..08d023d 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -332,7 +332,9 @@
                     Settings.Global.SETUP_PREPAID_DETECTION_TARGET_URL,
                     Settings.Global.SHORTCUT_MANAGER_CONSTANTS,
                     Settings.Global.SHOW_FIRST_CRASH_DIALOG,
+                    Settings.Global.SHOW_MUTE_IN_CRASH_DIALOG,
                     Settings.Global.SHOW_NOTIFICATION_CHANNEL_WARNINGS,
+                    Settings.Global.SHOW_RESTART_IN_CRASH_DIALOG,
                     Settings.Global.SHOW_TEMPERATURE_WARNING,
                     Settings.Global.SMART_SELECTION_UPDATE_CONTENT_URL,
                     Settings.Global.SMART_SELECTION_UPDATE_METADATA_URL,
@@ -538,7 +540,8 @@
                  Settings.Secure.BACKUP_MANAGER_CONSTANTS,
                  Settings.Secure.KEYGUARD_SLICE_URI,
                  Settings.Secure.PARENTAL_CONTROL_ENABLED,
-                 Settings.Secure.PARENTAL_CONTROL_REDIRECT_URL);
+                 Settings.Secure.PARENTAL_CONTROL_REDIRECT_URL,
+                 Settings.Secure.BLUETOOTH_ON_WHILE_DRIVING);
 
     @Test
     public void systemSettingsBackedUpOrBlacklisted() {
diff --git a/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java b/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java
index 4c4aeaf..e750766 100644
--- a/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java
@@ -17,6 +17,8 @@
 package android.provider;
 
 import static org.junit.Assert.fail;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import android.platform.test.annotations.Presubmit;
 import android.provider.SettingsValidators.Validator;
@@ -28,13 +30,101 @@
 
 import java.util.Map;
 
-/** Tests that ensure all backed up settings have non-null validators. */
+/**
+ * Tests that ensure all backed up settings have non-null validators. Also, common validators
+ * are tested.
+ */
 @Presubmit
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class SettingsValidatorsTest {
 
     @Test
+    public void testNonNegativeIntegerValidator() {
+        assertTrue(SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR.validate("1"));
+        assertTrue(SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR.validate("0"));
+        assertFalse(SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR.validate("-1"));
+        assertFalse(SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR.validate("rectangle"));
+    }
+
+    @Test
+    public void testAnyIntegerValidator() {
+        assertTrue(SettingsValidators.ANY_INTEGER_VALIDATOR.validate("1"));
+        assertTrue(SettingsValidators.ANY_INTEGER_VALIDATOR.validate("0"));
+        assertTrue(SettingsValidators.ANY_INTEGER_VALIDATOR.validate("-1"));
+        assertFalse(SettingsValidators.ANY_INTEGER_VALIDATOR.validate("rectangle"));
+    }
+
+    @Test
+    public void testComponentNameValidator() {
+        assertTrue(SettingsValidators.COMPONENT_NAME_VALIDATOR.validate(
+                "android/com.android.internal.backup.LocalTransport"));
+        assertFalse(SettingsValidators.COMPONENT_NAME_VALIDATOR.validate("rectangle"));
+    }
+
+    @Test
+    public void testLocaleValidator() {
+        assertTrue(SettingsValidators.LOCALE_VALIDATOR.validate("en_US"));
+        assertTrue(SettingsValidators.LOCALE_VALIDATOR.validate("es"));
+        assertFalse(SettingsValidators.LOCALE_VALIDATOR.validate("rectangle"));
+    }
+
+    @Test
+    public void testPackageNameValidator() {
+        assertTrue(SettingsValidators.PACKAGE_NAME_VALIDATOR.validate(
+                "com.google.android"));
+        assertFalse(SettingsValidators.PACKAGE_NAME_VALIDATOR.validate("com.google.@android"));
+        assertFalse(SettingsValidators.PACKAGE_NAME_VALIDATOR.validate(".com.google.android"));
+        assertFalse(SettingsValidators.PACKAGE_NAME_VALIDATOR.validate(".com.google.5android"));
+    }
+
+    @Test
+    public void testDiscreteValueValidator() {
+        String[] beerTypes = new String[]{"Ale", "American IPA", "Stout"};
+        Validator v = new SettingsValidators.DiscreteValueValidator(beerTypes);
+        assertTrue(v.validate("Ale"));
+        assertTrue(v.validate("American IPA"));
+        assertTrue(v.validate("Stout"));
+        assertFalse(v.validate("Cider")); // just juice pretending to be beer
+    }
+
+    @Test
+    public void testInclusiveIntegerRangeValidator() {
+        Validator v = new SettingsValidators.InclusiveIntegerRangeValidator(0, 5);
+        assertTrue(v.validate("0"));
+        assertTrue(v.validate("2"));
+        assertTrue(v.validate("5"));
+        assertFalse(v.validate("-1"));
+        assertFalse(v.validate("6"));
+    }
+
+    @Test
+    public void testInclusiveFloatRangeValidator() {
+        Validator v = new SettingsValidators.InclusiveFloatRangeValidator(0.0f, 5.0f);
+        assertTrue(v.validate("0.0"));
+        assertTrue(v.validate("2.0"));
+        assertTrue(v.validate("5.0"));
+        assertFalse(v.validate("-1.0"));
+        assertFalse(v.validate("6.0"));
+    }
+
+    @Test
+    public void testComponentNameListValidator() {
+        Validator v = new SettingsValidators.ComponentNameListValidator(",");
+        assertTrue(v.validate("android/com.android.internal.backup.LocalTransport,"
+                + "com.google.android.gms/.backup.migrate.service.D2dTransport"));
+        assertFalse(v.validate("com.google.5android,android"));
+    }
+
+    @Test
+    public void testPackageNameListValidator() {
+        Validator v = new SettingsValidators.PackageNameListValidator(",");
+        assertTrue(v.validate("com.android.internal.backup.LocalTransport,com.google.android.gms"));
+        assertFalse(v.validate("5com.android.internal.backup.LocalTransport,android"));
+    }
+
+
+    @Test
     public void ensureAllBackedUpSystemSettingsHaveValidators() {
         String offenders = getOffenders(concat(Settings.System.SETTINGS_TO_BACKUP,
                 Settings.System.LEGACY_RESTORE_SETTINGS), Settings.System.VALIDATORS);
diff --git a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
index b18fa74..c0bc3a8 100644
--- a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
@@ -24,6 +24,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.support.test.InstrumentationRegistry;
@@ -269,8 +270,8 @@
         }
 
         @Override
-        public void startActivityAsCaller(Intent intent, @Nullable Bundle options, boolean
-                ignoreTargetSecurity, int userId) {
+        public void startActivityAsCaller(Intent intent, @Nullable Bundle options,
+                IBinder permissionToken, boolean ignoreTargetSecurity, int userId) {
             mStartActivityIntent = intent;
             mUserIdActivityLaunchedIn = userId;
         }
@@ -293,4 +294,4 @@
             return mPm;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index c0958cd..0949a90 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -368,6 +368,7 @@
         <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
         <permission name="android.permission.REAL_GET_TASKS"/>
         <permission name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE"/>
+        <permission name="android.permission.START_ACTIVITY_AS_CALLER"/>
         <permission name="android.permission.START_TASKS_FROM_RECENTS"/>
         <permission name="android.permission.STATUS_BAR"/>
         <permission name="android.permission.STOP_APP_SWITCHES"/>
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 317144a..5a80ee2 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -2742,7 +2742,7 @@
      * @param offset index of caret position
      * @return width measurement between start and offset
      */
-    public float getRunAdvance(@NonNull CharSequence text, int start, int end, int contextStart,
+    public float getRunAdvance(CharSequence text, int start, int end, int contextStart,
             int contextEnd, boolean isRtl, int offset) {
         if (text == null) {
             throw new IllegalArgumentException("text cannot be null");
diff --git a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
index da170c0..6d3ddd5 100644
--- a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
@@ -118,9 +118,12 @@
 
     @Override
     public void draw(@NonNull Canvas canvas) {
-        long nextUpdate = nDraw(mNativePtr, canvas.getNativeCanvasWrapper(),
-                SystemClock.uptimeMillis());
-        scheduleSelf(mRunnable, nextUpdate);
+        long nextUpdate = nDraw(mNativePtr, canvas.getNativeCanvasWrapper());
+        // a value <= 0 indicates that the drawable is stopped or that renderThread
+        // will manage the animation
+        if (nextUpdate > 0) {
+            scheduleSelf(mRunnable, nextUpdate);
+        }
     }
 
     @Override
@@ -130,6 +133,7 @@
                    + " 255! provided " + alpha);
         }
         nSetAlpha(mNativePtr, alpha);
+        invalidateSelf();
     }
 
     @Override
@@ -141,6 +145,7 @@
     public void setColorFilter(@Nullable ColorFilter colorFilter) {
         long nativeFilter = colorFilter == null ? 0 : colorFilter.getNativeInstance();
         nSetColorFilter(mNativePtr, nativeFilter);
+        invalidateSelf();
     }
 
     @Override
@@ -161,7 +166,10 @@
 
     @Override
     public void start() {
-        nStart(mNativePtr);
+        if (isRunning() == false) {
+            nStart(mNativePtr);
+            invalidateSelf();
+        }
     }
 
     @Override
@@ -173,7 +181,7 @@
             @Nullable ImageDecoder decoder, int width, int height, Rect cropRect)
         throws IOException;
     private static native long nGetNativeFinalizer();
-    private static native long nDraw(long nativePtr, long canvasNativePtr, long msecs);
+    private static native long nDraw(long nativePtr, long canvasNativePtr);
     private static native void nSetAlpha(long nativePtr, int alpha);
     private static native int nGetAlpha(long nativePtr);
     private static native void nSetColorFilter(long nativePtr, long nativeFilter);
diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java
index 3a5f7b7..e3740e3 100644
--- a/graphics/java/android/graphics/drawable/BitmapDrawable.java
+++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java
@@ -27,7 +27,6 @@
 import android.graphics.BitmapShader;
 import android.graphics.Canvas;
 import android.graphics.ColorFilter;
-import android.graphics.ImageDecoder;
 import android.graphics.Insets;
 import android.graphics.Matrix;
 import android.graphics.Outline;
@@ -50,7 +49,6 @@
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 
@@ -113,7 +111,7 @@
      */
     @Deprecated
     public BitmapDrawable() {
-        init(new BitmapState((Bitmap) null), null);
+        mBitmapState = new BitmapState((Bitmap) null);
     }
 
     /**
@@ -126,7 +124,8 @@
     @SuppressWarnings("unused")
     @Deprecated
     public BitmapDrawable(Resources res) {
-        init(new BitmapState((Bitmap) null), res);
+        mBitmapState = new BitmapState((Bitmap) null);
+        mBitmapState.mTargetDensity = mTargetDensity;
     }
 
     /**
@@ -136,7 +135,7 @@
      */
     @Deprecated
     public BitmapDrawable(Bitmap bitmap) {
-        init(new BitmapState(bitmap), null);
+        this(new BitmapState(bitmap), null);
     }
 
     /**
@@ -144,7 +143,8 @@
      * the display metrics of the resources.
      */
     public BitmapDrawable(Resources res, Bitmap bitmap) {
-        init(new BitmapState(bitmap), res);
+        this(new BitmapState(bitmap), res);
+        mBitmapState.mTargetDensity = mTargetDensity;
     }
 
     /**
@@ -154,7 +154,10 @@
      */
     @Deprecated
     public BitmapDrawable(String filepath) {
-        this(null, filepath);
+        this(new BitmapState(BitmapFactory.decodeFile(filepath)), null);
+        if (mBitmapState.mBitmap == null) {
+            android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath);
+        }
     }
 
     /**
@@ -162,21 +165,10 @@
      */
     @SuppressWarnings("unused")
     public BitmapDrawable(Resources res, String filepath) {
-        Bitmap bitmap = null;
-        try (FileInputStream stream = new FileInputStream(filepath)) {
-            bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(res, stream),
-                    (decoder, info, src) -> {
-                decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
-            });
-        } catch (Exception e) {
-            /*  do nothing. This matches the behavior of BitmapFactory.decodeFile()
-                If the exception happened on decode, mBitmapState.mBitmap will be null.
-            */
-        } finally {
-            init(new BitmapState(bitmap), res);
-            if (mBitmapState.mBitmap == null) {
-                android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath);
-            }
+        this(new BitmapState(BitmapFactory.decodeFile(filepath)), null);
+        mBitmapState.mTargetDensity = mTargetDensity;
+        if (mBitmapState.mBitmap == null) {
+            android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath);
         }
     }
 
@@ -187,7 +179,10 @@
      */
     @Deprecated
     public BitmapDrawable(java.io.InputStream is) {
-        this(null, is);
+        this(new BitmapState(BitmapFactory.decodeStream(is)), null);
+        if (mBitmapState.mBitmap == null) {
+            android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
+        }
     }
 
     /**
@@ -195,21 +190,10 @@
      */
     @SuppressWarnings("unused")
     public BitmapDrawable(Resources res, java.io.InputStream is) {
-        Bitmap bitmap = null;
-        try {
-            bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(res, is),
-                    (decoder, info, src) -> {
-                decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
-            });
-        } catch (Exception e) {
-            /*  do nothing. This matches the behavior of BitmapFactory.decodeStream()
-                If the exception happened on decode, mBitmapState.mBitmap will be null.
-            */
-        } finally {
-            init(new BitmapState(bitmap), res);
-            if (mBitmapState.mBitmap == null) {
-                android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
-            }
+        this(new BitmapState(BitmapFactory.decodeStream(is)), null);
+        mBitmapState.mTargetDensity = mTargetDensity;
+        if (mBitmapState.mBitmap == null) {
+            android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
         }
     }
 
@@ -828,19 +812,9 @@
                 }
             }
 
-            int density = Bitmap.DENSITY_NONE;
-            if (value.density == TypedValue.DENSITY_DEFAULT) {
-                density = DisplayMetrics.DENSITY_DEFAULT;
-            } else if (value.density != TypedValue.DENSITY_NONE) {
-                density = value.density;
-            }
-
             Bitmap bitmap = null;
             try (InputStream is = r.openRawResource(srcResId, value)) {
-                ImageDecoder.Source source = ImageDecoder.createSource(r, is, density);
-                bitmap = ImageDecoder.decodeBitmap(source, (decoder, info, src) -> {
-                    decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
-                });
+                bitmap = BitmapFactory.decodeResourceStream(r, value, is, null, null);
             } catch (Exception e) {
                 // Do nothing and pick up the error below.
             }
@@ -1039,21 +1013,14 @@
         }
     }
 
-    private BitmapDrawable(BitmapState state, Resources res) {
-        init(state, res);
-    }
-
     /**
-     * The one helper to rule them all. This is called by all public & private
+     * The one constructor to rule them all. This is called by all public
      * constructors to set the state and initialize local properties.
      */
-    private void init(BitmapState state, Resources res) {
+    private BitmapDrawable(BitmapState state, Resources res) {
         mBitmapState = state;
-        updateLocalState(res);
 
-        if (mBitmapState != null && res != null) {
-            mBitmapState.mTargetDensity = mTargetDensity;
-        }
+        updateLocalState(res);
     }
 
     /**
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 36a4d26..f17cd76 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -37,7 +37,6 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.ColorFilter;
-import android.graphics.ImageDecoder;
 import android.graphics.Insets;
 import android.graphics.NinePatch;
 import android.graphics.Outline;
@@ -51,13 +50,11 @@
 import android.os.Trace;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
-import android.util.Log;
 import android.util.StateSet;
 import android.util.TypedValue;
 import android.util.Xml;
 import android.view.View;
 
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.ref.WeakReference;
@@ -1178,10 +1175,6 @@
             return null;
         }
 
-        if (opts == null) {
-            return getBitmapDrawable(res, value, is);
-        }
-
         /*  ugh. The decodeStream contract is that we have already allocated
             the pad rect, but if the bitmap does not had a ninepatch chunk,
             then the pad will be ignored. If we could change this to lazily
@@ -1214,33 +1207,6 @@
         return null;
     }
 
-    private static Drawable getBitmapDrawable(Resources res, TypedValue value, InputStream is) {
-        try {
-            ImageDecoder.Source source = null;
-            if (value != null) {
-                int density = Bitmap.DENSITY_NONE;
-                if (value.density == TypedValue.DENSITY_DEFAULT) {
-                    density = DisplayMetrics.DENSITY_DEFAULT;
-                } else if (value.density != TypedValue.DENSITY_NONE) {
-                    density = value.density;
-                }
-                source = ImageDecoder.createSource(res, is, density);
-            } else {
-                source = ImageDecoder.createSource(res, is);
-            }
-
-            return ImageDecoder.decodeDrawable(source, (decoder, info, src) -> {
-                decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
-            });
-        } catch (IOException e) {
-            /*  do nothing.
-                If the exception happened on decode, the drawable will be null.
-            */
-            Log.e("Drawable", "Unable to decode stream: " + e);
-        }
-        return null;
-    }
-
     /**
      * Create a drawable from an XML document. For more information on how to
      * create resources in XML, see
@@ -1340,10 +1306,11 @@
         }
 
         Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, pathName);
-        try (FileInputStream stream = new FileInputStream(pathName)) {
-            return getBitmapDrawable(null, null, stream);
-        } catch(IOException e) {
-            // Do nothing; we will just return null if the FileInputStream had an error
+        try {
+            Bitmap bm = BitmapFactory.decodeFile(pathName);
+            if (bm != null) {
+                return drawableFromBitmap(null, bm, null, null, null, pathName);
+            }
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
         }
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 7cacaf6..17f9b7c 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -137,6 +137,7 @@
     whole_static_libs: ["libskia"],
 
     srcs: [
+        "hwui/AnimatedImageDrawable.cpp",
         "hwui/Bitmap.cpp",
         "font/CacheTexture.cpp",
         "font/Font.cpp",
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index fb7b246..e1df1e7 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -495,9 +495,9 @@
                                           refPaint(paint), refBitmap(bitmap), refPatch(&patch)));
 }
 
-void RecordingCanvas::drawAnimatedImage(SkAnimatedImage*, float left, float top,
-                                        const SkPaint*) {
+double RecordingCanvas::drawAnimatedImage(AnimatedImageDrawable*) {
     // Unimplemented
+    return 0;
 }
 
 // Text
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index dd06ada..e663402 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -183,8 +183,7 @@
     virtual void drawNinePatch(Bitmap& bitmap, const android::Res_png_9patch& chunk, float dstLeft,
                                float dstTop, float dstRight, float dstBottom,
                                const SkPaint* paint) override;
-    virtual void drawAnimatedImage(SkAnimatedImage*, float left, float top,
-                                   const SkPaint* paint) override;
+    virtual double drawAnimatedImage(AnimatedImageDrawable*) override;
 
     // Text
     virtual bool drawTextAbsolutePos() const override { return false; }
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index dc274cf..b2edd33 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -725,18 +725,8 @@
     mCanvas->drawImageLattice(image.get(), lattice, dst, addFilter(paint, &tmpPaint, colorFilter));
 }
 
-void SkiaCanvas::drawAnimatedImage(SkAnimatedImage* image, float left, float top,
-                                   const SkPaint* paint) {
-    sk_sp<SkPicture> pic(image->newPictureSnapshot());
-    SkMatrix matrixStorage;
-    SkMatrix* matrix;
-    if (left == 0.0f && top == 0.0f) {
-        matrix = nullptr;
-    } else {
-        matrixStorage = SkMatrix::MakeTrans(left, top);
-        matrix = &matrixStorage;
-    }
-    mCanvas->drawPicture(pic.get(), matrix, paint);
+double SkiaCanvas::drawAnimatedImage(AnimatedImageDrawable* imgDrawable) {
+    return imgDrawable->drawStaging(mCanvas);
 }
 
 void SkiaCanvas::drawVectorDrawable(VectorDrawableRoot* vectorDrawable) {
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 7137210..3efc22a 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -124,8 +124,7 @@
     virtual void drawNinePatch(Bitmap& bitmap, const android::Res_png_9patch& chunk, float dstLeft,
                                float dstTop, float dstRight, float dstBottom,
                                const SkPaint* paint) override;
-    virtual void drawAnimatedImage(SkAnimatedImage*, float left, float top,
-                                   const SkPaint* paint) override;
+    virtual double drawAnimatedImage(AnimatedImageDrawable* imgDrawable) override;
 
     virtual bool drawTextAbsolutePos() const override { return true; }
     virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override;
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp
new file mode 100644
index 0000000..36dd06f
--- /dev/null
+++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "AnimatedImageDrawable.h"
+
+#include "thread/Task.h"
+#include "thread/TaskManager.h"
+#include "thread/TaskProcessor.h"
+#include "utils/TraceUtils.h"
+
+#include <SkPicture.h>
+#include <SkRefCnt.h>
+#include <SkTime.h>
+#include <SkTLazy.h>
+
+namespace android {
+
+AnimatedImageDrawable::AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage)
+    : mSkAnimatedImage(std::move(animatedImage)) { }
+
+void AnimatedImageDrawable::syncProperties() {
+    mAlpha = mStagingAlpha;
+    mColorFilter = mStagingColorFilter;
+}
+
+void AnimatedImageDrawable::start() {
+    SkAutoExclusive lock(mLock);
+
+    mSnapshot.reset(mSkAnimatedImage->newPictureSnapshot());
+
+    mSkAnimatedImage->start();
+}
+
+void AnimatedImageDrawable::stop() {
+    SkAutoExclusive lock(mLock);
+    mSkAnimatedImage->stop();
+    mSnapshot.reset(nullptr);
+}
+
+bool AnimatedImageDrawable::isRunning() {
+    return mSkAnimatedImage->isRunning();
+}
+
+// This is really a Task<void> but that doesn't really work when Future<>
+// expects to be able to get/set a value
+class AnimatedImageDrawable::AnimatedImageTask : public uirenderer::Task<bool> {
+public:
+    AnimatedImageTask(AnimatedImageDrawable* animatedImageDrawable)
+            : mAnimatedImageDrawable(sk_ref_sp(animatedImageDrawable)) {}
+
+    sk_sp<AnimatedImageDrawable> mAnimatedImageDrawable;
+    bool mIsCompleted = false;
+};
+
+class AnimatedImageDrawable::AnimatedImageTaskProcessor : public uirenderer::TaskProcessor<bool> {
+public:
+    explicit AnimatedImageTaskProcessor(uirenderer::TaskManager* taskManager)
+            : uirenderer::TaskProcessor<bool>(taskManager) {}
+    ~AnimatedImageTaskProcessor() {}
+
+    virtual void onProcess(const sp<uirenderer::Task<bool>>& task) override {
+        ATRACE_NAME("Updating AnimatedImageDrawables");
+        AnimatedImageTask* t = static_cast<AnimatedImageTask*>(task.get());
+        t->mAnimatedImageDrawable->update();
+        t->mIsCompleted = true;
+        task->setResult(true);
+    };
+};
+
+void AnimatedImageDrawable::scheduleUpdate(uirenderer::TaskManager* taskManager) {
+    if (!mSkAnimatedImage->isRunning()
+            || (mDecodeTask.get() != nullptr && !mDecodeTask->mIsCompleted)) {
+        return;
+    }
+
+    if (!mDecodeTaskProcessor.get()) {
+        mDecodeTaskProcessor = new AnimatedImageTaskProcessor(taskManager);
+    }
+
+    // TODO get one frame ahead and only schedule updates when you need to replenish
+    mDecodeTask = new AnimatedImageTask(this);
+    mDecodeTaskProcessor->add(mDecodeTask);
+}
+
+void AnimatedImageDrawable::update() {
+    SkAutoExclusive lock(mLock);
+
+    if (!mSkAnimatedImage->isRunning()) {
+        return;
+    }
+
+    const double currentTime = SkTime::GetMSecs();
+    if (currentTime >= mNextFrameTime) {
+        mNextFrameTime = mSkAnimatedImage->update(currentTime);
+        mSnapshot.reset(mSkAnimatedImage->newPictureSnapshot());
+        mIsDirty = true;
+    }
+}
+
+void AnimatedImageDrawable::onDraw(SkCanvas* canvas) {
+    SkTLazy<SkPaint> lazyPaint;
+    if (mAlpha != SK_AlphaOPAQUE || mColorFilter.get()) {
+        lazyPaint.init();
+        lazyPaint.get()->setAlpha(mAlpha);
+        lazyPaint.get()->setColorFilter(mColorFilter);
+        lazyPaint.get()->setFilterQuality(kLow_SkFilterQuality);
+    }
+
+    SkAutoExclusive lock(mLock);
+    if (mSkAnimatedImage->isRunning()) {
+        canvas->drawPicture(mSnapshot, nullptr, lazyPaint.getMaybeNull());
+    } else {
+        // TODO: we could potentially keep the cached surface around if there is a paint and we know
+        // the drawable is attached to the view system
+        SkAutoCanvasRestore acr(canvas, false);
+        if (lazyPaint.isValid()) {
+            canvas->saveLayer(mSkAnimatedImage->getBounds(), lazyPaint.get());
+        }
+        mSkAnimatedImage->draw(canvas);
+    }
+
+    mIsDirty = false;
+}
+
+double AnimatedImageDrawable::drawStaging(SkCanvas* canvas) {
+    // update the drawable with the current time
+    double nextUpdate = mSkAnimatedImage->update(SkTime::GetMSecs());
+    SkAutoCanvasRestore acr(canvas, false);
+    if (mStagingAlpha != SK_AlphaOPAQUE || mStagingColorFilter.get()) {
+        SkPaint paint;
+        paint.setAlpha(mStagingAlpha);
+        paint.setColorFilter(mStagingColorFilter);
+        canvas->saveLayer(mSkAnimatedImage->getBounds(), &paint);
+    }
+    canvas->drawDrawable(mSkAnimatedImage.get());
+    return nextUpdate;
+}
+
+};  // namespace android
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.h b/libs/hwui/hwui/AnimatedImageDrawable.h
new file mode 100644
index 0000000..18764af
--- /dev/null
+++ b/libs/hwui/hwui/AnimatedImageDrawable.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cutils/compiler.h>
+#include <utils/RefBase.h>
+
+#include <SkAnimatedImage.h>
+#include <SkCanvas.h>
+#include <SkColorFilter.h>
+#include <SkDrawable.h>
+#include <SkMutex.h>
+
+class SkPicture;
+
+namespace android {
+
+namespace uirenderer {
+class TaskManager;
+}
+
+/**
+ * Native component of android.graphics.drawable.AnimatedImageDrawables.java.  This class can be
+ * drawn into Canvas.h and maintains the state needed to drive the animation from the RenderThread.
+ */
+class ANDROID_API AnimatedImageDrawable : public SkDrawable {
+public:
+    AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage);
+
+    /**
+     * This returns true if the animation has updated and signals that the next draw will contain
+     * new content.
+     */
+    bool isDirty() const { return mIsDirty; }
+
+    int getStagingAlpha() const { return mStagingAlpha; }
+    void setStagingAlpha(int alpha) { mStagingAlpha = alpha; }
+    void setStagingColorFilter(sk_sp<SkColorFilter> filter) { mStagingColorFilter = filter; }
+    void syncProperties();
+
+    virtual SkRect onGetBounds() override {
+        return mSkAnimatedImage->getBounds();
+    }
+
+    double drawStaging(SkCanvas* canvas);
+
+    void start();
+    void stop();
+    bool isRunning();
+
+    void scheduleUpdate(uirenderer::TaskManager* taskManager);
+
+protected:
+    virtual void onDraw(SkCanvas* canvas) override;
+
+private:
+    void update();
+
+    sk_sp<SkAnimatedImage> mSkAnimatedImage;
+    sk_sp<SkPicture> mSnapshot;
+    SkMutex mLock;
+
+    int mStagingAlpha = SK_AlphaOPAQUE;
+    sk_sp<SkColorFilter> mStagingColorFilter;
+
+    int mAlpha = SK_AlphaOPAQUE;
+    sk_sp<SkColorFilter> mColorFilter;
+    double mNextFrameTime = 0.0;
+    bool mIsDirty = false;
+
+    class AnimatedImageTask;
+    class AnimatedImageTaskProcessor;
+    sp<AnimatedImageTask> mDecodeTask;
+    sp<AnimatedImageTaskProcessor> mDecodeTaskProcessor;
+};
+
+};  // namespace android
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 5efd357..cae4542 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -73,6 +73,7 @@
 
 typedef std::function<void(uint16_t* text, float* positions)> ReadGlyphFunc;
 
+class AnimatedImageDrawable;
 class Bitmap;
 class Paint;
 struct Typeface;
@@ -238,8 +239,7 @@
                                float dstTop, float dstRight, float dstBottom,
                                const SkPaint* paint) = 0;
 
-    virtual void drawAnimatedImage(SkAnimatedImage*, float left, float top,
-                                   const SkPaint* paint) = 0;
+    virtual double drawAnimatedImage(AnimatedImageDrawable* imgDrawable) = 0;
 
     /**
      * Specifies if the positions passed to ::drawText are absolute or relative
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index cb10901..cf0b6a4 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -31,6 +31,9 @@
     for (auto& functor : mChildFunctors) {
         functor.syncFunctor();
     }
+    for (auto& animatedImage : mAnimatedImages) {
+        animatedImage->syncProperties();
+    }
     for (auto& vectorDrawable : mVectorDrawables) {
         vectorDrawable->syncProperties();
     }
@@ -89,6 +92,18 @@
     }
 
     bool isDirty = false;
+    for (auto& animatedImage : mAnimatedImages) {
+        // If any animated image in the display list needs updated, then damage the node.
+        if (animatedImage->isDirty()) {
+            isDirty = true;
+        }
+        if (animatedImage->isRunning()) {
+            static_cast<SkiaPipeline*>(info.canvasContext.getRenderPipeline())
+                    ->scheduleDeferredUpdate(animatedImage);
+            info.out.hasAnimations = true;
+        }
+    }
+
     for (auto& vectorDrawable : mVectorDrawables) {
         // If any vector drawable in the display list needs update, damage the node.
         if (vectorDrawable->isDirty()) {
@@ -109,6 +124,7 @@
 
     mMutableImages.clear();
     mVectorDrawables.clear();
+    mAnimatedImages.clear();
     mChildFunctors.clear();
     mChildNodes.clear();
 
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index 6883d33..818ec11 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include "DisplayList.h"
+#include "hwui/AnimatedImageDrawable.h"
 #include "GLFunctorDrawable.h"
 #include "RenderNodeDrawable.h"
 
@@ -144,6 +145,7 @@
     std::deque<GLFunctorDrawable> mChildFunctors;
     std::vector<SkImage*> mMutableImages;
     std::vector<VectorDrawableRoot*> mVectorDrawables;
+    std::vector<AnimatedImageDrawable*> mAnimatedImages;
     SkLiteDL mDisplayList;
 
     // mProjectionReceiver points to a child node (stored in mChildNodes) that is as a projection
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 9db39d9..534782a 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -40,6 +40,7 @@
 Vector3 SkiaPipeline::mLightCenter = {FLT_MIN, FLT_MIN, FLT_MIN};
 
 SkiaPipeline::SkiaPipeline(RenderThread& thread) : mRenderThread(thread) {
+    mAnimatedImageDrawables.reserve(30);
     mVectorDrawables.reserve(30);
 }
 
@@ -326,6 +327,15 @@
 
     ATRACE_NAME("flush commands");
     surface->getCanvas()->flush();
+
+    // TODO move to another method
+    if (!mAnimatedImageDrawables.empty()) {
+        ATRACE_NAME("Update AnimatedImageDrawables");
+        for (auto animatedImage : mAnimatedImageDrawables) {
+            animatedImage->scheduleUpdate(getTaskManager());
+        }
+        mAnimatedImageDrawables.clear();
+    }
 }
 
 namespace {
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index 2709227..cc75e9c 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -18,6 +18,7 @@
 
 #include <SkSurface.h>
 #include "FrameBuilder.h"
+#include "hwui/AnimatedImageDrawable.h"
 #include "renderthread/CanvasContext.h"
 #include "renderthread/IRenderPipeline.h"
 
@@ -54,6 +55,12 @@
 
     std::vector<VectorDrawableRoot*>* getVectorDrawables() { return &mVectorDrawables; }
 
+    void scheduleDeferredUpdate(AnimatedImageDrawable* imageDrawable) {
+        mAnimatedImageDrawables.push_back(imageDrawable);
+    }
+
+    std::vector<AnimatedImageDrawable*>* getAnimatingImages() { return &mAnimatedImageDrawables; }
+
     static void destroyLayer(RenderNode* node);
 
     static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap);
@@ -137,6 +144,11 @@
      */
     std::vector<VectorDrawableRoot*> mVectorDrawables;
 
+    /**
+     * populated by prepareTree with images with active animations
+     */
+    std::vector<AnimatedImageDrawable*> mAnimatedImageDrawables;
+
     // Block of properties used only for debugging to record a SkPicture and save it in a file.
     /**
      * mCapturedFile is used to enforce we don't capture more than once for a given name (cause
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 035cea3..eabe2e8 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -246,6 +246,12 @@
     }
 }
 
+double SkiaRecordingCanvas::drawAnimatedImage(AnimatedImageDrawable* animatedImage) {
+    drawDrawable(animatedImage);
+    mDisplayList->mAnimatedImages.push_back(animatedImage);
+    return 0;
+}
+
 };  // namespace skiapipeline
 };  // namespace uirenderer
 };  // namespace android
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
index d35bbab..0e5dbdb 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
@@ -53,6 +53,7 @@
     virtual void drawNinePatch(Bitmap& hwuiBitmap, const android::Res_png_9patch& chunk,
                                float dstLeft, float dstTop, float dstRight, float dstBottom,
                                const SkPaint* paint) override;
+    virtual double drawAnimatedImage(AnimatedImageDrawable* animatedImage) override;
 
     virtual void drawRoundRect(uirenderer::CanvasPropertyPrimitive* left,
                                uirenderer::CanvasPropertyPrimitive* top,
diff --git a/media/java/android/media/AudioPort.java b/media/java/android/media/AudioPort.java
index 19bf51d..047db19 100644
--- a/media/java/android/media/AudioPort.java
+++ b/media/java/android/media/AudioPort.java
@@ -20,7 +20,7 @@
  * An audio port is a node of the audio framework or hardware that can be connected to or
  * disconnect from another audio node to create a specific audio routing configuration.
  * Examples of audio ports are an output device (speaker) or an output mix (see AudioMixPort).
- * All attributes that are relevant for applications to make routing selection are decribed
+ * All attributes that are relevant for applications to make routing selection are described
  * in an AudioPort,  in particular:
  * - possible channel mask configurations.
  * - audio format (PCM 16bit, PCM 24bit...)
@@ -173,6 +173,7 @@
     /**
      * Build a specific configuration of this audio port for use by methods
      * like AudioManager.connectAudioPatch().
+     * @param samplingRate
      * @param channelMask The desired channel mask. AudioFormat.CHANNEL_OUT_DEFAULT if no change
      * from active configuration requested.
      * @param format The desired audio format. AudioFormat.ENCODING_DEFAULT if no change
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index b4316ba..dcd37da 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -793,7 +793,7 @@
     public static native int getPrimaryOutputFrameCount();
     public static native int getOutputLatency(int stream);
 
-    public static native int setLowRamDevice(boolean isLowRamDevice);
+    public static native int setLowRamDevice(boolean isLowRamDevice, long totalMemory);
     public static native int checkAudioFlinger();
 
     public static native int listAudioPorts(ArrayList<AudioPort> ports, int[] generation);
diff --git a/media/java/android/media/MediaBrowser2.java b/media/java/android/media/MediaBrowser2.java
index fa00902..33377bc 100644
--- a/media/java/android/media/MediaBrowser2.java
+++ b/media/java/android/media/MediaBrowser2.java
@@ -16,12 +16,14 @@
 
 package android.media;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.media.update.ApiLoader;
 import android.media.update.MediaBrowser2Provider;
 import android.os.Bundle;
 
+import java.util.List;
 import java.util.concurrent.Executor;
 
 /**
@@ -35,7 +37,7 @@
     /**
      * Callback to listen events from {@link MediaLibraryService2}.
      */
-    public abstract static class BrowserCallback extends MediaController2.ControllerCallback {
+    public static class BrowserCallback extends MediaController2.ControllerCallback {
         /**
          * Called with the result of {@link #getBrowserRoot(Bundle)}.
          * <p>
@@ -46,8 +48,55 @@
          * @param rootMediaId media id of the browser root. Can be {@code null}
          * @param rootExtra extra of the browser root. Can be {@code null}
          */
-        public abstract void onGetRootResult(Bundle rootHints, @Nullable String rootMediaId,
-                @Nullable Bundle rootExtra);
+        public void onGetRootResult(Bundle rootHints, @Nullable String rootMediaId,
+                @Nullable Bundle rootExtra) { }
+
+        /**
+         * Called when the item has been returned by the library service for the previous
+         * {@link MediaBrowser2#getItem} call.
+         * <p>
+         * Result can be null if there had been error.
+         *
+         * @param mediaId media id
+         * @param result result. Can be {@code null}
+         */
+        public void onItemLoaded(@NonNull String mediaId, @Nullable MediaItem2 result) { }
+
+        /**
+         * Called when the list of items has been returned by the library service for the previous
+         * {@link MediaBrowser2#getChildren(String, int, int, Bundle)}.
+         *
+         * @param parentId parent id
+         * @param page page number that you've specified
+         * @param pageSize page size that you've specified
+         * @param options optional bundle that you've specified
+         * @param result result. Can be {@code null}
+         */
+        public void onChildrenLoaded(@NonNull String parentId, int page, int pageSize,
+                @Nullable Bundle options, @Nullable List<MediaItem2> result) { }
+
+        /**
+         * Called when there's change in the parent's children.
+         *
+         * @param parentId parent id that you've specified with subscribe
+         * @param options optional bundle that you've specified with subscribe
+         */
+        public void onChildrenChanged(@NonNull String parentId, @Nullable Bundle options) { }
+
+        /**
+         * Called when the search result has been returned by the library service for the previous
+         * {@link MediaBrowser2#search(String, int, int, Bundle)}.
+         * <p>
+         * Result can be null if there had been error.
+         *
+         * @param query query string that you've specified
+         * @param page page number that you've specified
+         * @param pageSize page size that you've specified
+         * @param options optional bundle that you've specified
+         * @param result result. Can be {@code null}
+         */
+        public void onSearchResult(@NonNull String query, int page, int pageSize,
+                @Nullable Bundle options, @Nullable List<MediaItem2> result) { }
     }
 
     public MediaBrowser2(Context context, SessionToken token, BrowserCallback callback,
@@ -66,4 +115,62 @@
     public void getBrowserRoot(Bundle rootHints) {
         mProvider.getBrowserRoot_impl(rootHints);
     }
+
+    /**
+     * Subscribe to a parent id for the change in its children. When there's a change,
+     * {@link BrowserCallback#onChildrenChanged(String, Bundle)} will be called with the bundle
+     * that you've specified. You should call {@link #getChildren(String, int, int, Bundle)} to get
+     * the actual contents for the parent.
+     *
+     * @param parentId parent id
+     * @param options optional bundle
+     */
+    public void subscribe(String parentId, @Nullable Bundle options) {
+        mProvider.subscribe_impl(parentId, options);
+    }
+
+    /**
+     * Unsubscribe for changes to the children of the parent, which was previously subscribed with
+     * {@link #subscribe(String, Bundle)}.
+     *
+     * @param parentId parent id
+     * @param options optional bundle
+     */
+    public void unsubscribe(String parentId, @Nullable Bundle options) {
+        mProvider.unsubscribe_impl(parentId, options);
+    }
+
+    /**
+     * Get the media item with the given media id. Result would be sent back asynchronously with the
+     * {@link BrowserCallback#onItemLoaded(String, MediaItem2)}.
+     *
+     * @param mediaId media id
+     */
+    public void getItem(String mediaId) {
+        mProvider.getItem_impl(mediaId);
+    }
+
+    /**
+     * Get list of children under the parent. Result would be sent back asynchronously with the
+     * {@link BrowserCallback#onChildrenLoaded(String, int, int, Bundle, List)}.
+     *
+     * @param parentId
+     * @param page
+     * @param pageSize
+     * @param options
+     */
+    public void getChildren(String parentId, int page, int pageSize, @Nullable Bundle options) {
+        mProvider.getChildren_impl(parentId, page, pageSize, options);
+    }
+
+    /**
+     *
+     * @param query search query deliminated by string
+     * @param page page number to get search result. Starts from {@code 1}
+     * @param pageSize page size. Should be greater or equal to {@code 1}
+     * @param extras extra bundle
+     */
+    public void search(String query, int page, int pageSize, Bundle extras) {
+        mProvider.search_impl(query, page, pageSize, extras);
+    }
 }
diff --git a/media/java/android/media/MediaController2.java b/media/java/android/media/MediaController2.java
index 3836e78..dca1027 100644
--- a/media/java/android/media/MediaController2.java
+++ b/media/java/android/media/MediaController2.java
@@ -18,16 +18,19 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.PendingIntent;
 import android.content.Context;
-import android.media.MediaPlayerBase.PlaybackListener;
+import android.media.MediaSession2.Command;
 import android.media.MediaSession2.CommandButton;
 import android.media.MediaSession2.CommandGroup;
 import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.PlaylistParam;
 import android.media.session.MediaSessionManager;
-import android.media.session.PlaybackState;
 import android.media.update.ApiLoader;
 import android.media.update.MediaController2Provider;
-import android.os.Handler;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ResultReceiver;
 
 import java.util.List;
 import java.util.concurrent.Executor;
@@ -37,7 +40,7 @@
  * {@link MediaSessionService2} in any status. Media buttons and other commands can be sent to
  * the session.
  * <p>
- * When you're done, use {@link #release()} to clean up resources. This also helps session service
+ * When you're done, use {@link #close()} to clean up resources. This also helps session service
  * to be destroyed when there's no controller associated with it.
  * <p>
  * When controlling {@link MediaSession2}, the controller will be available immediately after
@@ -87,7 +90,7 @@
         public void onDisconnected() { }
 
         /**
-         * Called when the session sets the custom layout through the
+         * Called when the session set the custom layout through the
          * {@link MediaSession2#setCustomLayout(ControllerInfo, List)}.
          * <p>
          * Can be called before {@link #onConnected(CommandGroup)} is called.
@@ -95,6 +98,137 @@
          * @param layout
          */
         public void onCustomLayoutChanged(List<CommandButton> layout) { }
+
+        /**
+         * Called when the session has changed anything related with the {@link PlaybackInfo}.
+         *
+         * @param info new playback info
+         */
+        public void onAudioInfoChanged(PlaybackInfo info) { }
+
+        /**
+         * Called when the allowed commands are changed by session.
+         *
+         * @param commands newly allowed commands
+         */
+        public void onAllowedCommandsChanged(CommandGroup commands) { }
+
+        /**
+         * Called when the session sent a custom command.
+         *
+         * @param command
+         * @param args
+         * @param receiver
+         */
+        public void onCustomCommand(Command command, @Nullable Bundle args,
+                @Nullable ResultReceiver receiver) { }
+
+        /**
+         * Called when the playlist is changed.
+         *
+         * @param list
+         * @param param
+         */
+        public void onPlaylistChanged(
+                @NonNull List<MediaItem2> list, @NonNull PlaylistParam param) { }
+
+        /**
+         * Called when the playback state is changed.
+         *
+         * @param state
+         */
+        public void onPlaybackStateChanged(@NonNull PlaybackState2 state) { }
+    }
+
+    /**
+     * Holds information about the current playback and how audio is handled for
+     * this session.
+     */
+    // The same as MediaController.PlaybackInfo
+    public static final class PlaybackInfo {
+        /**
+         * The session uses remote playback.
+         */
+        public static final int PLAYBACK_TYPE_REMOTE = 2;
+        /**
+         * The session uses local playback.
+         */
+        public static final int PLAYBACK_TYPE_LOCAL = 1;
+
+        private final int mVolumeType;
+        private final int mVolumeControl;
+        private final int mMaxVolume;
+        private final int mCurrentVolume;
+        private final AudioAttributes mAudioAttrs;
+
+        /**
+         * @hide
+         */
+        public PlaybackInfo(int type, AudioAttributes attrs, int control, int max, int current) {
+            mVolumeType = type;
+            mAudioAttrs = attrs;
+            mVolumeControl = control;
+            mMaxVolume = max;
+            mCurrentVolume = current;
+        }
+
+        /**
+         * Get the type of playback which affects volume handling. One of:
+         * <ul>
+         * <li>{@link #PLAYBACK_TYPE_LOCAL}</li>
+         * <li>{@link #PLAYBACK_TYPE_REMOTE}</li>
+         * </ul>
+         *
+         * @return The type of playback this session is using.
+         */
+        public int getPlaybackType() {
+            return mVolumeType;
+        }
+
+        /**
+         * Get the audio attributes for this session. The attributes will affect
+         * volume handling for the session. When the volume type is
+         * {@link PlaybackInfo#PLAYBACK_TYPE_REMOTE} these may be ignored by the
+         * remote volume handler.
+         *
+         * @return The attributes for this session.
+         */
+        public AudioAttributes getAudioAttributes() {
+            return mAudioAttrs;
+        }
+
+        /**
+         * Get the type of volume control that can be used. One of:
+         * <ul>
+         * <li>{@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}</li>
+         * <li>{@link VolumeProvider#VOLUME_CONTROL_RELATIVE}</li>
+         * <li>{@link VolumeProvider#VOLUME_CONTROL_FIXED}</li>
+         * </ul>
+         *
+         * @return The type of volume control that may be used with this
+         *         session.
+         */
+        public int getVolumeControl() {
+            return mVolumeControl;
+        }
+
+        /**
+         * Get the maximum volume that may be set for this session.
+         *
+         * @return The maximum allowed volume where this session is playing.
+         */
+        public int getMaxVolume() {
+            return mMaxVolume;
+        }
+
+        /**
+         * Get the current volume for this session.
+         *
+         * @return The current volume where this session is playing.
+         */
+        public int getCurrentVolume() {
+            return mCurrentVolume;
+        }
     }
 
     private final MediaController2Provider mProvider;
@@ -147,8 +281,7 @@
     /**
      * @return token
      */
-    public @NonNull
-    SessionToken getSessionToken() {
+    public @NonNull SessionToken getSessionToken() {
         return mProvider.getSessionToken_impl();
     }
 
@@ -179,33 +312,304 @@
         mProvider.skipToNext_impl();
     }
 
+    /**
+     * Request that the player prepare its playback. In other words, other sessions can continue
+     * to play during the preparation of this session. This method can be used to speed up the
+     * start of the playback. Once the preparation is done, the session will change its playback
+     * state to {@link PlaybackState2#STATE_PAUSED}. Afterwards, {@link #play} can be called to
+     * start playback.
+     */
+    public void prepare() {
+        mProvider.prepare_impl();
+    }
 
-    public @Nullable PlaybackState getPlaybackState() {
+    /**
+     * Start fast forwarding. If playback is already fast forwarding this
+     * may increase the rate.
+     */
+    public void fastForward() {
+        mProvider.fastForward_impl();
+    }
+
+    /**
+     * Start rewinding. If playback is already rewinding this may increase
+     * the rate.
+     */
+    public void rewind() {
+        mProvider.rewind_impl();
+    }
+
+    /**
+     * Move to a new location in the media stream.
+     *
+     * @param pos Position to move to, in milliseconds.
+     */
+    public void seekTo(long pos) {
+        mProvider.seekTo_impl(pos);
+    }
+
+    /**
+     * Sets the index of current DataSourceDesc in the play list to be played.
+     *
+     * @param index the index of DataSourceDesc in the play list you want to play
+     * @throws IllegalArgumentException if the play list is null
+     * @throws NullPointerException if index is outside play list range
+     */
+    public void setCurrentPlaylistItem(int index) {
+        mProvider.setCurrentPlaylistItem_impl(index);
+    }
+
+    /**
+     * @hide
+     */
+    public void skipForward() {
+        // To match with KEYCODE_MEDIA_SKIP_FORWARD
+    }
+
+    /**
+     * @hide
+     */
+    public void skipBackward() {
+        // To match with KEYCODE_MEDIA_SKIP_BACKWARD
+    }
+
+    /**
+     * Request that the player start playback for a specific media id.
+     *
+     * @param mediaId The id of the requested media.
+     * @param extras Optional extras that can include extra information about the media item
+     *               to be played.
+     */
+    public void playFromMediaId(@NonNull String mediaId, @Nullable Bundle extras) {
+        mProvider.playFromMediaId_impl(mediaId, extras);
+    }
+
+    /**
+     * Request that the player start playback for a specific search query.
+     * An empty or null query should be treated as a request to play any
+     * music.
+     *
+     * @param query The search query.
+     * @param extras Optional extras that can include extra information
+     *               about the query.
+     */
+    public void playFromSearch(@NonNull String query, @Nullable Bundle extras) {
+        mProvider.playFromSearch_impl(query, extras);
+    }
+
+    /**
+     * Request that the player start playback for a specific {@link Uri}.
+     *
+     * @param uri The URI of the requested media.
+     * @param extras Optional extras that can include extra information about the media item
+     *               to be played.
+     */
+    public void playFromUri(@NonNull String uri, @Nullable Bundle extras) {
+        mProvider.playFromUri_impl(uri, extras);
+    }
+
+
+    /**
+     * Request that the player prepare playback for a specific media id. In other words, other
+     * sessions can continue to play during the preparation of this session. This method can be
+     * used to speed up the start of the playback. Once the preparation is done, the session
+     * will change its playback state to {@link PlaybackState2#STATE_PAUSED}. Afterwards,
+     * {@link #play} can be called to start playback. If the preparation is not needed,
+     * {@link #playFromMediaId} can be directly called without this method.
+     *
+     * @param mediaId The id of the requested media.
+     * @param extras Optional extras that can include extra information about the media item
+     *               to be prepared.
+     */
+    public void prepareFromMediaId(@NonNull String mediaId, @Nullable Bundle extras) {
+        mProvider.prepareMediaId_impl(mediaId, extras);
+    }
+
+    /**
+     * Request that the player prepare playback for a specific search query. An empty or null
+     * query should be treated as a request to prepare any music. In other words, other sessions
+     * can continue to play during the preparation of this session. This method can be used to
+     * speed up the start of the playback. Once the preparation is done, the session will
+     * change its playback state to {@link PlaybackState2#STATE_PAUSED}. Afterwards,
+     * {@link #play} can be called to start playback. If the preparation is not needed,
+     * {@link #playFromSearch} can be directly called without this method.
+     *
+     * @param query The search query.
+     * @param extras Optional extras that can include extra information
+     *               about the query.
+     */
+    public void prepareFromSearch(@NonNull String query, @Nullable Bundle extras) {
+        mProvider.prepareFromSearch_impl(query, extras);
+    }
+
+    /**
+     * Request that the player prepare playback for a specific {@link Uri}. In other words,
+     * other sessions can continue to play during the preparation of this session. This method
+     * can be used to speed up the start of the playback. Once the preparation is done, the
+     * session will change its playback state to {@link PlaybackState2#STATE_PAUSED}. Afterwards,
+     * {@link #play} can be called to start playback. If the preparation is not needed,
+     * {@link #playFromUri} can be directly called without this method.
+     *
+     * @param uri The URI of the requested media.
+     * @param extras Optional extras that can include extra information about the media item
+     *               to be prepared.
+     */
+    public void prepareFromUri(@NonNull Uri uri, @Nullable Bundle extras) {
+        mProvider.prepareFromUri_impl(uri, extras);
+    }
+
+    /**
+     * Set the volume of the output this session is playing on. The command will be ignored if it
+     * does not support {@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}.
+     * <p>
+     * If the session is local playback, this changes the device's volume with the stream that
+     * session's player is using. Flags will be specified for the {@link AudioManager}.
+     * <p>
+     * If the session is remote player (i.e. session has set volume provider), its volume provider
+     * will receive this request instead.
+     *
+     * @see #getPlaybackInfo()
+     * @param value The value to set it to, between 0 and the reported max.
+     * @param flags flags from {@link AudioManager} to include with the volume request for local
+     *              playback
+     */
+    public void setVolumeTo(int value, int flags) {
+        mProvider.setVolumeTo_impl(value, flags);
+    }
+
+    /**
+     * Adjust the volume of the output this session is playing on. The direction
+     * must be one of {@link AudioManager#ADJUST_LOWER},
+     * {@link AudioManager#ADJUST_RAISE}, or {@link AudioManager#ADJUST_SAME}.
+     * The command will be ignored if the session does not support
+     * {@link VolumeProvider#VOLUME_CONTROL_RELATIVE} or
+     * {@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}.
+     * <p>
+     * If the session is local playback, this changes the device's volume with the stream that
+     * session's player is using. Flags will be specified for the {@link AudioManager}.
+     * <p>
+     * If the session is remote player (i.e. session has set volume provider), its volume provider
+     * will receive this request instead.
+     *
+     * @see #getPlaybackInfo()
+     * @param direction The direction to adjust the volume in.
+     * @param flags flags from {@link AudioManager} to include with the volume request for local
+     *              playback
+     */
+    public void adjustVolume(int direction, int flags) {
+        mProvider.adjustVolume_impl(direction, flags);
+    }
+
+    /**
+     * Get the rating type supported by the session. One of:
+     * <ul>
+     * <li>{@link Rating2#RATING_NONE}</li>
+     * <li>{@link Rating2#RATING_HEART}</li>
+     * <li>{@link Rating2#RATING_THUMB_UP_DOWN}</li>
+     * <li>{@link Rating2#RATING_3_STARS}</li>
+     * <li>{@link Rating2#RATING_4_STARS}</li>
+     * <li>{@link Rating2#RATING_5_STARS}</li>
+     * <li>{@link Rating2#RATING_PERCENTAGE}</li>
+     * </ul>
+     *
+     * @return The supported rating type
+     */
+    public int getRatingType() {
+        return mProvider.getRatingType_impl();
+    }
+
+    /**
+     * Get an intent for launching UI associated with this session if one exists.
+     *
+     * @return A {@link PendingIntent} to launch UI or null.
+     */
+    public @Nullable PendingIntent getSessionActivity() {
+        return mProvider.getSessionActivity_impl();
+    }
+
+    /**
+     * Get the latest {@link PlaybackState2} from the session.
+     *
+     * @return a playback state
+     */
+    public PlaybackState2 getPlaybackState() {
         return mProvider.getPlaybackState_impl();
     }
 
     /**
-     * Add a {@link PlaybackListener} to listen changes in the
-     * {@link MediaSession2}.
+     * Get the current playback info for this session.
      *
-     * @param listener the listener that will be run
-     * @param handler the Handler that will receive the listener
-     * @throws IllegalArgumentException Called when either the listener or handler is {@code null}.
+     * @return The current playback info or null.
      */
-    // TODO(jaewan): Match with the addSessionAvailabilityListener() that tells the current state
-    //               through the listener.
-    // TODO(jaewan): Can handler be null? Follow the API guideline after it's finalized.
-    public void addPlaybackListener(@NonNull PlaybackListener listener, @NonNull Handler handler) {
-        mProvider.addPlaybackListener_impl(listener, handler);
+    public @Nullable PlaybackInfo getPlaybackInfo() {
+        return mProvider.getPlaybackInfo_impl();
     }
 
     /**
-     * Remove previously added {@link PlaybackListener}.
+     * Rate the current content. This will cause the rating to be set for
+     * the current user. The Rating type must match the type returned by
+     * {@link #getRatingType()}.
      *
-     * @param listener the listener to be removed
-     * @throws IllegalArgumentException if the listener is {@code null}.
+     * @param rating The rating to set for the current content
      */
-    public void removePlaybackListener(@NonNull PlaybackListener listener) {
-        mProvider.removePlaybackListener_impl(listener);
+    public void setRating(Rating2 rating) {
+        mProvider.setRating_impl(rating);
+    }
+
+    /**
+     * Send custom command to the session
+     *
+     * @param command custom command
+     * @param args optional argument
+     * @param cb optional result receiver
+     */
+    public void sendCustomCommand(@NonNull Command command, @Nullable Bundle args,
+            @Nullable ResultReceiver cb) {
+        mProvider.sendCustomCommand_impl(command, args, cb);
+    }
+
+    /**
+     * Return playlist from the session.
+     *
+     * @return playlist. Can be {@code null} if the controller doesn't have enough permission.
+     */
+    public @Nullable List<MediaItem2> getPlaylist() {
+        return mProvider.getPlaylist_impl();
+    }
+
+    public @Nullable PlaylistParam getPlaylistParam() {
+        return mProvider.getPlaylistParam_impl();
+    }
+
+    /**
+     * Removes the media item at index in the play list.
+     *<p>
+     * If index is same as the current index of the playlist, current playback
+     * will be stopped and playback moves to next source in the list.
+     *
+     * @return the removed DataSourceDesc at index in the play list
+     * @throws IllegalArgumentException if the play list is null
+     * @throws IndexOutOfBoundsException if index is outside play list range
+     */
+    // TODO(jaewan): Remove with index was previously rejected by council (b/36524925)
+    // TODO(jaewan): Should we also add movePlaylistItem from index to index?
+    public void removePlaylistItem(MediaItem2 item) {
+        mProvider.removePlaylistItem_impl(item);
+    }
+
+    /**
+     * Inserts the media item to the play list at position index.
+     * <p>
+     * This will not change the currently playing media item.
+     * If index is less than or equal to the current index of the play list,
+     * the current index of the play list will be incremented correspondingly.
+     *
+     * @param index the index you want to add dsd to the play list
+     * @param item the media item you want to add to the play list
+     * @throws IndexOutOfBoundsException if index is outside play list range
+     * @throws NullPointerException if dsd is null
+     */
+    public void addPlaylistItem(int index, MediaItem2 item) {
+        mProvider.addPlaylistItem_impl(index, item);
     }
 }
diff --git a/media/java/android/media/MediaItem2.java b/media/java/android/media/MediaItem2.java
new file mode 100644
index 0000000..96a87d5
--- /dev/null
+++ b/media/java/android/media/MediaItem2.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A class with information on a single media item with the metadata information.
+ * Media item are application dependent so we cannot guarantee that they contain the right values.
+ * <p>
+ * When it's sent to a controller or browser, it's anonymized and data descriptor wouldn't be sent.
+ * <p>
+ * This object isn't a thread safe.
+ *
+ * @hide
+ */
+// TODO(jaewan): Unhide and extends from DataSourceDesc.
+//               Note) Feels like an anti-pattern. We should anonymize MediaItem2 to remove *all*
+//                     information in the DataSourceDesc. Why it should extends from this?
+// TODO(jaewan): Move this to updatable
+// Previously MediaBrowser.MediaItem
+public class MediaItem2 {
+    private final int mFlags;
+    private MediaMetadata2 mMetadata;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag=true, value = { FLAG_BROWSABLE, FLAG_PLAYABLE })
+    public @interface Flags { }
+
+    /**
+     * Flag: Indicates that the item has children of its own.
+     */
+    public static final int FLAG_BROWSABLE = 1 << 0;
+
+    /**
+     * Flag: Indicates that the item is playable.
+     * <p>
+     * The id of this item may be passed to
+     * {@link MediaController2#playFromMediaId(String, Bundle)}
+     */
+    public static final int FLAG_PLAYABLE = 1 << 1;
+
+    /**
+     * Create a new media item.
+     *
+     * @param metadata metadata with the media id.
+     * @param flags The flags for this item.
+     */
+    public MediaItem2(@NonNull MediaMetadata2 metadata, @Flags int flags) {
+        mFlags = flags;
+        setMetadata(metadata);
+    }
+
+    /**
+     * Return this object as a bundle to share between processes.
+     *
+     * @return a new bundle instance
+     */
+    public Bundle toBundle() {
+        // TODO(jaewan): Fill here when we rebase.
+        return new Bundle();
+    }
+
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("MediaItem2{");
+        sb.append("mFlags=").append(mFlags);
+        sb.append(", mMetadata=").append(mMetadata);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    /**
+     * Gets the flags of the item.
+     */
+    public @Flags int getFlags() {
+        return mFlags;
+    }
+
+    /**
+     * Returns whether this item is browsable.
+     * @see #FLAG_BROWSABLE
+     */
+    public boolean isBrowsable() {
+        return (mFlags & FLAG_BROWSABLE) != 0;
+    }
+
+    /**
+     * Returns whether this item is playable.
+     * @see #FLAG_PLAYABLE
+     */
+    public boolean isPlayable() {
+        return (mFlags & FLAG_PLAYABLE) != 0;
+    }
+
+    /**
+     * Set a metadata. Metadata shouldn't be null and should have non-empty media id.
+     *
+     * @param metadata
+     */
+    public void setMetadata(@NonNull MediaMetadata2 metadata) {
+        if (metadata == null) {
+            throw new IllegalArgumentException("metadata cannot be null");
+        }
+        if (TextUtils.isEmpty(metadata.getMediaId())) {
+            throw new IllegalArgumentException("metadata must have a non-empty media id");
+        }
+        mMetadata = metadata;
+    }
+
+    /**
+     * Returns the metadata of the media.
+     */
+    public @NonNull MediaMetadata2 getMetadata() {
+        return mMetadata;
+    }
+
+    /**
+     * Returns the media id in the {@link MediaMetadata2} for this item.
+     * @see MediaMetadata2#METADATA_KEY_MEDIA_ID
+     */
+    public @Nullable String getMediaId() {
+        return mMetadata.getMediaId();
+    }
+}
diff --git a/media/java/android/media/MediaLibraryService2.java b/media/java/android/media/MediaLibraryService2.java
index bbc9407..b98936e 100644
--- a/media/java/android/media/MediaLibraryService2.java
+++ b/media/java/android/media/MediaLibraryService2.java
@@ -18,14 +18,19 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.PendingIntent;
 import android.content.Context;
 import android.media.MediaSession2.BuilderBase;
 import android.media.MediaSession2.ControllerInfo;
 import android.media.update.ApiLoader;
+import android.media.update.MediaLibraryService2Provider.MediaLibrarySessionProvider;
+import android.media.update.MediaSession2Provider;
 import android.media.update.MediaSessionService2Provider;
 import android.os.Bundle;
 import android.service.media.MediaBrowserService.BrowserRoot;
 
+import java.util.List;
+
 /**
  * Base class for media library services.
  * <p>
@@ -59,14 +64,50 @@
      * Session for the media library service.
      */
     public class MediaLibrarySession extends MediaSession2 {
+        private final MediaLibrarySessionProvider mProvider;
+
         MediaLibrarySession(Context context, MediaPlayerBase player, String id,
-                SessionCallback callback) {
-            super(context, player, id, callback);
+                SessionCallback callback, VolumeProvider volumeProvider,
+                int ratingType, PendingIntent sessionActivity) {
+            super(context, player, id, callback, volumeProvider, ratingType, sessionActivity);
+            mProvider = (MediaLibrarySessionProvider) getProvider();
         }
-        // TODO(jaewan): Place public methods here.
+
+        @Override
+        MediaSession2Provider createProvider(Context context, MediaPlayerBase player, String id,
+                SessionCallback callback, VolumeProvider volumeProvider, int ratingType,
+                PendingIntent sessionActivity) {
+            return ApiLoader.getProvider(context)
+                    .createMediaLibraryService2MediaLibrarySession(this, context, player, id,
+                            (MediaLibrarySessionCallback) callback, volumeProvider, ratingType,
+                            sessionActivity);
+        }
+
+        /**
+         * Notify subscribed controller about change in a parent's children.
+         *
+         * @param controller controller to notify
+         * @param parentId
+         * @param options
+         */
+        public void notifyChildrenChanged(@NonNull ControllerInfo controller,
+                @NonNull String parentId, @NonNull Bundle options) {
+            mProvider.notifyChildrenChanged_impl(controller, parentId, options);
+        }
+
+        /**
+         * Notify subscribed controller about change in a parent's children.
+         *
+         * @param parentId parent id
+         * @param options optional bundle
+         */
+        // This is for the backward compatibility.
+        public void notifyChildrenChanged(@NonNull String parentId, @Nullable Bundle options) {
+            mProvider.notifyChildrenChanged_impl(parentId, options);
+        }
     }
 
-    public static abstract class MediaLibrarySessionCallback extends MediaSession2.SessionCallback {
+    public static class MediaLibrarySessionCallback extends MediaSession2.SessionCallback {
         /**
          * Called to get the root information for browsing by a particular client.
          * <p>
@@ -85,8 +126,76 @@
          * @see BrowserRoot#EXTRA_OFFLINE
          * @see BrowserRoot#EXTRA_SUGGESTED
          */
-        public abstract @Nullable BrowserRoot onGetRoot(
-                @NonNull ControllerInfo controllerInfo, @Nullable Bundle rootHints);
+        public @Nullable BrowserRoot onGetRoot(@NonNull ControllerInfo controllerInfo,
+                @Nullable Bundle rootHints) {
+            return null;
+        }
+
+        /**
+         * Called to get the search result. Return search result here for the browser.
+         * <p>
+         * Return an empty list for no search result, and return {@code null} for the error.
+         *
+         * @param query The search query sent from the media browser. It contains keywords separated
+         *            by space.
+         * @param extras The bundle of service-specific arguments sent from the media browser.
+         * @return search result. {@code null} for error.
+         */
+        public @Nullable List<MediaItem2> onSearch(@NonNull ControllerInfo controllerInfo,
+                @NonNull String query, @Nullable Bundle extras) {
+            return null;
+        }
+
+        /**
+         * Called to get the search result . Return result here for the browser.
+         * <p>
+         * Return an empty list for no search result, and return {@code null} for the error.
+         *
+         * @param itemId item id to get media item.
+         * @return media item2. {@code null} for error.
+         */
+        public @Nullable MediaItem2 onLoadItem(@NonNull ControllerInfo controllerInfo,
+                @NonNull String itemId) {
+            return null;
+        }
+
+        /**
+         * Called to get the search result. Return search result here for the browser.
+         * <p>
+         * Return an empty list for no search result, and return {@code null} for the error.
+         *
+         * @param parentId parent id to get children
+         * @param page number of page
+         * @param pageSize size of the page
+         * @param options
+         * @return list of children. Can be {@code null}.
+         */
+        public @Nullable List<MediaItem2> onLoadChildren(@NonNull ControllerInfo controller,
+                @NonNull String parentId, int page, int pageSize, @Nullable Bundle options) {
+            return null;
+        }
+
+        /**
+         * Called when a controller subscribes to the parent.
+         *
+         * @param controller controller
+         * @param parentId parent id
+         * @param options optional bundle
+         */
+        public void onSubscribed(@NonNull ControllerInfo controller,
+                String parentId, @Nullable Bundle options) {
+        }
+
+        /**
+         * Called when a controller unsubscribes to the parent.
+         *
+         * @param controller controller
+         * @param parentId parent id
+         * @param options optional bundle
+         */
+        public void onUnsubscribed(@NonNull ControllerInfo controller,
+                String parentId, @Nullable Bundle options) {
+        }
     }
 
     /**
@@ -113,7 +222,8 @@
 
         @Override
         public MediaLibrarySession build() throws IllegalStateException {
-            return new MediaLibrarySession(mContext, mPlayer, mId, mCallback);
+            return new MediaLibrarySession(mContext, mPlayer, mId, mCallback,
+                    mVolumeProvider, mRatingType, mSessionActivity);
         }
     }
 
@@ -141,4 +251,95 @@
      */
     @Override
     public @NonNull abstract MediaLibrarySession onCreateSession(String sessionId);
+
+    /**
+     * Contains information that the browser service needs to send to the client
+     * when first connected.
+     */
+    public static final class BrowserRoot {
+        /**
+         * The lookup key for a boolean that indicates whether the browser service should return a
+         * browser root for recently played media items.
+         *
+         * <p>When creating a media browser for a given media browser service, this key can be
+         * supplied as a root hint for retrieving media items that are recently played.
+         * If the media browser service can provide such media items, the implementation must return
+         * the key in the root hint when
+         * {@link MediaLibrarySessionCallback#onGetRoot(ControllerInfo, Bundle)} is called back.
+         *
+         * <p>The root hint may contain multiple keys.
+         *
+         * @see #EXTRA_OFFLINE
+         * @see #EXTRA_SUGGESTED
+         */
+        public static final String EXTRA_RECENT = "android.service.media.extra.RECENT";
+
+        /**
+         * The lookup key for a boolean that indicates whether the browser service should return a
+         * browser root for offline media items.
+         *
+         * <p>When creating a media browser for a given media browser service, this key can be
+         * supplied as a root hint for retrieving media items that are can be played without an
+         * internet connection.
+         * If the media browser service can provide such media items, the implementation must return
+         * the key in the root hint when
+         * {@link MediaLibrarySessionCallback#onGetRoot(ControllerInfo, Bundle)} is called back.
+         *
+         * <p>The root hint may contain multiple keys.
+         *
+         * @see #EXTRA_RECENT
+         * @see #EXTRA_SUGGESTED
+         */
+        public static final String EXTRA_OFFLINE = "android.service.media.extra.OFFLINE";
+
+        /**
+         * The lookup key for a boolean that indicates whether the browser service should return a
+         * browser root for suggested media items.
+         *
+         * <p>When creating a media browser for a given media browser service, this key can be
+         * supplied as a root hint for retrieving the media items suggested by the media browser
+         * service. The list of media items passed in {@link android.media.browse.MediaBrowser.SubscriptionCallback#onChildrenLoaded(String, List)}
+         * is considered ordered by relevance, first being the top suggestion.
+         * If the media browser service can provide such media items, the implementation must return
+         * the key in the root hint when
+         * {@link MediaLibrarySessionCallback#onGetRoot(ControllerInfo, Bundle)} is called back.
+         *
+         * <p>The root hint may contain multiple keys.
+         *
+         * @see #EXTRA_RECENT
+         * @see #EXTRA_OFFLINE
+         */
+        public static final String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
+
+        final private String mRootId;
+        final private Bundle mExtras;
+
+        /**
+         * Constructs a browser root.
+         * @param rootId The root id for browsing.
+         * @param extras Any extras about the browser service.
+         */
+        public BrowserRoot(@NonNull String rootId, @Nullable Bundle extras) {
+            if (rootId == null) {
+                throw new IllegalArgumentException("The root id in BrowserRoot cannot be null. " +
+                        "Use null for BrowserRoot instead.");
+            }
+            mRootId = rootId;
+            mExtras = extras;
+        }
+
+        /**
+         * Gets the root id for browsing.
+         */
+        public String getRootId() {
+            return mRootId;
+        }
+
+        /**
+         * Gets any extras about the browser service.
+         */
+        public Bundle getExtras() {
+            return mExtras;
+        }
+    }
 }
diff --git a/media/java/android/media/MediaMetadata2.java b/media/java/android/media/MediaMetadata2.java
new file mode 100644
index 0000000..0e24db6
--- /dev/null
+++ b/media/java/android/media/MediaMetadata2.java
@@ -0,0 +1,815 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.annotation.Nullable;
+import android.annotation.StringDef;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.ArrayMap;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
+
+/**
+ * Contains metadata about an item, such as the title, artist, etc.
+ * @hide
+ */
+// TODO(jaewan): Move this to updatable
+public final class MediaMetadata2 {
+    private static final String TAG = "MediaMetadata2";
+
+    /**
+     * The title of the media.
+     */
+    public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
+
+    /**
+     * The artist of the media.
+     */
+    public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
+
+    /**
+     * The duration of the media in ms. A negative duration indicates that the
+     * duration is unknown (or infinite).
+     */
+    public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
+
+    /**
+     * The album title for the media.
+     */
+    public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
+
+    /**
+     * The author of the media.
+     */
+    public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
+
+    /**
+     * The writer of the media.
+     */
+    public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
+
+    /**
+     * The composer of the media.
+     */
+    public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
+
+    /**
+     * The compilation status of the media.
+     */
+    public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
+
+    /**
+     * The date the media was created or published. The format is unspecified
+     * but RFC 3339 is recommended.
+     */
+    public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
+
+    /**
+     * The year the media was created or published as a long.
+     */
+    public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
+
+    /**
+     * The genre of the media.
+     */
+    public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
+
+    /**
+     * The track number for the media.
+     */
+    public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+
+    /**
+     * The number of tracks in the media's original source.
+     */
+    public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
+
+    /**
+     * The disc number for the media's original source.
+     */
+    public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+
+    /**
+     * The artist for the album of the media's original source.
+     */
+    public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+
+    /**
+     * The artwork for the media as a {@link Bitmap}.
+     *
+     * The artwork should be relatively small and may be scaled down
+     * if it is too large. For higher resolution artwork
+     * {@link #METADATA_KEY_ART_URI} should be used instead.
+     */
+    public static final String METADATA_KEY_ART = "android.media.metadata.ART";
+
+    /**
+     * The artwork for the media as a Uri style String.
+     */
+    public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
+
+    /**
+     * The artwork for the album of the media's original source as a
+     * {@link Bitmap}.
+     * The artwork should be relatively small and may be scaled down
+     * if it is too large. For higher resolution artwork
+     * {@link #METADATA_KEY_ALBUM_ART_URI} should be used instead.
+     */
+    public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
+
+    /**
+     * The artwork for the album of the media's original source as a Uri style
+     * String.
+     */
+    public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
+
+    /**
+     * The user's rating for the media.
+     *
+     * @see Rating
+     */
+    public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
+
+    /**
+     * The overall rating for the media.
+     *
+     * @see Rating
+     */
+    public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
+
+    /**
+     * A title that is suitable for display to the user. This will generally be
+     * the same as {@link #METADATA_KEY_TITLE} but may differ for some formats.
+     * When displaying media described by this metadata this should be preferred
+     * if present.
+     */
+    public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
+
+    /**
+     * A subtitle that is suitable for display to the user. When displaying a
+     * second line for media described by this metadata this should be preferred
+     * to other fields if present.
+     */
+    public static final String METADATA_KEY_DISPLAY_SUBTITLE
+            = "android.media.metadata.DISPLAY_SUBTITLE";
+
+    /**
+     * A description that is suitable for display to the user. When displaying
+     * more information for media described by this metadata this should be
+     * preferred to other fields if present.
+     */
+    public static final String METADATA_KEY_DISPLAY_DESCRIPTION
+            = "android.media.metadata.DISPLAY_DESCRIPTION";
+
+    /**
+     * An icon or thumbnail that is suitable for display to the user. When
+     * displaying an icon for media described by this metadata this should be
+     * preferred to other fields if present. This must be a {@link Bitmap}.
+     *
+     * The icon should be relatively small and may be scaled down
+     * if it is too large. For higher resolution artwork
+     * {@link #METADATA_KEY_DISPLAY_ICON_URI} should be used instead.
+     */
+    public static final String METADATA_KEY_DISPLAY_ICON
+            = "android.media.metadata.DISPLAY_ICON";
+
+    /**
+     * An icon or thumbnail that is suitable for display to the user. When
+     * displaying more information for media described by this metadata the
+     * display description should be preferred to other fields when present.
+     * This must be a Uri style String.
+     */
+    public static final String METADATA_KEY_DISPLAY_ICON_URI
+            = "android.media.metadata.DISPLAY_ICON_URI";
+
+    /**
+     * A String key for identifying the content. This value is specific to the
+     * service providing the content. If used, this should be a persistent
+     * unique key for the underlying content.  It may be used with
+     * {@link MediaController2#playFromMediaId(String, Bundle)}
+     * to initiate playback when provided by a {@link MediaBrowser2} connected to
+     * the same app.
+     */
+    public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
+
+    /**
+     * A Uri formatted String representing the content. This value is specific to the
+     * service providing the content. It may be used with
+     * {@link MediaController2#playFromUri(Uri, Bundle)}
+     * to initiate playback when provided by a {@link MediaBrowser2} connected to
+     * the same app.
+     */
+    public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
+
+    /**
+     * The bluetooth folder type of the media specified in the section 6.10.2.2 of the Bluetooth
+     * AVRCP 1.5. It should be one of the following:
+     * <ul>
+     * <li>{@link #BT_FOLDER_TYPE_MIXED}</li>
+     * <li>{@link #BT_FOLDER_TYPE_TITLES}</li>
+     * <li>{@link #BT_FOLDER_TYPE_ALBUMS}</li>
+     * <li>{@link #BT_FOLDER_TYPE_ARTISTS}</li>
+     * <li>{@link #BT_FOLDER_TYPE_GENRES}</li>
+     * <li>{@link #BT_FOLDER_TYPE_PLAYLISTS}</li>
+     * <li>{@link #BT_FOLDER_TYPE_YEARS}</li>
+     * </ul>
+     */
+    public static final String METADATA_KEY_BT_FOLDER_TYPE
+            = "android.media.metadata.BT_FOLDER_TYPE";
+
+    /**
+     * The type of folder that is unknown or contains media elements of mixed types as specified in
+     * the section 6.10.2.2 of the Bluetooth AVRCP 1.5.
+     */
+    public static final long BT_FOLDER_TYPE_MIXED = 0;
+
+    /**
+     * The type of folder that contains media elements only as specified in the section 6.10.2.2 of
+     * the Bluetooth AVRCP 1.5.
+     */
+    public static final long BT_FOLDER_TYPE_TITLES = 1;
+
+    /**
+     * The type of folder that contains folders categorized by album as specified in the section
+     * 6.10.2.2 of the Bluetooth AVRCP 1.5.
+     */
+    public static final long BT_FOLDER_TYPE_ALBUMS = 2;
+
+    /**
+     * The type of folder that contains folders categorized by artist as specified in the section
+     * 6.10.2.2 of the Bluetooth AVRCP 1.5.
+     */
+    public static final long BT_FOLDER_TYPE_ARTISTS = 3;
+
+    /**
+     * The type of folder that contains folders categorized by genre as specified in the section
+     * 6.10.2.2 of the Bluetooth AVRCP 1.5.
+     */
+    public static final long BT_FOLDER_TYPE_GENRES = 4;
+
+    /**
+     * The type of folder that contains folders categorized by playlist as specified in the section
+     * 6.10.2.2 of the Bluetooth AVRCP 1.5.
+     */
+    public static final long BT_FOLDER_TYPE_PLAYLISTS = 5;
+
+    /**
+     * The type of folder that contains folders categorized by year as specified in the section
+     * 6.10.2.2 of the Bluetooth AVRCP 1.5.
+     */
+    public static final long BT_FOLDER_TYPE_YEARS = 6;
+
+    /**
+     * Whether the media is an advertisement. A value of 0 indicates it is not an advertisement. A
+     * value of 1 or non-zero indicates it is an advertisement. If not specified, this value is set
+     * to 0 by default.
+     */
+    public static final String METADATA_KEY_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT";
+
+    /**
+     * The download status of the media which will be used for later offline playback. It should be
+     * one of the following:
+     *
+     * <ul>
+     * <li>{@link #STATUS_NOT_DOWNLOADED}</li>
+     * <li>{@link #STATUS_DOWNLOADING}</li>
+     * <li>{@link #STATUS_DOWNLOADED}</li>
+     * </ul>
+     */
+    public static final String METADATA_KEY_DOWNLOAD_STATUS =
+            "android.media.metadata.DOWNLOAD_STATUS";
+
+    /**
+     * The status value to indicate the media item is not downloaded.
+     *
+     * @see #METADATA_KEY_DOWNLOAD_STATUS
+     */
+    public static final long STATUS_NOT_DOWNLOADED = 0;
+
+    /**
+     * The status value to indicate the media item is being downloaded.
+     *
+     * @see #METADATA_KEY_DOWNLOAD_STATUS
+     */
+    public static final long STATUS_DOWNLOADING = 1;
+
+    /**
+     * The status value to indicate the media item is downloaded for later offline playback.
+     *
+     * @see #METADATA_KEY_DOWNLOAD_STATUS
+     */
+    public static final long STATUS_DOWNLOADED = 2;
+
+    /**
+     * A {@link Bundle} extra.
+     * @hide
+     */
+    public static final String METADATA_KEY_EXTRA = "android.media.metadata.EXTRA";
+
+    /**
+     * @hide
+     */
+    @StringDef({METADATA_KEY_TITLE, METADATA_KEY_ARTIST, METADATA_KEY_ALBUM, METADATA_KEY_AUTHOR,
+            METADATA_KEY_WRITER, METADATA_KEY_COMPOSER, METADATA_KEY_COMPILATION,
+            METADATA_KEY_DATE, METADATA_KEY_GENRE, METADATA_KEY_ALBUM_ARTIST, METADATA_KEY_ART_URI,
+            METADATA_KEY_ALBUM_ART_URI, METADATA_KEY_DISPLAY_TITLE, METADATA_KEY_DISPLAY_SUBTITLE,
+            METADATA_KEY_DISPLAY_DESCRIPTION, METADATA_KEY_DISPLAY_ICON_URI,
+            METADATA_KEY_MEDIA_ID, METADATA_KEY_MEDIA_URI})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TextKey {}
+
+    /**
+     * @hide
+     */
+    @StringDef({METADATA_KEY_DURATION, METADATA_KEY_YEAR, METADATA_KEY_TRACK_NUMBER,
+            METADATA_KEY_NUM_TRACKS, METADATA_KEY_DISC_NUMBER, METADATA_KEY_BT_FOLDER_TYPE,
+            METADATA_KEY_ADVERTISEMENT, METADATA_KEY_DOWNLOAD_STATUS})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface LongKey {}
+
+    /**
+     * @hide
+     */
+    @StringDef({METADATA_KEY_ART, METADATA_KEY_ALBUM_ART, METADATA_KEY_DISPLAY_ICON})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface BitmapKey {}
+
+    /**
+     * @hide
+     */
+    @StringDef({METADATA_KEY_USER_RATING, METADATA_KEY_RATING})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RatingKey {}
+
+    static final int METADATA_TYPE_LONG = 0;
+    static final int METADATA_TYPE_TEXT = 1;
+    static final int METADATA_TYPE_BITMAP = 2;
+    static final int METADATA_TYPE_RATING = 3;
+    static final ArrayMap<String, Integer> METADATA_KEYS_TYPE;
+
+    static {
+        METADATA_KEYS_TYPE = new ArrayMap<String, Integer>();
+        METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_DURATION, METADATA_TYPE_LONG);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_AUTHOR, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_WRITER, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_COMPOSER, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_COMPILATION, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_DATE, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_YEAR, METADATA_TYPE_LONG);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_TRACK_NUMBER, METADATA_TYPE_LONG);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_NUM_TRACKS, METADATA_TYPE_LONG);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_DISC_NUMBER, METADATA_TYPE_LONG);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ARTIST, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_ART_URI, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART, METADATA_TYPE_BITMAP);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART_URI, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_USER_RATING, METADATA_TYPE_RATING);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_RATING, METADATA_TYPE_RATING);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_TITLE, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_SUBTITLE, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_DESCRIPTION, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON, METADATA_TYPE_BITMAP);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON_URI, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_ID, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_BT_FOLDER_TYPE, METADATA_TYPE_LONG);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_URI, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_ADVERTISEMENT, METADATA_TYPE_LONG);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_DOWNLOAD_STATUS, METADATA_TYPE_LONG);
+    }
+
+    private static final @TextKey String[] PREFERRED_DESCRIPTION_ORDER = {
+            METADATA_KEY_TITLE,
+            METADATA_KEY_ARTIST,
+            METADATA_KEY_ALBUM,
+            METADATA_KEY_ALBUM_ARTIST,
+            METADATA_KEY_WRITER,
+            METADATA_KEY_AUTHOR,
+            METADATA_KEY_COMPOSER
+    };
+
+    private static final @BitmapKey String[] PREFERRED_BITMAP_ORDER = {
+            METADATA_KEY_DISPLAY_ICON,
+            METADATA_KEY_ART,
+            METADATA_KEY_ALBUM_ART
+    };
+
+    private static final @TextKey String[] PREFERRED_URI_ORDER = {
+            METADATA_KEY_DISPLAY_ICON_URI,
+            METADATA_KEY_ART_URI,
+            METADATA_KEY_ALBUM_ART_URI
+    };
+
+    final Bundle mBundle;
+
+    /**
+     * @hide
+     */
+    public MediaMetadata2(Bundle bundle) {
+        mBundle = new Bundle(bundle);
+    }
+
+    /**
+     * Returns true if the given key is contained in the metadata
+     *
+     * @param key a String key
+     * @return true if the key exists in this metadata, false otherwise
+     */
+    public boolean containsKey(String key) {
+        return mBundle.containsKey(key);
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if no mapping of
+     * the desired type exists for the given key or a null value is explicitly
+     * associated with the key.
+     *
+     * @param key The key the value is stored under
+     * @return a CharSequence value, or null
+     */
+    public CharSequence getText(@TextKey String key) {
+        return mBundle.getCharSequence(key);
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if no mapping of
+     * the desired type exists for the given key or a null value is explicitly
+     * associated with the key.
+     *
+     * @
+     * @return media id. Can be {@code null}
+     */
+    public @Nullable String getMediaId() {
+        return getString(METADATA_KEY_MEDIA_ID);
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if no mapping of
+     * the desired type exists for the given key or a null value is explicitly
+     * associated with the key.
+     *
+     * @param key The key the value is stored under
+     * @return a String value, or null
+     */
+    public String getString(@TextKey String key) {
+        CharSequence text = mBundle.getCharSequence(key);
+        if (text != null) {
+            return text.toString();
+        }
+        return null;
+    }
+
+    /**
+     * Returns the value associated with the given key, or 0L if no long exists
+     * for the given key.
+     *
+     * @param key The key the value is stored under
+     * @return a long value
+     */
+    public long getLong(@LongKey String key) {
+        return mBundle.getLong(key, 0);
+    }
+
+    /**
+     * Return a {@link Rating2} for the given key or null if no rating exists for
+     * the given key.
+     *
+     * @param key The key the value is stored under
+     * @return A {@link Rating2} or null
+     */
+    public Rating2 getRating(@RatingKey String key) {
+        // TODO(jaewan): Add backward compatibility
+        Rating2 rating = null;
+        try {
+            rating = Rating2.fromBundle(mBundle.getBundle(key));
+        } catch (Exception e) {
+            // ignore, value was not a rating
+            Log.w(TAG, "Failed to retrieve a key as Rating.", e);
+        }
+        return rating;
+    }
+
+    /**
+     * Return a {@link Bitmap} for the given key or null if no bitmap exists for
+     * the given key.
+     *
+     * @param key The key the value is stored under
+     * @return A {@link Bitmap} or null
+     */
+    public Bitmap getBitmap(@BitmapKey String key) {
+        Bitmap bmp = null;
+        try {
+            bmp = mBundle.getParcelable(key);
+        } catch (Exception e) {
+            // ignore, value was not a bitmap
+            Log.w(TAG, "Failed to retrieve a key as Bitmap.", e);
+        }
+        return bmp;
+    }
+
+    /**
+     * Get the extra {@link Bundle} from the metadata object.
+     *
+     * @return A {@link Bundle} or {@code null}
+     */
+    public Bundle getExtra() {
+        try {
+            return mBundle.getBundle(METADATA_KEY_EXTRA);
+        } catch (Exception e) {
+            // ignore, value was not an bundle
+            Log.w(TAG, "Failed to retrieve an extra");
+        }
+        return null;
+    }
+
+    /**
+     * Get the number of fields in this metadata.
+     *
+     * @return The number of fields in the metadata.
+     */
+    public int size() {
+        return mBundle.size();
+    }
+
+    /**
+     * Returns a Set containing the Strings used as keys in this metadata.
+     *
+     * @return a Set of String keys
+     */
+    public Set<String> keySet() {
+        return mBundle.keySet();
+    }
+
+    /**
+     * Gets the bundle backing the metadata object. This is available to support
+     * backwards compatibility. Apps should not modify the bundle directly.
+     *
+     * @return The Bundle backing this metadata.
+     */
+    public Bundle getBundle() {
+        return mBundle;
+    }
+
+    /**
+     * Use to build MediaMetadata2 objects. The system defined metadata keys must
+     * use the appropriate data type.
+     */
+    public static final class Builder {
+        private final Bundle mBundle;
+
+        /**
+         * Create an empty Builder. Any field that should be included in the
+         * {@link MediaMetadata2} must be added.
+         */
+        public Builder() {
+            mBundle = new Bundle();
+        }
+
+        /**
+         * Create a Builder using a {@link MediaMetadata2} instance to set the
+         * initial values. All fields in the source metadata will be included in
+         * the new metadata. Fields can be overwritten by adding the same key.
+         *
+         * @param source
+         */
+        public Builder(MediaMetadata2 source) {
+            mBundle = new Bundle(source.mBundle);
+        }
+
+        /**
+         * Create a Builder using a {@link MediaMetadata2} instance to set
+         * initial values, but replace bitmaps with a scaled down copy if they
+         * are larger than maxBitmapSize.
+         *
+         * @param source The original metadata to copy.
+         * @param maxBitmapSize The maximum height/width for bitmaps contained
+         *            in the metadata.
+         * @hide
+         */
+        public Builder(MediaMetadata2 source, int maxBitmapSize) {
+            this(source);
+            for (String key : mBundle.keySet()) {
+                Object value = mBundle.get(key);
+                if (value instanceof Bitmap) {
+                    Bitmap bmp = (Bitmap) value;
+                    if (bmp.getHeight() > maxBitmapSize || bmp.getWidth() > maxBitmapSize) {
+                        putBitmap(key, scaleBitmap(bmp, maxBitmapSize));
+                    }
+                }
+            }
+        }
+
+        /**
+         * Put a CharSequence value into the metadata. Custom keys may be used,
+         * but if the METADATA_KEYs defined in this class are used they may only
+         * be one of the following:
+         * <ul>
+         * <li>{@link #METADATA_KEY_TITLE}</li>
+         * <li>{@link #METADATA_KEY_ARTIST}</li>
+         * <li>{@link #METADATA_KEY_ALBUM}</li>
+         * <li>{@link #METADATA_KEY_AUTHOR}</li>
+         * <li>{@link #METADATA_KEY_WRITER}</li>
+         * <li>{@link #METADATA_KEY_COMPOSER}</li>
+         * <li>{@link #METADATA_KEY_DATE}</li>
+         * <li>{@link #METADATA_KEY_GENRE}</li>
+         * <li>{@link #METADATA_KEY_ALBUM_ARTIST}</li>
+         * <li>{@link #METADATA_KEY_ART_URI}</li>
+         * <li>{@link #METADATA_KEY_ALBUM_ART_URI}</li>
+         * <li>{@link #METADATA_KEY_DISPLAY_TITLE}</li>
+         * <li>{@link #METADATA_KEY_DISPLAY_SUBTITLE}</li>
+         * <li>{@link #METADATA_KEY_DISPLAY_DESCRIPTION}</li>
+         * <li>{@link #METADATA_KEY_DISPLAY_ICON_URI}</li>
+         * </ul>
+         *
+         * @param key The key for referencing this value
+         * @param value The CharSequence value to store
+         * @return The Builder to allow chaining
+         */
+        public Builder putText(@TextKey String key, CharSequence value) {
+            if (METADATA_KEYS_TYPE.containsKey(key)) {
+                if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
+                    throw new IllegalArgumentException("The " + key
+                            + " key cannot be used to put a CharSequence");
+                }
+            }
+            mBundle.putCharSequence(key, value);
+            return this;
+        }
+
+        /**
+         * Put a String value into the metadata. Custom keys may be used, but if
+         * the METADATA_KEYs defined in this class are used they may only be one
+         * of the following:
+         * <ul>
+         * <li>{@link #METADATA_KEY_TITLE}</li>
+         * <li>{@link #METADATA_KEY_ARTIST}</li>
+         * <li>{@link #METADATA_KEY_ALBUM}</li>
+         * <li>{@link #METADATA_KEY_AUTHOR}</li>
+         * <li>{@link #METADATA_KEY_WRITER}</li>
+         * <li>{@link #METADATA_KEY_COMPOSER}</li>
+         * <li>{@link #METADATA_KEY_DATE}</li>
+         * <li>{@link #METADATA_KEY_GENRE}</li>
+         * <li>{@link #METADATA_KEY_ALBUM_ARTIST}</li>
+         * <li>{@link #METADATA_KEY_ART_URI}</li>
+         * <li>{@link #METADATA_KEY_ALBUM_ART_URI}</li>
+         * <li>{@link #METADATA_KEY_DISPLAY_TITLE}</li>
+         * <li>{@link #METADATA_KEY_DISPLAY_SUBTITLE}</li>
+         * <li>{@link #METADATA_KEY_DISPLAY_DESCRIPTION}</li>
+         * <li>{@link #METADATA_KEY_DISPLAY_ICON_URI}</li>
+         * </ul>
+         *
+         * @param key The key for referencing this value
+         * @param value The String value to store
+         * @return The Builder to allow chaining
+         */
+        public Builder putString(@TextKey String key, String value) {
+            if (METADATA_KEYS_TYPE.containsKey(key)) {
+                if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
+                    throw new IllegalArgumentException("The " + key
+                            + " key cannot be used to put a String");
+                }
+            }
+            mBundle.putCharSequence(key, value);
+            return this;
+        }
+
+        /**
+         * Put a long value into the metadata. Custom keys may be used, but if
+         * the METADATA_KEYs defined in this class are used they may only be one
+         * of the following:
+         * <ul>
+         * <li>{@link #METADATA_KEY_DURATION}</li>
+         * <li>{@link #METADATA_KEY_TRACK_NUMBER}</li>
+         * <li>{@link #METADATA_KEY_NUM_TRACKS}</li>
+         * <li>{@link #METADATA_KEY_DISC_NUMBER}</li>
+         * <li>{@link #METADATA_KEY_YEAR}</li>
+         * <li>{@link #METADATA_KEY_BT_FOLDER_TYPE}</li>
+         * <li>{@link #METADATA_KEY_ADVERTISEMENT}</li>
+         * <li>{@link #METADATA_KEY_DOWNLOAD_STATUS}</li>
+         * </ul>
+         *
+         * @param key The key for referencing this value
+         * @param value The String value to store
+         * @return The Builder to allow chaining
+         */
+        public Builder putLong(@LongKey String key, long value) {
+            if (METADATA_KEYS_TYPE.containsKey(key)) {
+                if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_LONG) {
+                    throw new IllegalArgumentException("The " + key
+                            + " key cannot be used to put a long");
+                }
+            }
+            mBundle.putLong(key, value);
+            return this;
+        }
+
+        /**
+         * Put a {@link Rating2} into the metadata. Custom keys may be used, but
+         * if the METADATA_KEYs defined in this class are used they may only be
+         * one of the following:
+         * <ul>
+         * <li>{@link #METADATA_KEY_RATING}</li>
+         * <li>{@link #METADATA_KEY_USER_RATING}</li>
+         * </ul>
+         *
+         * @param key The key for referencing this value
+         * @param value The String value to store
+         * @return The Builder to allow chaining
+         */
+        public Builder putRating(@RatingKey String key, Rating2 value) {
+            if (METADATA_KEYS_TYPE.containsKey(key)) {
+                if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_RATING) {
+                    throw new IllegalArgumentException("The " + key
+                            + " key cannot be used to put a Rating");
+                }
+            }
+            mBundle.putBundle(key, value.toBundle());
+
+            return this;
+        }
+
+        /**
+         * Put a {@link Bitmap} into the metadata. Custom keys may be used, but
+         * if the METADATA_KEYs defined in this class are used they may only be
+         * one of the following:
+         * <ul>
+         * <li>{@link #METADATA_KEY_ART}</li>
+         * <li>{@link #METADATA_KEY_ALBUM_ART}</li>
+         * <li>{@link #METADATA_KEY_DISPLAY_ICON}</li>
+         * </ul>
+         * Large bitmaps may be scaled down by the system when
+         * {@link android.media.session.MediaSession#setMetadata} is called.
+         * To pass full resolution images {@link Uri Uris} should be used with
+         * {@link #putString}.
+         *
+         * @param key The key for referencing this value
+         * @param value The Bitmap to store
+         * @return The Builder to allow chaining
+         */
+        public Builder putBitmap(@BitmapKey String key, Bitmap value) {
+            if (METADATA_KEYS_TYPE.containsKey(key)) {
+                if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
+                    throw new IllegalArgumentException("The " + key
+                            + " key cannot be used to put a Bitmap");
+                }
+            }
+            mBundle.putParcelable(key, value);
+            return this;
+        }
+
+        /**
+         * Set an extra {@link Bundle} into the metadata.
+         */
+        public Builder setExtra(Bundle bundle) {
+            mBundle.putBundle(METADATA_KEY_EXTRA, bundle);
+            return this;
+        }
+
+        /**
+         * Creates a {@link MediaMetadata2} instance with the specified fields.
+         *
+         * @return The new MediaMetadata2 instance
+         */
+        public MediaMetadata2 build() {
+            return new MediaMetadata2(mBundle);
+        }
+
+        private Bitmap scaleBitmap(Bitmap bmp, int maxSize) {
+            float maxSizeF = maxSize;
+            float widthScale = maxSizeF / bmp.getWidth();
+            float heightScale = maxSizeF / bmp.getHeight();
+            float scale = Math.min(widthScale, heightScale);
+            int height = (int) (bmp.getHeight() * scale);
+            int width = (int) (bmp.getWidth() * scale);
+            return Bitmap.createScaledBitmap(bmp, width, height, true);
+        }
+    }
+}
+
diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java
index 0932fea..6fdf7a8 100644
--- a/media/java/android/media/MediaSession2.java
+++ b/media/java/android/media/MediaSession2.java
@@ -18,7 +18,12 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.PendingIntent;
 import android.content.Context;
+import android.content.Intent;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
 import android.media.MediaPlayerBase.PlaybackListener;
 import android.media.session.MediaSession;
 import android.media.session.MediaSession.Callback;
@@ -26,9 +31,11 @@
 import android.media.update.ApiLoader;
 import android.media.update.MediaSession2Provider;
 import android.media.update.MediaSession2Provider.ControllerInfoProvider;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Parcelable;
+import android.os.ResultReceiver;
 import android.text.TextUtils;
 import android.util.ArraySet;
 
@@ -69,8 +76,6 @@
  */
 // TODO(jaewan): Unhide
 // TODO(jaewan): Revisit comments. Currently it's borrowed from the MediaSession.
-// TODO(jaewan): Add explicit release(), and make token @NonNull. Session will be active while the
-//               session exists, and controllers will be invalidated when session becomes inactive.
 // TODO(jaewan): Should we support thread safe? It may cause tricky issue such as b/63797089
 // TODO(jaewan): Should we make APIs for MediaSessionService2 public? It's helpful for
 //               developers that doesn't want to override from Browser, but user may not use this
@@ -79,12 +84,30 @@
     private final MediaSession2Provider mProvider;
 
     // Note: Do not define IntDef because subclass can add more command code on top of these.
+    // TODO(jaewan): Shouldn't we pull out?
     public static final int COMMAND_CODE_CUSTOM = 0;
     public static final int COMMAND_CODE_PLAYBACK_START = 1;
     public static final int COMMAND_CODE_PLAYBACK_PAUSE = 2;
     public static final int COMMAND_CODE_PLAYBACK_STOP = 3;
     public static final int COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM = 4;
     public static final int COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM = 5;
+    public static final int COMMAND_CODE_PLAYBACK_PREPARE = 6;
+    public static final int COMMAND_CODE_PLAYBACK_FAST_FORWARD = 7;
+    public static final int COMMAND_CODE_PLAYBACK_REWIND = 8;
+    public static final int COMMAND_CODE_PLAYBACK_SEEK_TO = 9;
+    public static final int COMMAND_CODE_PLAYBACK_SET_CURRENT_PLAYLIST_ITEM = 10;
+
+    public static final int COMMAND_CODE_PLAYLIST_GET = 11;
+    public static final int COMMAND_CODE_PLAYLIST_ADD = 12;
+    public static final int COMMAND_CODE_PLAYLIST_REMOVE = 13;
+
+    public static final int COMMAND_CODE_PLAY_FROM_MEDIA_ID = 14;
+    public static final int COMMAND_CODE_PLAY_FROM_URI = 15;
+    public static final int COMMAND_CODE_PLAY_FROM_SEARCH = 16;
+
+    public static final int COMMAND_CODE_PREPARE_FROM_MEDIA_ID = 17;
+    public static final int COMMAND_CODE_PREPARE_FROM_URI = 18;
+    public static final int COMMAND_CODE_PREPARE_FROM_SEARCH = 19;
 
     /**
      * Define a command that a {@link MediaController2} can send to a {@link MediaSession2}.
@@ -299,19 +322,133 @@
         }
 
         /**
-         * Called when a controller sent a command to the session. You can also reject the request
-         * by return {@code false}.
-         * <p>
-         * This method will be called on the session handler.
+         * Called when a controller is disconnected
+         *
+         * @param controller controller information
+         */
+        public void onDisconnected(@NonNull ControllerInfo controller) { }
+
+        /**
+         * Called when a controller sent a command to the session, and the command will be sent to
+         * the player directly unless you reject the request by {@code false}.
          *
          * @param controller controller information.
          * @param command a command. This method will be called for every single command.
          * @return {@code true} if you want to accept incoming command. {@code false} otherwise.
          */
+        // TODO(jaewan): Add more documentations (or make it clear) which commands can be filtered
+        //               with this.
         public boolean onCommandRequest(@NonNull ControllerInfo controller,
                 @NonNull Command command) {
             return true;
         }
+
+        /**
+         * Called when a controller set rating on the currently playing contents.
+         *
+         * @param
+         */
+        public void onSetRating(@NonNull ControllerInfo controller, @NonNull Rating2 rating) { }
+
+        /**
+         * Called when a controller sent a custom command.
+         *
+         * @param controller controller information
+         * @param customCommand custom command.
+         * @param args optional arguments
+         * @param cb optional result receiver
+         */
+        public void onCustomCommand(@NonNull ControllerInfo controller,
+                @NonNull Command customCommand, @Nullable Bundle args,
+                @Nullable ResultReceiver cb) { }
+
+        /**
+         * Override to handle requests to prepare for playing a specific mediaId.
+         * During the preparation, a session should not hold audio focus in order to allow other
+         * sessions play seamlessly. The state of playback should be updated to
+         * {@link PlaybackState#STATE_PAUSED} after the preparation is done.
+         * <p>
+         * The playback of the prepared content should start in the later calls of
+         * {@link MediaSession2#play()}.
+         * <p>
+         * Override {@link #onPlayFromMediaId} to handle requests for starting
+         * playback without preparation.
+         */
+        public void onPlayFromMediaId(@NonNull ControllerInfo controller,
+                @NonNull String mediaId, @Nullable Bundle extras) { }
+
+        /**
+         * Override to handle requests to prepare playback from a search query. An empty query
+         * indicates that the app may prepare any music. The implementation should attempt to make a
+         * smart choice about what to play. During the preparation, a session should not hold audio
+         * focus in order to allow other sessions play seamlessly. The state of playback should be
+         * updated to {@link PlaybackState#STATE_PAUSED} after the preparation is done.
+         * <p>
+         * The playback of the prepared content should start in the later calls of
+         * {@link MediaSession2#play()}.
+         * <p>
+         * Override {@link #onPlayFromSearch} to handle requests for starting playback without
+         * preparation.
+         */
+        public void onPlayFromSearch(@NonNull ControllerInfo controller,
+                @NonNull String query, @Nullable Bundle extras) { }
+
+        /**
+         * Override to handle requests to prepare a specific media item represented by a URI.
+         * During the preparation, a session should not hold audio focus in order to allow
+         * other sessions play seamlessly. The state of playback should be updated to
+         * {@link PlaybackState#STATE_PAUSED} after the preparation is done.
+         * <p>
+         * The playback of the prepared content should start in the later calls of
+         * {@link MediaSession2#play()}.
+         * <p>
+         * Override {@link #onPlayFromUri} to handle requests for starting playback without
+         * preparation.
+         */
+        public void onPlayFromUri(@NonNull ControllerInfo controller,
+                @NonNull String uri, @Nullable Bundle extras) { }
+
+        /**
+         * Override to handle requests to play a specific mediaId.
+         */
+        public void onPrepareFromMediaId(@NonNull ControllerInfo controller,
+                @NonNull String mediaId, @Nullable Bundle extras) { }
+
+        /**
+         * Override to handle requests to begin playback from a search query. An
+         * empty query indicates that the app may play any music. The
+         * implementation should attempt to make a smart choice about what to
+         * play.
+         */
+        public void onPrepareFromSearch(@NonNull ControllerInfo controller,
+                @NonNull String query, @Nullable Bundle extras) { }
+
+        /**
+         * Override to handle requests to play a specific media item represented by a URI.
+         */
+        public void prepareFromUri(@NonNull ControllerInfo controller,
+                @NonNull Uri uri, @Nullable Bundle extras) { }
+
+        /**
+         * Called when a controller wants to add a {@link MediaItem2} at the specified position
+         * in the play queue.
+         * <p>
+         * The item from the media controller wouldn't have valid data source descriptor because
+         * it would have been anonymized when it's sent to the remote process.
+         *
+         * @param item The media item to be inserted.
+         * @param index The index at which the item is to be inserted.
+         */
+        public void onAddPlaylistItem(@NonNull ControllerInfo controller,
+                @NonNull MediaItem2 item, int index) { }
+
+        /**
+         * Called when a controller wants to remove the {@link MediaItem2}
+         *
+         * @param item
+         */
+        // Can we do this automatically?
+        public void onRemovePlaylistItem(@NonNull MediaItem2 item) { }
     };
 
     /**
@@ -325,6 +462,9 @@
         final MediaPlayerBase mPlayer;
         String mId;
         C mCallback;
+        VolumeProvider mVolumeProvider;
+        int mRatingType;
+        PendingIntent mSessionActivity;
 
         /**
          * Constructor.
@@ -349,6 +489,50 @@
         }
 
         /**
+         * Set volume provider to configure this session to use remote volume handling.
+         * This must be called to receive volume button events, otherwise the system
+         * will adjust the appropriate stream volume for this session's player.
+         * <p>
+         * Set {@code null} to reset.
+         *
+         * @param volumeProvider The provider that will handle volume changes. Can be {@code null}
+         */
+        public T setVolumeProvider(@Nullable VolumeProvider volumeProvider) {
+            mVolumeProvider = volumeProvider;
+            return (T) this;
+        }
+
+        /**
+         * Set the style of rating used by this session. Apps trying to set the
+         * rating should use this style. Must be one of the following:
+         * <ul>
+         * <li>{@link Rating2#RATING_NONE}</li>
+         * <li>{@link Rating2#RATING_3_STARS}</li>
+         * <li>{@link Rating2#RATING_4_STARS}</li>
+         * <li>{@link Rating2#RATING_5_STARS}</li>
+         * <li>{@link Rating2#RATING_HEART}</li>
+         * <li>{@link Rating2#RATING_PERCENTAGE}</li>
+         * <li>{@link Rating2#RATING_THUMB_UP_DOWN}</li>
+         * </ul>
+         */
+        public T setRatingType(@Rating2.Style int type) {
+            mRatingType = type;
+            return (T) this;
+        }
+
+        /**
+         * Set an intent for launching UI for this Session. This can be used as a
+         * quick link to an ongoing media screen. The intent should be for an
+         * activity that may be started using {@link Activity#startActivity(Intent)}.
+         *
+         * @param pi The intent to launch to show UI for this session.
+         */
+        public T setSessionActivity(@Nullable PendingIntent pi) {
+            mSessionActivity = pi;
+            return (T) this;
+        }
+
+        /**
          * Set ID of the session. If it's not set, an empty string with used to create a session.
          * <p>
          * Use this if and only if your app supports multiple playback at the same time and also
@@ -406,7 +590,8 @@
             if (mCallback == null) {
                 mCallback = new SessionCallback();
             }
-            return new MediaSession2(mContext, mPlayer, mId, mCallback);
+            return new MediaSession2(mContext, mPlayer, mId, mCallback,
+                    mVolumeProvider, mRatingType, mSessionActivity);
         }
     }
 
@@ -654,6 +839,39 @@
     }
 
     /**
+     * Parameter for the playlist.
+     */
+    // TODO(jaewan): add fromBundle()/toBundle()
+    public class PlaylistParam {
+        private MediaMetadata2 mPlaylistMetadata;
+
+        // It's mixture of shuffle mode and repeat mode. Needs to be separated for avrcp 1.6 support
+        // PlaybackState#REPEAT_MODE_ALL
+        // PlaybackState#REPEAT_MODE_GROUP <- for avrcp 1.6
+        // PlaybackState#REPEAT_MODE_INVALID
+        // PlaybackState#REPEAT_MODE_NONE
+        // PlaybackState#REPEAT_MODE_ONE
+        // PlaybackState#SHUFFLE_MODE_ALL
+        // PlaybackState#SHUFFLE_MODE_GROUP <- for avrcp 1.6
+        // PlaybackState#SHUFFLE_MODE_INVALID
+        // PlaybackState#SHUFFLE_MODE_NONE
+        private int mLoopingMode;
+
+        public PlaylistParam(int loopingMode, @Nullable MediaMetadata2 playlistMetadata) {
+            mLoopingMode = loopingMode;
+            mPlaylistMetadata = playlistMetadata;
+        }
+
+        public int getLoopingMode() {
+            return mLoopingMode;
+        }
+
+        public MediaMetadata2 getPlaylistMetadata() {
+            return mPlaylistMetadata;
+        }
+    }
+
+    /**
      * Constructor is hidden and apps can only instantiate indirectly through {@link Builder}.
      * <p>
      * This intended behavior and here's the reasons.
@@ -668,10 +886,19 @@
      * @hide
      */
     MediaSession2(Context context, MediaPlayerBase player, String id,
-            SessionCallback callback) {
+            SessionCallback callback, VolumeProvider volumeProvider, int ratingType,
+            PendingIntent sessionActivity) {
         super();
-        mProvider = ApiLoader.getProvider(context)
-                .createMediaSession2(this, context, player, id, callback);
+        mProvider = createProvider(context, player, id, callback,
+                volumeProvider, ratingType, sessionActivity);
+    }
+
+    MediaSession2Provider createProvider(Context context, MediaPlayerBase player, String id,
+            SessionCallback callback, VolumeProvider volumeProvider, int ratingType,
+            PendingIntent sessionActivity) {
+        return ApiLoader.getProvider(context)
+                .createMediaSession2(this, context, player, id, callback,
+                        volumeProvider, ratingType, sessionActivity);
     }
 
     /**
@@ -690,17 +917,30 @@
      * If the new player is successfully set, {@link PlaybackListener}
      * will be called to tell the current playback state of the new player.
      * <p>
-     * Calling this method with {@code null} will disconnect binding connection between the
-     * controllers and also release this object.
+     * You can also specify a volume provider. If so, playback in the player is considered as
+     * remote playback.
      *
      * @param player a {@link MediaPlayerBase} that handles actual media playback in your app.
-     *      It shouldn't be {@link MediaSession2} nor {@link MediaController2}.
      * @throws IllegalArgumentException if the player is {@code null}.
      */
-    public void setPlayer(@NonNull MediaPlayerBase player) throws IllegalArgumentException {
+    public void setPlayer(@NonNull MediaPlayerBase player) {
         mProvider.setPlayer_impl(player);
     }
 
+    /**
+     * Set the underlying {@link MediaPlayerBase} with the volume provider for remote playback.
+     *
+     * @param player a {@link MediaPlayerBase} that handles actual media playback in your app.
+     * @param volumeProvider a volume provider
+     * @see #setPlayer(MediaPlayerBase)
+     * @see Builder#setVolumeProvider(VolumeProvider)
+     * @throws IllegalArgumentException if a parameter is {@code null}.
+     */
+    public void setPlayer(@NonNull MediaPlayerBase player, @NonNull VolumeProvider volumeProvider)
+            throws IllegalArgumentException {
+        mProvider.setPlayer_impl(player, volumeProvider);
+    }
+
     @Override
     public void close() {
         mProvider.close_impl();
@@ -726,6 +966,34 @@
     }
 
     /**
+     * Sets the {@link AudioAttributes} to be used during the playback of the video.
+     *
+     * @param attributes non-null <code>AudioAttributes</code>.
+     */
+    public void setAudioAttributes(@NonNull AudioAttributes attributes) {
+        mProvider.setAudioAttributes_impl(attributes);
+    }
+
+    /**
+     * Sets which type of audio focus will be requested during the playback, or configures playback
+     * to not request audio focus. Valid values for focus requests are
+     * {@link AudioManager#AUDIOFOCUS_GAIN}, {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT},
+     * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}, and
+     * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}. Or use
+     * {@link AudioManager#AUDIOFOCUS_NONE} to express that audio focus should not be
+     * requested when playback starts. You can for instance use this when playing a silent animation
+     * through this class, and you don't want to affect other audio applications playing in the
+     * background.
+     *
+     * @param focusGain the type of audio focus gain that will be requested, or
+     *                  {@link AudioManager#AUDIOFOCUS_NONE} to disable the use audio focus during
+     *                  playback.
+     */
+    public void setAudioFocusRequest(int focusGain) {
+        mProvider.setAudioFocusRequest_impl(focusGain);
+    }
+
+    /**
      * Sets ordered list of {@link CommandButton} for controllers to build UI with it.
      * <p>
      * It's up to controller's decision how to represent the layout in its own UI.
@@ -750,6 +1018,48 @@
     }
 
     /**
+     * Set the new allowed command group for the controller
+     *
+     * @param controller controller to change allowed commands
+     * @param commands new allowed commands
+     */
+    public void setAllowedCommands(@NonNull ControllerInfo controller,
+            @NonNull CommandGroup commands) {
+        mProvider.setAllowedCommands_impl(controller, commands);
+    }
+
+    /**
+     * Notify changes in metadata of previously set playlist. Controller will get the whole set of
+     * playlist again.
+     */
+    public void notifyMetadataChanged() {
+        mProvider.notifyMetadataChanged_impl();
+    }
+
+    /**
+     * Send custom command to all connected controllers.
+     *
+     * @param command a command
+     * @param args optional argument
+     */
+    public void sendCustomCommand(@NonNull Command command, @Nullable Bundle args) {
+        mProvider.sendCustomCommand_impl(command, args);
+    }
+
+    /**
+     * Send custom command to a specific controller.
+     *
+     * @param command a command
+     * @param args optional argument
+     * @param receiver result receiver for the session
+     */
+    public void sendCustomCommand(@NonNull ControllerInfo controller, @NonNull Command command,
+            @Nullable Bundle args, @Nullable ResultReceiver receiver) {
+        // Equivalent to the MediaController.sendCustomCommand(Action action, ResultReceiver r);
+        mProvider.sendCustomCommand_impl(controller, command, args, receiver);
+    }
+
+    /**
      * Play playback
      */
     public void play() {
@@ -784,33 +1094,66 @@
         mProvider.skipToNext_impl();
     }
 
-    public @NonNull PlaybackState getPlaybackState() {
-        return mProvider.getPlaybackState_impl();
+    /**
+     * Request that the player prepare its playback. In other words, other sessions can continue
+     * to play during the preparation of this session. This method can be used to speed up the
+     * start of the playback. Once the preparation is done, the session will change its playback
+     * state to {@link PlaybackState#STATE_PAUSED}. Afterwards, {@link #play} can be called to
+     * start playback.
+     */
+    public void prepare() {
+        mProvider.prepare_impl();
     }
 
     /**
-     * Add a {@link PlaybackListener} to listen changes in the
-     * underlying {@link MediaPlayerBase} which is previously set by
-     * {@link #setPlayer(MediaPlayerBase)}.
-     * <p>
-     * Added listeners will be also called when the underlying player is changed.
-     *
-     * @param listener the listener that will be run
-     * @param handler the Handler that will receive the listener
-     * @throws IllegalArgumentException when either the listener or handler is {@code null}.
+     * Start fast forwarding. If playback is already fast forwarding this may increase the rate.
      */
-    // TODO(jaewan): Can handler be null? Follow API guideline after it's finalized.
-    public void addPlaybackListener(@NonNull PlaybackListener listener, @NonNull Handler handler) {
-        mProvider.addPlaybackListener_impl(listener, handler);
+    public void fastForward() {
+        mProvider.fastForward_impl();
     }
 
     /**
-     * Remove previously added {@link PlaybackListener}.
-     *
-     * @param listener the listener to be removed
-     * @throws IllegalArgumentException if the listener is {@code null}.
+     * Start rewinding. If playback is already rewinding this may increase the rate.
      */
-    public void removePlaybackListener(PlaybackListener listener) {
-        mProvider.removePlaybackListener_impl(listener);
+    public void rewind() {
+        mProvider.rewind_impl();
+    }
+
+    /**
+     * Move to a new location in the media stream.
+     *
+     * @param pos Position to move to, in milliseconds.
+     */
+    public void seekTo(long pos) {
+        mProvider.seekTo_impl(pos);
+    }
+
+    /**
+     * Sets the index of current DataSourceDesc in the play list to be played.
+     *
+     * @param index the index of DataSourceDesc in the play list you want to play
+     * @throws IllegalArgumentException if the play list is null
+     * @throws NullPointerException if index is outside play list range
+     */
+    public void setCurrentPlaylistItem(int index) {
+        mProvider.setCurrentPlaylistItem_impl(index);
+    }
+
+    /**
+     * @hide
+     */
+    public void skipForward() {
+        // To match with KEYCODE_MEDIA_SKIP_FORWARD
+    }
+
+    /**
+     * @hide
+     */
+    public void skipBackward() {
+        // To match with KEYCODE_MEDIA_SKIP_BACKWARD
+    }
+
+    public void setPlaylist(@NonNull List<MediaItem2> playlist, @NonNull PlaylistParam param) {
+        mProvider.setPlaylist_impl(playlist, param);
     }
 }
diff --git a/media/java/android/media/PlaybackState2.java b/media/java/android/media/PlaybackState2.java
new file mode 100644
index 0000000..3740aea
--- /dev/null
+++ b/media/java/android/media/PlaybackState2.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Playback state for a {@link MediaPlayerBase}, to be shared between {@link MediaSession2} and
+ * {@link MediaController2}. This includes a playback state {@link #STATE_PLAYING},
+ * the current playback position and extra.
+ * @hide
+ */
+// TODO(jaewan): Move to updatable
+// TODO(jaewan): Add fromBundle() toBundle()
+public final class PlaybackState2 {
+    private static final String TAG = "PlaybackState2";
+
+    // TODO(jaewan): Replace states from MediaPlayer2
+    /**
+     * @hide
+     */
+    @IntDef({STATE_NONE, STATE_STOPPED, STATE_PREPARED, STATE_PAUSED, STATE_PLAYING,
+            STATE_FINISH, STATE_BUFFERING, STATE_ERROR})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface State {}
+
+    /**
+     * This is the default playback state and indicates that no media has been
+     * added yet, or the performer has been reset and has no content to play.
+     */
+    public final static int STATE_NONE = 0;
+
+    /**
+     * State indicating this item is currently stopped.
+     */
+    public final static int STATE_STOPPED = 1;
+
+    /**
+     * State indicating this item is currently prepared
+     */
+    public final static int STATE_PREPARED = 2;
+
+    /**
+     * State indicating this item is currently paused.
+     */
+    public final static int STATE_PAUSED = 3;
+
+    /**
+     * State indicating this item is currently playing.
+     */
+    public final static int STATE_PLAYING = 4;
+
+    /**
+     * State indicating the playback reaches the end of the item.
+     */
+    public final static int STATE_FINISH = 5;
+
+    /**
+     * State indicating this item is currently buffering and will begin playing
+     * when enough data has buffered.
+     */
+    public final static int STATE_BUFFERING = 6;
+
+    /**
+     * State indicating this item is currently in an error state. The error
+     * message should also be set when entering this state.
+     */
+    public final static int STATE_ERROR = 7;
+
+    /**
+     * Use this value for the position to indicate the position is not known.
+     */
+    public final static long PLAYBACK_POSITION_UNKNOWN = -1;
+
+    private final int mState;
+    private final long mPosition;
+    private final long mBufferedPosition;
+    private final float mSpeed;
+    private final CharSequence mErrorMessage;
+    private final long mUpdateTime;
+    private final long mActiveItemId;
+
+    private PlaybackState2(int state, long position, long updateTime, float speed,
+            long bufferedPosition, long activeItemId, CharSequence error) {
+        mState = state;
+        mPosition = position;
+        mSpeed = speed;
+        mUpdateTime = updateTime;
+        mBufferedPosition = bufferedPosition;
+        mActiveItemId = activeItemId;
+        mErrorMessage = error;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder bob = new StringBuilder("PlaybackState {");
+        bob.append("state=").append(mState);
+        bob.append(", position=").append(mPosition);
+        bob.append(", buffered position=").append(mBufferedPosition);
+        bob.append(", speed=").append(mSpeed);
+        bob.append(", updated=").append(mUpdateTime);
+        bob.append(", active item id=").append(mActiveItemId);
+        bob.append(", error=").append(mErrorMessage);
+        bob.append("}");
+        return bob.toString();
+    }
+
+    /**
+     * Get the current state of playback. One of the following:
+     * <ul>
+     * <li> {@link PlaybackState2#STATE_NONE}</li>
+     * <li> {@link PlaybackState2#STATE_STOPPED}</li>
+     * <li> {@link PlaybackState2#STATE_PLAYING}</li>
+     * <li> {@link PlaybackState2#STATE_PAUSED}</li>
+     * <li> {@link PlaybackState2#STATE_FAST_FORWARDING}</li>
+     * <li> {@link PlaybackState2#STATE_REWINDING}</li>
+     * <li> {@link PlaybackState2#STATE_BUFFERING}</li>
+     * <li> {@link PlaybackState2#STATE_ERROR}</li>
+     * <li> {@link PlaybackState2#STATE_CONNECTING}</li>
+     * <li> {@link PlaybackState2#STATE_SKIPPING_TO_PREVIOUS}</li>
+     * <li> {@link PlaybackState2#STATE_SKIPPING_TO_NEXT}</li>
+     * <li> {@link PlaybackState2#STATE_SKIPPING_TO_QUEUE_ITEM}</li>
+     * </ul>
+     */
+    @State
+    public int getState() {
+        return mState;
+    }
+
+    /**
+     * Get the current playback position in ms.
+     */
+    public long getPosition() {
+        return mPosition;
+    }
+
+    /**
+     * Get the current buffered position in ms. This is the farthest playback
+     * point that can be reached from the current position using only buffered
+     * content.
+     */
+    public long getBufferedPosition() {
+        return mBufferedPosition;
+    }
+
+    /**
+     * Get the current playback speed as a multiple of normal playback. This
+     * should be negative when rewinding. A value of 1 means normal playback and
+     * 0 means paused.
+     *
+     * @return The current speed of playback.
+     */
+    public float getPlaybackSpeed() {
+        return mSpeed;
+    }
+
+    /**
+     * Get a user readable error message. This should be set when the state is
+     * {@link PlaybackState2#STATE_ERROR}.
+     */
+    public CharSequence getErrorMessage() {
+        return mErrorMessage;
+    }
+
+    /**
+     * Get the elapsed real time at which position was last updated. If the
+     * position has never been set this will return 0;
+     *
+     * @return The last time the position was updated.
+     */
+    public long getLastPositionUpdateTime() {
+        return mUpdateTime;
+    }
+
+    /**
+     * Get the id of the currently active item in the playlist.
+     *
+     * @return The id of the currently active item in the queue
+     */
+    public long getCurrentPlaylistItemIndex() {
+        return mActiveItemId;
+    }
+}
\ No newline at end of file
diff --git a/media/java/android/media/Rating2.java b/media/java/android/media/Rating2.java
new file mode 100644
index 0000000..67e5e72
--- /dev/null
+++ b/media/java/android/media/Rating2.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.annotation.IntDef;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A class to encapsulate rating information used as content metadata.
+ * A rating is defined by its rating style (see {@link #RATING_HEART},
+ * {@link #RATING_THUMB_UP_DOWN}, {@link #RATING_3_STARS}, {@link #RATING_4_STARS},
+ * {@link #RATING_5_STARS} or {@link #RATING_PERCENTAGE}) and the actual rating value (which may
+ * be defined as "unrated"), both of which are defined when the rating instance is constructed
+ * through one of the factory methods.
+ * @hide
+ */
+// TODO(jaewan): Move this to updatable
+public final class Rating2 {
+    private static final String TAG = "Rating2";
+
+    private static final String KEY_STYLE = "android.media.rating2.style";
+    private static final String KEY_VALUE = "android.media.rating2.value";
+
+    /**
+     * @hide
+     */
+    @IntDef({RATING_NONE, RATING_HEART, RATING_THUMB_UP_DOWN, RATING_3_STARS, RATING_4_STARS,
+            RATING_5_STARS, RATING_PERCENTAGE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Style {}
+
+    /**
+     * @hide
+     */
+    @IntDef({RATING_3_STARS, RATING_4_STARS, RATING_5_STARS})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface StarStyle {}
+
+    /**
+     * Indicates a rating style is not supported. A Rating2 will never have this
+     * type, but can be used by other classes to indicate they do not support
+     * Rating2.
+     */
+    public final static int RATING_NONE = 0;
+
+    /**
+     * A rating style with a single degree of rating, "heart" vs "no heart". Can be used to
+     * indicate the content referred to is a favorite (or not).
+     */
+    public final static int RATING_HEART = 1;
+
+    /**
+     * A rating style for "thumb up" vs "thumb down".
+     */
+    public final static int RATING_THUMB_UP_DOWN = 2;
+
+    /**
+     * A rating style with 0 to 3 stars.
+     */
+    public final static int RATING_3_STARS = 3;
+
+    /**
+     * A rating style with 0 to 4 stars.
+     */
+    public final static int RATING_4_STARS = 4;
+
+    /**
+     * A rating style with 0 to 5 stars.
+     */
+    public final static int RATING_5_STARS = 5;
+
+    /**
+     * A rating style expressed as a percentage.
+     */
+    public final static int RATING_PERCENTAGE = 6;
+
+    private final static float RATING_NOT_RATED = -1.0f;
+
+    private final int mRatingStyle;
+
+    private final float mRatingValue;
+
+    private Rating2(@Style int ratingStyle, float rating) {
+        mRatingStyle = ratingStyle;
+        mRatingValue = rating;
+    }
+
+    @Override
+    public String toString() {
+        return "Rating2:style=" + mRatingStyle + " rating="
+                + (mRatingValue < 0.0f ? "unrated" : String.valueOf(mRatingValue));
+    }
+
+    /**
+     * Create an instance from bundle object, previoulsy created by {@link #toBundle()}
+     *
+     * @param bundle bundle
+     * @return new Rating2 instance
+     */
+    public static Rating2 fromBundle(Bundle bundle) {
+        return new Rating2(bundle.getInt(KEY_STYLE), bundle.getFloat(KEY_VALUE));
+    }
+
+    /**
+     * Return bundle for this object to share across the process.
+     * @return bundle of this object
+     */
+    public Bundle toBundle() {
+        Bundle bundle = new Bundle();
+        bundle.putInt(KEY_STYLE, mRatingStyle);
+        bundle.putFloat(KEY_VALUE, mRatingValue);
+        return bundle;
+    }
+
+    /**
+     * Return a Rating2 instance with no rating.
+     * Create and return a new Rating2 instance with no rating known for the given
+     * rating style.
+     * @param ratingStyle one of {@link #RATING_HEART}, {@link #RATING_THUMB_UP_DOWN},
+     *    {@link #RATING_3_STARS}, {@link #RATING_4_STARS}, {@link #RATING_5_STARS},
+     *    or {@link #RATING_PERCENTAGE}.
+     * @return null if an invalid rating style is passed, a new Rating2 instance otherwise.
+     */
+    public static Rating2 newUnratedRating(@Style int ratingStyle) {
+        switch(ratingStyle) {
+            case RATING_HEART:
+            case RATING_THUMB_UP_DOWN:
+            case RATING_3_STARS:
+            case RATING_4_STARS:
+            case RATING_5_STARS:
+            case RATING_PERCENTAGE:
+                return new Rating2(ratingStyle, RATING_NOT_RATED);
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * Return a Rating2 instance with a heart-based rating.
+     * Create and return a new Rating2 instance with a rating style of {@link #RATING_HEART},
+     * and a heart-based rating.
+     * @param hasHeart true for a "heart selected" rating, false for "heart unselected".
+     * @return a new Rating2 instance.
+     */
+    public static Rating2 newHeartRating(boolean hasHeart) {
+        return new Rating2(RATING_HEART, hasHeart ? 1.0f : 0.0f);
+    }
+
+    /**
+     * Return a Rating2 instance with a thumb-based rating.
+     * Create and return a new Rating2 instance with a {@link #RATING_THUMB_UP_DOWN}
+     * rating style, and a "thumb up" or "thumb down" rating.
+     * @param thumbIsUp true for a "thumb up" rating, false for "thumb down".
+     * @return a new Rating2 instance.
+     */
+    public static Rating2 newThumbRating(boolean thumbIsUp) {
+        return new Rating2(RATING_THUMB_UP_DOWN, thumbIsUp ? 1.0f : 0.0f);
+    }
+
+    /**
+     * Return a Rating2 instance with a star-based rating.
+     * Create and return a new Rating2 instance with one of the star-base rating styles
+     * and the given integer or fractional number of stars. Non integer values can for instance
+     * be used to represent an average rating value, which might not be an integer number of stars.
+     * @param starRatingStyle one of {@link #RATING_3_STARS}, {@link #RATING_4_STARS},
+     *     {@link #RATING_5_STARS}.
+     * @param starRating a number ranging from 0.0f to 3.0f, 4.0f or 5.0f according to
+     *     the rating style.
+     * @return null if the rating style is invalid, or the rating is out of range,
+     *     a new Rating2 instance otherwise.
+     */
+    public static Rating2 newStarRating(@StarStyle int starRatingStyle, float starRating) {
+        float maxRating = -1.0f;
+        switch(starRatingStyle) {
+            case RATING_3_STARS:
+                maxRating = 3.0f;
+                break;
+            case RATING_4_STARS:
+                maxRating = 4.0f;
+                break;
+            case RATING_5_STARS:
+                maxRating = 5.0f;
+                break;
+            default:
+                Log.e(TAG, "Invalid rating style (" + starRatingStyle + ") for a star rating");
+                        return null;
+        }
+        if ((starRating < 0.0f) || (starRating > maxRating)) {
+            Log.e(TAG, "Trying to set out of range star-based rating");
+            return null;
+        }
+        return new Rating2(starRatingStyle, starRating);
+    }
+
+    /**
+     * Return a Rating2 instance with a percentage-based rating.
+     * Create and return a new Rating2 instance with a {@link #RATING_PERCENTAGE}
+     * rating style, and a rating of the given percentage.
+     * @param percent the value of the rating
+     * @return null if the rating is out of range, a new Rating2 instance otherwise.
+     */
+    public static Rating2 newPercentageRating(float percent) {
+        if ((percent < 0.0f) || (percent > 100.0f)) {
+            Log.e(TAG, "Invalid percentage-based rating value");
+            return null;
+        } else {
+            return new Rating2(RATING_PERCENTAGE, percent);
+        }
+    }
+
+    /**
+     * Return whether there is a rating value available.
+     * @return true if the instance was not created with {@link #newUnratedRating(int)}.
+     */
+    public boolean isRated() {
+        return mRatingValue >= 0.0f;
+    }
+
+    /**
+     * Return the rating style.
+     * @return one of {@link #RATING_HEART}, {@link #RATING_THUMB_UP_DOWN},
+     *    {@link #RATING_3_STARS}, {@link #RATING_4_STARS}, {@link #RATING_5_STARS},
+     *    or {@link #RATING_PERCENTAGE}.
+     */
+    @Style
+    public int getRatingStyle() {
+        return mRatingStyle;
+    }
+
+    /**
+     * Return whether the rating is "heart selected".
+     * @return true if the rating is "heart selected", false if the rating is "heart unselected",
+     *    if the rating style is not {@link #RATING_HEART} or if it is unrated.
+     */
+    public boolean hasHeart() {
+        if (mRatingStyle != RATING_HEART) {
+            return false;
+        } else {
+            return (mRatingValue == 1.0f);
+        }
+    }
+
+    /**
+     * Return whether the rating is "thumb up".
+     * @return true if the rating is "thumb up", false if the rating is "thumb down",
+     *    if the rating style is not {@link #RATING_THUMB_UP_DOWN} or if it is unrated.
+     */
+    public boolean isThumbUp() {
+        if (mRatingStyle != RATING_THUMB_UP_DOWN) {
+            return false;
+        } else {
+            return (mRatingValue == 1.0f);
+        }
+    }
+
+    /**
+     * Return the star-based rating value.
+     * @return a rating value greater or equal to 0.0f, or a negative value if the rating style is
+     *    not star-based, or if it is unrated.
+     */
+    public float getStarRating() {
+        switch (mRatingStyle) {
+            case RATING_3_STARS:
+            case RATING_4_STARS:
+            case RATING_5_STARS:
+                if (isRated()) {
+                    return mRatingValue;
+                }
+            default:
+                return -1.0f;
+        }
+    }
+
+    /**
+     * Return the percentage-based rating value.
+     * @return a rating value greater or equal to 0.0f, or a negative value if the rating style is
+     *    not percentage-based, or if it is unrated.
+     */
+    public float getPercentRating() {
+        if ((mRatingStyle != RATING_PERCENTAGE) || !isRated()) {
+            return -1.0f;
+        } else {
+            return mRatingValue;
+        }
+    }
+}
diff --git a/media/java/android/media/update/MediaBrowser2Provider.java b/media/java/android/media/update/MediaBrowser2Provider.java
index 355dbc9..e48711d 100644
--- a/media/java/android/media/update/MediaBrowser2Provider.java
+++ b/media/java/android/media/update/MediaBrowser2Provider.java
@@ -23,4 +23,11 @@
  */
 public interface MediaBrowser2Provider extends MediaController2Provider {
     void getBrowserRoot_impl(Bundle rootHints);
+
+    void subscribe_impl(String parentId, Bundle options);
+    void unsubscribe_impl(String parentId, Bundle options);
+
+    void getItem_impl(String mediaId);
+    void getChildren_impl(String parentId, int page, int pageSize, Bundle options);
+    void search_impl(String query, int page, int pageSize, Bundle extras);
 }
diff --git a/media/java/android/media/update/MediaControlView2Provider.java b/media/java/android/media/update/MediaControlView2Provider.java
index e0e4cef..6b38c92 100644
--- a/media/java/android/media/update/MediaControlView2Provider.java
+++ b/media/java/android/media/update/MediaControlView2Provider.java
@@ -40,12 +40,6 @@
     void show_impl(int timeout);
     boolean isShowing_impl();
     void hide_impl();
-    boolean isPlaying_impl();
-    int getCurrentPosition_impl();
-    int getBufferPercentage_impl();
-    boolean canPause_impl();
-    boolean canSeekBackward_impl();
-    boolean canSeekForward_impl();
     void showSubtitle_impl();
     void hideSubtitle_impl();
     void setPrevNextListeners_impl(View.OnClickListener next, View.OnClickListener prev);
diff --git a/media/java/android/media/update/MediaController2Provider.java b/media/java/android/media/update/MediaController2Provider.java
index 9ad5a68..8f5a643 100644
--- a/media/java/android/media/update/MediaController2Provider.java
+++ b/media/java/android/media/update/MediaController2Provider.java
@@ -16,10 +16,19 @@
 
 package android.media.update;
 
-import android.media.MediaPlayerBase;
+import android.app.PendingIntent;
+import android.media.MediaController2.PlaybackInfo;
+import android.media.MediaItem2;
+import android.media.MediaSession2.Command;
+import android.media.MediaSession2.PlaylistParam;
+import android.media.PlaybackState2;
+import android.media.Rating2;
 import android.media.SessionToken;
-import android.media.session.PlaybackState;
-import android.os.Handler;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+
+import java.util.List;
 
 /**
  * @hide
@@ -29,7 +38,27 @@
     SessionToken getSessionToken_impl();
     boolean isConnected_impl();
 
-    PlaybackState getPlaybackState_impl();
-    void addPlaybackListener_impl(MediaPlayerBase.PlaybackListener listener, Handler handler);
-    void removePlaybackListener_impl(MediaPlayerBase.PlaybackListener listener);
+    PendingIntent getSessionActivity_impl();
+    int getRatingType_impl();
+
+    void setVolumeTo_impl(int value, int flags);
+    void adjustVolume_impl(int direction, int flags);
+    PlaybackInfo getPlaybackInfo_impl();
+
+    void prepareFromUri_impl(Uri uri, Bundle extras);
+    void prepareFromSearch_impl(String query, Bundle extras);
+    void prepareMediaId_impl(String mediaId, Bundle extras);
+    void playFromSearch_impl(String query, Bundle extras);
+    void playFromUri_impl(String uri, Bundle extras);
+    void playFromMediaId_impl(String mediaId, Bundle extras);
+
+    void setRating_impl(Rating2 rating);
+    void sendCustomCommand_impl(Command command, Bundle args, ResultReceiver cb);
+    List<MediaItem2> getPlaylist_impl();
+
+    void removePlaylistItem_impl(MediaItem2 index);
+    void addPlaylistItem_impl(int index, MediaItem2 item);
+
+    PlaylistParam getPlaylistParam_impl();
+    PlaybackState2 getPlaybackState_impl();
 }
diff --git a/media/java/android/media/update/MediaLibraryService2Provider.java b/media/java/android/media/update/MediaLibraryService2Provider.java
index 7e3444f..dac5784 100644
--- a/media/java/android/media/update/MediaLibraryService2Provider.java
+++ b/media/java/android/media/update/MediaLibraryService2Provider.java
@@ -16,9 +16,15 @@
 
 package android.media.update;
 
-/**
+import android.media.MediaSession2.ControllerInfo;
+import android.os.Bundle; /**
  * @hide
  */
 public interface MediaLibraryService2Provider extends MediaSessionService2Provider {
     // Nothing new for now
+
+    interface MediaLibrarySessionProvider extends MediaSession2Provider {
+        void notifyChildrenChanged_impl(ControllerInfo controller, String parentId, Bundle options);
+        void notifyChildrenChanged_impl(String parentId, Bundle options);
+    }
 }
diff --git a/media/java/android/media/update/MediaSession2Provider.java b/media/java/android/media/update/MediaSession2Provider.java
index 402397e..511686d 100644
--- a/media/java/android/media/update/MediaSession2Provider.java
+++ b/media/java/android/media/update/MediaSession2Provider.java
@@ -16,12 +16,21 @@
 
 package android.media.update;
 
+import android.media.AudioAttributes;
+import android.media.MediaItem2;
 import android.media.MediaPlayerBase;
+import android.media.MediaSession2;
+import android.media.MediaSession2.Command;
 import android.media.MediaSession2.CommandButton;
+import android.media.MediaSession2.CommandGroup;
 import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.PlaylistParam;
 import android.media.SessionToken;
+import android.media.VolumeProvider;
 import android.media.session.PlaybackState;
+import android.os.Bundle;
 import android.os.Handler;
+import android.os.ResultReceiver;
 
 import java.util.List;
 
@@ -30,11 +39,21 @@
  */
 public interface MediaSession2Provider extends TransportControlProvider {
     void close_impl();
-    void setPlayer_impl(MediaPlayerBase player) throws IllegalArgumentException;
+    void setPlayer_impl(MediaPlayerBase player);
+    void setPlayer_impl(MediaPlayerBase player, VolumeProvider volumeProvider);
     MediaPlayerBase getPlayer_impl();
     SessionToken getToken_impl();
     List<ControllerInfo> getConnectedControllers_impl();
     void setCustomLayout_impl(ControllerInfo controller, List<CommandButton> layout);
+    void setAudioAttributes_impl(AudioAttributes attributes);
+    void setAudioFocusRequest_impl(int focusGain);
+
+    void setAllowedCommands_impl(ControllerInfo controller, CommandGroup commands);
+    void notifyMetadataChanged_impl();
+    void sendCustomCommand_impl(ControllerInfo controller, Command command, Bundle args,
+            ResultReceiver receiver);
+    void sendCustomCommand_impl(Command command, Bundle args);
+    void setPlaylist_impl(List<MediaItem2> playlist, MediaSession2.PlaylistParam param);
 
     /**
      * @hide
diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java
index fef2cbb..64968d6 100644
--- a/media/java/android/media/update/StaticProvider.java
+++ b/media/java/android/media/update/StaticProvider.java
@@ -17,6 +17,7 @@
 package android.media.update;
 
 import android.annotation.Nullable;
+import android.app.PendingIntent;
 import android.content.Context;
 import android.media.IMediaSession2Callback;
 import android.media.MediaBrowser2;
@@ -24,10 +25,16 @@
 import android.media.MediaController2;
 import android.media.MediaController2.ControllerCallback;
 import android.media.MediaLibraryService2;
+import android.media.MediaLibraryService2.MediaLibrarySession;
+import android.media.MediaLibraryService2.MediaLibrarySessionCallback;
 import android.media.MediaPlayerBase;
 import android.media.MediaSession2;
+import android.media.MediaSession2.SessionCallback;
 import android.media.MediaSessionService2;
 import android.media.SessionToken;
+import android.media.VolumeProvider;
+import android.media.update.MediaLibraryService2Provider.MediaLibrarySessionProvider;
+import android.media.update.MediaSession2Provider.ControllerInfoProvider;
 import android.util.AttributeSet;
 import android.widget.MediaControlView2;
 import android.widget.VideoView2;
@@ -51,8 +58,10 @@
             @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes);
 
     MediaSession2Provider createMediaSession2(MediaSession2 mediaSession2, Context context,
-            MediaPlayerBase player, String id, MediaSession2.SessionCallback callback);
-    MediaSession2Provider.ControllerInfoProvider createMediaSession2ControllerInfoProvider(
+            MediaPlayerBase player, String id, SessionCallback callback,
+            VolumeProvider volumeProvider, int ratingType,
+            PendingIntent sessionActivity);
+    ControllerInfoProvider createMediaSession2ControllerInfoProvider(
             MediaSession2.ControllerInfo instance, Context context, int uid, int pid,
             String packageName, IMediaSession2Callback callback);
     MediaController2Provider createMediaController2(
@@ -65,4 +74,8 @@
             MediaSessionService2 instance);
     MediaSessionService2Provider createMediaLibraryService2(
             MediaLibraryService2 instance);
+    MediaLibrarySessionProvider createMediaLibraryService2MediaLibrarySession(
+            MediaLibrarySession instance, Context context, MediaPlayerBase player, String id,
+            MediaLibrarySessionCallback callback, VolumeProvider volumeProvider, int ratingType,
+            PendingIntent sessionActivity);
 }
diff --git a/media/java/android/media/update/TransportControlProvider.java b/media/java/android/media/update/TransportControlProvider.java
index 1b6b201..5217a9d 100644
--- a/media/java/android/media/update/TransportControlProvider.java
+++ b/media/java/android/media/update/TransportControlProvider.java
@@ -31,7 +31,9 @@
     void skipToPrevious_impl();
     void skipToNext_impl();
 
-    PlaybackState getPlaybackState_impl();
-    void addPlaybackListener_impl(MediaPlayerBase.PlaybackListener listener, Handler handler);
-    void removePlaybackListener_impl(MediaPlayerBase.PlaybackListener listener);
+    void prepare_impl();
+    void fastForward_impl();
+    void rewind_impl();
+    void seekTo_impl(long pos);
+    void setCurrentPlaylistItem_impl(int index);
 }
diff --git a/nfc-extras/tests/Android.mk b/nfc-extras/tests/Android.mk
index 34d6508..51396d3 100644
--- a/nfc-extras/tests/Android.mk
+++ b/nfc-extras/tests/Android.mk
@@ -19,7 +19,7 @@
 LOCAL_MODULE_TAGS := tests
 
 LOCAL_JAVA_LIBRARIES := \
-    android.test.runner \
+    android.test.runner.stubs \
     com.android.nfc_extras \
     android.test.base.stubs
 
diff --git a/packages/MtpDocumentsProvider/AndroidManifest.xml b/packages/MtpDocumentsProvider/AndroidManifest.xml
index 8d79f62..c0a59b3 100644
--- a/packages/MtpDocumentsProvider/AndroidManifest.xml
+++ b/packages/MtpDocumentsProvider/AndroidManifest.xml
@@ -3,6 +3,7 @@
           package="com.android.mtp"
           android:sharedUserId="android.media">
     <uses-feature android:name="android.hardware.usb.host" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
     <uses-permission android:name="android.permission.MANAGE_USB" />
     <application android:label="@string/app_label">
         <provider
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 55f7a0a..d556db4 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1120,8 +1120,8 @@
                 Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
                 GlobalSettingsProto.NOTIFICATION_SNOOZE_OPTIONS);
         dumpSetting(s, p,
-                    Settings.Global.ZRAM_ENABLED,
-                    GlobalSettingsProto.ZRAM_ENABLED);
+                Settings.Global.ZRAM_ENABLED,
+                GlobalSettingsProto.ZRAM_ENABLED);
         dumpSetting(s, p,
                 Settings.Global.ENABLE_SMART_REPLIES_IN_NOTIFICATIONS,
                 GlobalSettingsProto.ENABLE_SMART_REPLIES_IN_NOTIFICATIONS);
@@ -1129,8 +1129,14 @@
                 Settings.Global.SHOW_FIRST_CRASH_DIALOG,
                 GlobalSettingsProto.SHOW_FIRST_CRASH_DIALOG);
         dumpSetting(s, p,
-                    Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED,
-                    GlobalSettingsProto.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED);
+                Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED,
+                GlobalSettingsProto.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED);
+        dumpSetting(s, p,
+                Settings.Global.SHOW_RESTART_IN_CRASH_DIALOG,
+                GlobalSettingsProto.SHOW_RESTART_IN_CRASH_DIALOG);
+        dumpSetting(s, p,
+                Settings.Global.SHOW_MUTE_IN_CRASH_DIALOG,
+                GlobalSettingsProto.SHOW_MUTE_IN_CRASH_DIALOG);
     }
 
     /** Dump a single {@link SettingsState.Setting} to a proto buf */
@@ -1755,6 +1761,9 @@
         dumpSetting(s, p,
                 Settings.Secure.BACKUP_MANAGER_CONSTANTS,
                 SecureSettingsProto.BACKUP_MANAGER_CONSTANTS);
+        dumpSetting(s, p,
+                Settings.Secure.BLUETOOTH_ON_WHILE_DRIVING,
+                SecureSettingsProto.BLUETOOTH_ON_WHILE_DRIVING);
     }
 
     private static void dumpProtoSystemSettingsLocked(
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 5435e118..b7d6da4 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1647,6 +1647,15 @@
                 restriction = UserManager.DISALLOW_AIRPLANE_MODE;
                 break;
 
+            case Settings.Secure.DOZE_ENABLED:
+            case Settings.Secure.DOZE_ALWAYS_ON:
+            case Settings.Secure.DOZE_PULSE_ON_PICK_UP:
+            case Settings.Secure.DOZE_PULSE_ON_LONG_PRESS:
+            case Settings.Secure.DOZE_PULSE_ON_DOUBLE_TAP:
+                if ("0".equals(value)) return false;
+                restriction = UserManager.DISALLOW_AMBIENT_DISPLAY;
+                break;
+
             default:
                 if (setting != null && setting.startsWith(Settings.Global.DATA_ROAMING)) {
                     if ("0".equals(value)) return false;
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 64b2ae6..79299aa 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -44,6 +44,7 @@
     <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
     <uses-permission android:name="android.permission.MANAGE_USB" />
     <uses-permission android:name="android.permission.USE_RESERVED_DISK" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
     <!-- System tool permissions granted to the shell. -->
     <uses-permission android:name="android.permission.REAL_GET_TASKS" />
     <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 4b2e62c..0b6e11b 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -89,6 +89,7 @@
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
     <uses-permission android:name="android.permission.GET_TOP_ACTIVITY_INFO" />
     <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
+    <uses-permission android:name="android.permission.START_ACTIVITY_AS_CALLER" />
     <uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" />
     <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT" />
 
@@ -555,6 +556,22 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".chooser.ChooserActivity"
+                android:theme="@*android:style/Theme.NoDisplay"
+                android:finishOnCloseSystemDialogs="true"
+                android:excludeFromRecents="true"
+                android:documentLaunchMode="never"
+                android:relinquishTaskIdentity="true"
+                android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+                android:process=":ui"
+                android:visibleToInstantApps="true">
+            <intent-filter>
+                <action android:name="android.intent.action.CHOOSER_UI" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.VOICE" />
+            </intent-filter>
+        </activity>
+
         <!-- Doze with notifications, run in main sysui process for every user  -->
         <service
             android:name=".doze.DozeService"
diff --git a/packages/SystemUI/res/drawable/ic_chevron_up.xml b/packages/SystemUI/res/drawable/ic_chevron_up.xml
new file mode 100644
index 0000000..835d0ad
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_chevron_up.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/output_chooser.xml b/packages/SystemUI/res/layout/output_chooser.xml
index b96f447..b9f7b15 100644
--- a/packages/SystemUI/res/layout/output_chooser.xml
+++ b/packages/SystemUI/res/layout/output_chooser.xml
@@ -15,47 +15,56 @@
      limitations under the License.
 -->
 <!-- extends LinearLayout -->
-<com.android.systemui.volume.OutputChooserLayout
+<com.android.systemui.HardwareUiLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:sysui="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/output_chooser"
-    android:minWidth="320dp"
-    android:minHeight="320dp"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:padding="20dp" >
-
-    <TextView
-        android:id="@+id/title"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:textDirection="locale"
-        android:textAppearance="@style/TextAppearance.QS.DetailHeader"
-        android:layout_marginBottom="20dp" />
-
-    <com.android.systemui.qs.AutoSizingList
-        android:id="@android:id/list"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
+    android:layout_marginBottom="0dp"
+    android:clipToPadding="false"
+    android:theme="@style/qs_theme"
+    android:clipChildren="false">
+    <com.android.systemui.volume.OutputChooserLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:sysui="http://schemas.android.com/apk/res-auto"
+        android:id="@+id/output_chooser"
+        android:layout_width="@dimen/output_chooser_panel_width"
+        android:layout_height="@dimen/output_chooser_panel_width"
+        android:layout_gravity="center_vertical|end"
         android:orientation="vertical"
-        sysui:itemHeight="@dimen/qs_detail_item_height"
-        style="@style/AutoSizingList"/>
-
-    <LinearLayout
-        android:id="@android:id/empty"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_gravity="center"
-        android:gravity="center"
-        android:orientation="vertical">
+        android:translationZ="8dp"
+        android:padding="20dp" >
 
         <TextView
-            android:id="@+id/empty_text"
+            android:id="@+id/title"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:textDirection="locale"
-            android:layout_marginTop="20dp"
-            android:textAppearance="@style/TextAppearance.QS.DetailEmpty"/>
-    </LinearLayout>
-</com.android.systemui.volume.OutputChooserLayout>
+            android:textAppearance="@style/TextAppearance.QS.DetailHeader"
+            android:layout_marginBottom="20dp" />
+
+        <com.android.systemui.qs.AutoSizingList
+            android:id="@android:id/list"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical"
+            sysui:itemHeight="@dimen/qs_detail_item_height"
+            style="@style/AutoSizingList"/>
+
+        <LinearLayout
+            android:id="@android:id/empty"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_gravity="center"
+            android:gravity="center"
+            android:orientation="vertical">
+
+            <TextView
+                android:id="@+id/empty_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textDirection="locale"
+                android:layout_marginTop="20dp"
+                android:textAppearance="@style/TextAppearance.QS.DetailEmpty"/>
+        </LinearLayout>
+    </com.android.systemui.volume.OutputChooserLayout>
+</com.android.systemui.HardwareUiLayout>
diff --git a/packages/SystemUI/res/layout/recents_swipe_up_onboarding.xml b/packages/SystemUI/res/layout/recents_swipe_up_onboarding.xml
new file mode 100644
index 0000000..b3d5c90
--- /dev/null
+++ b/packages/SystemUI/res/layout/recents_swipe_up_onboarding.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="48dp"
+    android:layout_width="match_parent"
+    android:background="@android:color/black"
+    android:layout_gravity="center">
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="1dp"
+        android:background="?android:attr/listDivider"
+        android:gravity="top"/>
+    <TextView
+        android:id="@+id/onboarding_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:text="@string/recents_swipe_up_onboarding"
+        android:textColor="@android:color/white"
+        android:textSize="16sp"
+        android:drawableBottom="@drawable/ic_chevron_up"/>
+    <ImageView
+        android:id="@+id/dismiss"
+        android:layout_width="48dp"
+        android:layout_height="48dp"
+        android:padding="12dp"
+        android:layout_marginEnd="6dp"
+        android:src="@drawable/ic_close_white"
+        android:background="?android:attr/selectableItemBackgroundBorderless"
+        android:layout_gravity="center_vertical|end"/>
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 5108f58..117cd14 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -41,6 +41,7 @@
             android:paddingTop="12dp"
             android:paddingBottom="12dp"
             android:background="@drawable/rounded_bg_full"
+            android:translationZ="8dp"
             android:orientation="horizontal" >
                 <!-- volume rows added and removed here! :-) -->
         </LinearLayout>
@@ -57,6 +58,7 @@
             android:background="@drawable/rounded_bg_full"
             android:gravity="center"
             android:layout_gravity="end"
+            android:translationZ="8dp"
             android:orientation="vertical" >
 
             <TextView
diff --git a/packages/SystemUI/res/layout/wireless_charging_layout.xml b/packages/SystemUI/res/layout/wireless_charging_layout.xml
new file mode 100644
index 0000000..113282b
--- /dev/null
+++ b/packages/SystemUI/res/layout/wireless_charging_layout.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2018, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <!-- Circle animation -->
+    <com.android.systemui.charging.WirelessChargingView
+        android:id="@+id/wireless_charging_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:elevation="4dp"/>
+
+    <!-- Text inside circle -->
+    <LinearLayout
+        android:id="@+id/wireless_charging_text_layout"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/wireless_charging_percentage"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:textSize="32sp"
+            android:textColor="?attr/wallpaperTextColor"/>
+
+        <TextView
+            android:id="@+id/wireless_charging_secondary_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:textColor="?attr/wallpaperTextColor"/>
+    </LinearLayout>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b749a18..7ee9eda 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -267,6 +267,8 @@
 
     <dimen name="volume_dialog_panel_width">120dp</dimen>
 
+    <dimen name="output_chooser_panel_width">320dp</dimen>
+
     <!-- Gravity for the notification panel -->
     <integer name="notification_panel_layout_gravity">0x31</integer><!-- center_horizontal|top -->
 
@@ -873,7 +875,7 @@
     <dimen name="rounded_corner_radius">0dp</dimen>
     <dimen name="rounded_corner_content_padding">0dp</dimen>
     <dimen name="nav_content_padding">0dp</dimen>
-    <dimen name="nav_quick_scrub_track_edge_padding">32dp</dimen>
+    <dimen name="nav_quick_scrub_track_edge_padding">42dp</dimen>
     <dimen name="nav_quick_scrub_track_thickness">2dp</dimen>
 
     <!-- Intended corner radius when drawing the mobile signal -->
@@ -893,4 +895,12 @@
     <dimen name="fingerprint_dialog_icon_size">44dp</dimen>
     <dimen name="fingerprint_dialog_fp_icon_size">60dp</dimen>
     <dimen name="fingerprint_dialog_animation_translation_offset">350dp</dimen>
+
+    <!-- WirelessCharging Animation values -->
+    <!-- Starting text size of batteryLevel for wireless charging animation -->
+    <dimen name="config_batteryLevelTextSizeStart" format="float">5.0</dimen>
+    <!-- Ending text size of batteryLevel for wireless charging animation -->
+    <dimen name="config_batteryLevelTextSizeEnd" format="float">32.0</dimen>
+    <!-- Wireless Charging battery level text animation duration -->
+    <integer name="config_batteryLevelTextAnimationDuration">400</integer>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 7dc4587..8ea1225 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -831,6 +831,8 @@
     <string name="recents_stack_action_button_label">Clear all</string>
     <!-- Recents: Hint text that shows on the drop targets to start multiwindow. [CHAR LIMIT=NONE] -->
     <string name="recents_drag_hint_message">Drag here to use split screen</string>
+    <!-- Recents: Text that shows above the nav bar after launching a few apps. [CHAR LIMIT=NONE] -->
+    <string name="recents_swipe_up_onboarding">Swipe up to switch apps</string>
 
     <!-- Recents: MultiStack add stack split horizontal radio button. [CHAR LIMIT=NONE] -->
     <string name="recents_multistack_add_stack_dialog_split_horizontal">Split Horizontal</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index 1c99d38..c9a6ea9 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -30,6 +30,7 @@
 import android.app.ActivityOptions;
 import android.app.AppGlobals;
 import android.app.IAssistDataReceiver;
+import android.app.WindowConfiguration.ActivityType;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
@@ -96,11 +97,14 @@
      * @return the top running task (can be {@code null}).
      */
     public ActivityManager.RunningTaskInfo getRunningTask() {
+        return getRunningTask(ACTIVITY_TYPE_RECENTS /* ignoreActivityType */);
+    }
+
+    public ActivityManager.RunningTaskInfo getRunningTask(@ActivityType int ignoreActivityType) {
         // Note: The set of running tasks from the system is ordered by recency
         try {
             List<ActivityManager.RunningTaskInfo> tasks =
-                    ActivityManager.getService().getFilteredTasks(1,
-                            ACTIVITY_TYPE_RECENTS /* ignoreActivityType */,
+                    ActivityManager.getService().getFilteredTasks(1, ignoreActivityType,
                             WINDOWING_MODE_PINNED /* ignoreWindowingMode */);
             if (tasks.isEmpty()) {
                 return null;
diff --git a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
index ca34345..9481788 100644
--- a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
@@ -58,6 +58,7 @@
     private boolean mRoundedDivider;
     private int mRotation = ROTATION_NONE;
     private boolean mRotatedBackground;
+    private boolean mSwapOrientation = true;
 
     public HardwareUiLayout(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -145,6 +146,10 @@
         updateRotation();
     }
 
+    public void setSwapOrientation(boolean swapOrientation) {
+        mSwapOrientation = swapOrientation;
+    }
+
     private void updateRotation() {
         int rotation = RotationUtils.getRotation(getContext());
         if (rotation != mRotation) {
@@ -173,7 +178,9 @@
                 if (to == ROTATION_SEASCAPE) {
                     swapOrder(linearLayout);
                 }
-                linearLayout.setOrientation(LinearLayout.HORIZONTAL);
+                if (mSwapOrientation) {
+                    linearLayout.setOrientation(LinearLayout.HORIZONTAL);
+                }
                 swapDimens(this.mChild);
             }
         } else {
@@ -184,7 +191,9 @@
                 if (from == ROTATION_SEASCAPE) {
                     swapOrder(linearLayout);
                 }
-                linearLayout.setOrientation(LinearLayout.VERTICAL);
+                if (mSwapOrientation) {
+                    linearLayout.setOrientation(LinearLayout.VERTICAL);
+                }
                 swapDimens(mChild);
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
index 0be522b..244c1b9 100644
--- a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
@@ -195,6 +195,10 @@
         return mOverviewProxy;
     }
 
+    public ComponentName getLauncherComponent() {
+        return mLauncherComponentName;
+    }
+
     private void disconnectFromLauncherService() {
         if (mOverviewProxy != null) {
             mOverviewProxy.asBinder().unlinkToDeath(mOverviewServiceDeathRcpt, 0);
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index 4437d31..9319bc6 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -48,6 +48,8 @@
         Key.QS_WORK_ADDED,
         Key.QS_NIGHTDISPLAY_ADDED,
         Key.SEEN_MULTI_USER,
+        Key.NUM_APPS_LAUNCHED,
+        Key.HAS_SWIPED_UP_FOR_RECENTS,
     })
     public @interface Key {
         @Deprecated
@@ -75,6 +77,8 @@
         @Deprecated
         String QS_NIGHTDISPLAY_ADDED = "QsNightDisplayAdded";
         String SEEN_MULTI_USER = "HasSeenMultiUser";
+        String NUM_APPS_LAUNCHED = "NumAppsLaunched";
+        String HAS_SWIPED_UP_FOR_RECENTS = "HasSwipedUpForRecents";
     }
 
     public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) {
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
new file mode 100644
index 0000000..348855b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.charging;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.util.Slog;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowManager;
+
+/**
+ * A WirelessChargingAnimation is a view containing view + animation for wireless charging.
+ * @hide
+ */
+public class WirelessChargingAnimation {
+
+    public static final long DURATION = 1400;
+    private static final String TAG = "WirelessChargingView";
+    private static final boolean LOCAL_LOGV = false;
+
+    private final WirelessChargingView mCurrentWirelessChargingView;
+    private static WirelessChargingView mPreviousWirelessChargingView;
+
+    /**
+     * Constructs an empty WirelessChargingAnimation object.  If looper is null,
+     * Looper.myLooper() is used.  Must set
+     * {@link WirelessChargingAnimation#mCurrentWirelessChargingView}
+     * before calling {@link #show} - can be done through {@link #makeWirelessChargingAnimation}.
+     * @hide
+     */
+    public WirelessChargingAnimation(@NonNull Context context, @Nullable Looper looper, int
+            batteryLevel) {
+        mCurrentWirelessChargingView = new WirelessChargingView(context, looper,
+                batteryLevel);
+    }
+
+    /**
+     * Creates a wireless charging animation object populated with next view.
+     * @hide
+     */
+    public static WirelessChargingAnimation makeWirelessChargingAnimation(@NonNull Context context,
+            @Nullable Looper looper, int batteryLevel) {
+        return new WirelessChargingAnimation(context, looper, batteryLevel);
+    }
+
+    /**
+     * Show the view for the specified duration.
+     */
+    public void show() {
+        if (mCurrentWirelessChargingView == null ||
+                mCurrentWirelessChargingView.mNextView == null) {
+            throw new RuntimeException("setView must have been called");
+        }
+
+        if (mPreviousWirelessChargingView != null) {
+            mPreviousWirelessChargingView.hide(0);
+        }
+
+        mPreviousWirelessChargingView = mCurrentWirelessChargingView;
+        mCurrentWirelessChargingView.show();
+        mCurrentWirelessChargingView.hide(DURATION);
+    }
+
+    private static class WirelessChargingView {
+        private static final int SHOW = 0;
+        private static final int HIDE = 1;
+
+        private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
+        private final Handler mHandler;
+
+        private int mGravity;
+
+        private View mView;
+        private View mNextView;
+        private WindowManager mWM;
+
+        public WirelessChargingView(Context context, @Nullable Looper looper, int batteryLevel) {
+            mNextView = new WirelessChargingLayout(context, batteryLevel);
+            mGravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER;
+
+            final WindowManager.LayoutParams params = mParams;
+            params.height = WindowManager.LayoutParams.WRAP_CONTENT;
+            params.width = WindowManager.LayoutParams.WRAP_CONTENT;
+            params.format = PixelFormat.TRANSLUCENT;
+
+            params.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
+            params.setTitle("Charging Animation");
+            params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+                    | WindowManager.LayoutParams.FLAG_DIM_BEHIND;
+
+            params.dimAmount = .3f;
+
+            if (looper == null) {
+                // Use Looper.myLooper() if looper is not specified.
+                looper = Looper.myLooper();
+                if (looper == null) {
+                    throw new RuntimeException(
+                            "Can't display wireless animation on a thread that has not called "
+                                    + "Looper.prepare()");
+                }
+            }
+
+            mHandler = new Handler(looper, null) {
+                @Override
+                public void handleMessage(Message msg) {
+                    switch (msg.what) {
+                        case SHOW: {
+                            handleShow();
+                            break;
+                        }
+                        case HIDE: {
+                            handleHide();
+                            // Don't do this in handleHide() because it is also invoked by
+                            // handleShow()
+                            mNextView = null;
+                            break;
+                        }
+                    }
+                }
+            };
+        }
+
+        public void show() {
+            if (LOCAL_LOGV) Log.v(TAG, "SHOW: " + this);
+            mHandler.obtainMessage(SHOW).sendToTarget();
+        }
+
+        public void hide(long duration) {
+            if (LOCAL_LOGV) Log.v(TAG, "HIDE: " + this);
+            mHandler.sendMessageDelayed(Message.obtain(mHandler, HIDE), duration);
+        }
+
+        private void handleShow() {
+            if (LOCAL_LOGV) {
+                Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView + " mNextView="
+                        + mNextView);
+            }
+
+            if (mView != mNextView) {
+                // remove the old view if necessary
+                handleHide();
+                mView = mNextView;
+                Context context = mView.getContext().getApplicationContext();
+                String packageName = mView.getContext().getOpPackageName();
+                if (context == null) {
+                    context = mView.getContext();
+                }
+                mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+                // We can resolve the Gravity here by using the Locale for getting
+                // the layout direction
+                final Configuration config = mView.getContext().getResources().getConfiguration();
+                final int gravity = Gravity.getAbsoluteGravity(mGravity,
+                        config.getLayoutDirection());
+                mParams.gravity = gravity;
+                if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
+                    mParams.horizontalWeight = 1.0f;
+                }
+                if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
+                    mParams.verticalWeight = 1.0f;
+                }
+                mParams.packageName = packageName;
+                mParams.hideTimeoutMilliseconds = DURATION;
+
+                if (mView.getParent() != null) {
+                    if (LOCAL_LOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
+                    mWM.removeView(mView);
+                }
+                if (LOCAL_LOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
+
+                try {
+                    mWM.addView(mView, mParams);
+                } catch (WindowManager.BadTokenException e) {
+                    Slog.d(TAG, "Unable to add wireless charging view. " + e);
+                }
+            }
+        }
+
+        private void handleHide() {
+            if (LOCAL_LOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
+            if (mView != null) {
+                if (mView.getParent() != null) {
+                    if (LOCAL_LOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
+                    mWM.removeViewImmediate(mView);
+                }
+
+                mView = null;
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
new file mode 100644
index 0000000..c78ea56
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.charging;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+
+import java.text.NumberFormat;
+
+/**
+ * @hide
+ */
+public class WirelessChargingLayout extends FrameLayout {
+    private final static int UNKNOWN_BATTERY_LEVEL = -1;
+
+    public WirelessChargingLayout(Context context) {
+        super(context);
+        init(context, null);
+    }
+
+    public WirelessChargingLayout(Context context, int batterylLevel) {
+        super(context);
+        init(context, null, batterylLevel);
+    }
+
+    public WirelessChargingLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init(context, attrs);
+    }
+
+    private void init(Context c, AttributeSet attrs) {
+        init(c, attrs, -1);
+    }
+
+    private void init(Context c, AttributeSet attrs, int batteryLevel) {
+        final int mBatteryLevel = batteryLevel;
+
+        inflate(c, R.layout.wireless_charging_layout, this);
+
+        // where the circle animation occurs:
+        final WirelessChargingView mChargingView = findViewById(R.id.wireless_charging_view);
+
+        // amount of battery:
+        final TextView mPercentage = findViewById(R.id.wireless_charging_percentage);
+
+        // (optional) time until full charge if available
+        final TextView mSecondaryText = findViewById(R.id.wireless_charging_secondary_text);
+
+        if (batteryLevel != UNKNOWN_BATTERY_LEVEL) {
+            mPercentage.setText(NumberFormat.getPercentInstance().format(mBatteryLevel / 100f));
+
+            ValueAnimator animator = ObjectAnimator.ofFloat(mPercentage, "textSize",
+                    getContext().getResources().getFloat(R.dimen.config_batteryLevelTextSizeStart),
+                    getContext().getResources().getFloat(R.dimen.config_batteryLevelTextSizeEnd));
+
+            animator.setDuration((long) getContext().getResources().getInteger(
+                    R.integer.config_batteryLevelTextAnimationDuration));
+            animator.start();
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.java
new file mode 100644
index 0000000..f5edf52
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.charging;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+import com.android.settingslib.Utils;
+import com.android.systemui.R;
+
+final class WirelessChargingView extends View {
+
+    private Interpolator mInterpolator;
+    private float mPathGone;
+    private float mInterpolatedPathGone;
+    private long mAnimationStartTime;
+    private long mStartSpinCircleAnimationTime;
+    private long mAnimationOffset = 500;
+    private long mTotalAnimationDuration = WirelessChargingAnimation.DURATION - mAnimationOffset;
+    private long mExpandingCircle = (long) (mTotalAnimationDuration * .9);
+    private long mSpinCircleAnimationTime = mTotalAnimationDuration - mExpandingCircle;
+
+    private boolean mFinishedAnimatingSpinningCircles = false;
+
+    private int mStartAngle = -90;
+    private int mNumSmallCircles = 20;
+    private int mSmallCircleRadius = 10;
+
+    private int mMainCircleStartRadius = 100;
+    private int mMainCircleEndRadius = 230;
+    private int mMainCircleCurrentRadius = mMainCircleStartRadius;
+
+    private int mCenterX;
+    private int mCenterY;
+
+    private Paint mPaint;
+    private Context mContext;
+
+    public WirelessChargingView(Context context) {
+        super(context);
+        init(context, null);
+    }
+
+    public WirelessChargingView(Context context, AttributeSet attr) {
+        super(context, attr);
+        init(context, attr);
+    }
+
+    public WirelessChargingView(Context context, AttributeSet attr, int styleAttr) {
+        super(context, attr, styleAttr);
+        init(context, attr);
+    }
+
+    public void init(Context context, AttributeSet attr) {
+        mContext = context;
+        setupPaint();
+        mInterpolator = new DecelerateInterpolator();
+    }
+
+    private void setupPaint() {
+        mPaint = new Paint();
+        mPaint.setColor(Utils.getColorAttr(mContext, R.attr.wallpaperTextColor));
+    }
+
+    @Override
+    protected void onDraw(final Canvas canvas) {
+        super.onDraw(canvas);
+
+        if (mAnimationStartTime == 0) {
+            mAnimationStartTime = System.currentTimeMillis();
+        }
+
+        updateDrawingParameters();
+        drawCircles(canvas);
+
+        if (!mFinishedAnimatingSpinningCircles) {
+            invalidate();
+        }
+    }
+
+    /**
+     * Draws a larger circle of radius {@link WirelessChargingView#mMainCircleEndRadius} composed of
+     * {@link WirelessChargingView#mNumSmallCircles} smaller circles
+     * @param canvas
+     */
+    private void drawCircles(Canvas canvas) {
+        mCenterX = canvas.getWidth() / 2;
+        mCenterY = canvas.getHeight() / 2;
+
+        // angleOffset makes small circles look like they're moving around the main circle
+        float angleOffset = mPathGone * 10;
+
+        // draws mNumSmallCircles to compose a larger, main circle
+        for (int circle = 0; circle < mNumSmallCircles; circle++) {
+            double angle = ((mStartAngle + angleOffset) * Math.PI / 180) + (circle * ((2 * Math.PI)
+                    / mNumSmallCircles));
+
+            int x = (int) (mCenterX + Math.cos(angle) * (mMainCircleCurrentRadius +
+                    mSmallCircleRadius));
+            int y = (int) (mCenterY + Math.sin(angle) * (mMainCircleCurrentRadius +
+                    mSmallCircleRadius));
+
+            canvas.drawCircle(x, y, mSmallCircleRadius, mPaint);
+        }
+
+        if (mMainCircleCurrentRadius >= mMainCircleEndRadius && !isSpinCircleAnimationStarted()) {
+            mStartSpinCircleAnimationTime = System.currentTimeMillis();
+        }
+
+        if (isSpinAnimationFinished()) {
+            mFinishedAnimatingSpinningCircles = true;
+        }
+    }
+
+    private boolean isSpinCircleAnimationStarted() {
+        return mStartSpinCircleAnimationTime != 0;
+    }
+
+    private boolean isSpinAnimationFinished() {
+        return isSpinCircleAnimationStarted() && System.currentTimeMillis() -
+                mStartSpinCircleAnimationTime > mSpinCircleAnimationTime;
+    }
+
+    private void updateDrawingParameters() {
+        mPathGone = getPathGone(System.currentTimeMillis());
+        mInterpolatedPathGone = mInterpolator.getInterpolation(mPathGone);
+
+        if (mPathGone < 1.0f) {
+            mMainCircleCurrentRadius = mMainCircleStartRadius + (int) (mInterpolatedPathGone *
+                    (mMainCircleEndRadius - mMainCircleStartRadius));
+        } else {
+            mMainCircleCurrentRadius = mMainCircleEndRadius;
+        }
+    }
+
+    /**
+     * @return decimal depicting how far along the creation of the larger circle (of circles) is
+     * For values < 1.0, the larger circle is being drawn
+     * For values > 1.0 the larger circle has been drawn and further animation can occur
+     */
+    private float getPathGone(long now) {
+        return (float) (now - mAnimationStartTime) / (mExpandingCircle);
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/chooser/ChooserActivity.java b/packages/SystemUI/src/com/android/systemui/chooser/ChooserActivity.java
new file mode 100644
index 0000000..085ece7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/chooser/ChooserActivity.java
@@ -0,0 +1,41 @@
+/*
+ * 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.systemui.chooser;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.systemui.R;
+
+import java.lang.Thread;
+import java.util.ArrayList;
+
+public final class ChooserActivity extends Activity {
+
+    private static final String TAG = "ChooserActivity";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        ChooserHelper.onChoose(this);
+        finish();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/chooser/ChooserHelper.java b/packages/SystemUI/src/com/android/systemui/chooser/ChooserHelper.java
new file mode 100644
index 0000000..ac22568
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/chooser/ChooserHelper.java
@@ -0,0 +1,45 @@
+/*
+ * 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.systemui.chooser;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.systemui.R;
+
+public class ChooserHelper {
+
+    private static final String TAG = "ChooserHelper";
+
+    static void onChoose(Activity activity) {
+        final Intent thisIntent = activity.getIntent();
+        final Bundle thisExtras = thisIntent.getExtras();
+        final Intent chosenIntent = thisIntent.getParcelableExtra(Intent.EXTRA_INTENT);
+        final Bundle options = thisIntent.getParcelableExtra(ActivityManager.EXTRA_OPTIONS);
+        final IBinder permissionToken =
+                thisExtras.getBinder(ActivityManager.EXTRA_PERMISSION_TOKEN);
+        final boolean ignoreTargetSecurity =
+                thisIntent.getBooleanExtra(ActivityManager.EXTRA_IGNORE_TARGET_SECURITY, false);
+        final int userId = thisIntent.getIntExtra(Intent.EXTRA_USER_ID, -1);
+        activity.startActivityAsCaller(
+                chosenIntent, options, permissionToken, ignoreTargetSecurity, userId);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
index f06cda0..aa08562 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
@@ -14,6 +14,10 @@
 
 package com.android.systemui.globalactions;
 
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.Dependency;
 import com.android.systemui.SysUiServiceProvider;
@@ -25,10 +29,6 @@
 import com.android.systemui.statusbar.policy.ExtensionController;
 import com.android.systemui.statusbar.policy.ExtensionController.Extension;
 
-import android.content.Context;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-
 public class GlobalActionsComponent extends SystemUI implements Callbacks, GlobalActionsManager {
 
     private GlobalActions mPlugin;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java b/packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java
new file mode 100644
index 0000000..6c553de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+
+import android.annotation.TargetApi;
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.RippleDrawable;
+import android.os.Build;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.systemui.Prefs;
+import com.android.systemui.R;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+
+/**
+ * Shows onboarding for the new recents interaction in P (codenamed quickstep).
+ */
+@TargetApi(Build.VERSION_CODES.P)
+public class SwipeUpOnboarding {
+
+    private static final String TAG = "SwipeUpOnboarding";
+    private static final boolean RESET_PREFS_FOR_DEBUG = false;
+    private static final long SHOW_DELAY_MS = 500;
+    private static final long SHOW_HIDE_DURATION_MS = 300;
+    // Don't show the onboarding until the user has launched this number of apps.
+    private static final int SHOW_ON_APP_LAUNCH = 2;
+
+    private final Context mContext;
+    private final WindowManager mWindowManager;
+    private final View mLayout;
+    private final TextView mTextView;
+    private final ImageView mDismissView;
+    private final ColorDrawable mBackgroundDrawable;
+    private final int mDarkBackgroundColor;
+    private final int mLightBackgroundColor;
+    private final int mDarkContentColor;
+    private final int mLightContentColor;
+    private final RippleDrawable mDarkRipple;
+    private final RippleDrawable mLightRipple;
+
+    private boolean mTaskListenerRegistered;
+    private ComponentName mLauncherComponent;
+    private boolean mLayoutAttachedToWindow;
+    private boolean mBackgroundIsLight;
+
+    private final SysUiTaskStackChangeListener mTaskListener = new SysUiTaskStackChangeListener() {
+        @Override
+        public void onTaskStackChanged() {
+            ActivityManager.RunningTaskInfo info = ActivityManagerWrapper.getInstance()
+                    .getRunningTask(ACTIVITY_TYPE_UNDEFINED /* ignoreActivityType */);
+            int activityType = info.configuration.windowConfiguration.getActivityType();
+            int numAppsLaunched = Prefs.getInt(mContext, Prefs.Key.NUM_APPS_LAUNCHED, 0);
+            if (activityType == ACTIVITY_TYPE_STANDARD) {
+                numAppsLaunched++;
+                if (numAppsLaunched >= SHOW_ON_APP_LAUNCH) {
+                    show();
+                } else {
+                    Prefs.putInt(mContext, Prefs.Key.NUM_APPS_LAUNCHED, numAppsLaunched);
+                }
+            } else {
+                String runningPackage = info.topActivity.getPackageName();
+                // TODO: use callback from the overview proxy service to handle this case
+                if (runningPackage.equals(mLauncherComponent.getPackageName())
+                        && activityType == ACTIVITY_TYPE_RECENTS) {
+                    Prefs.putBoolean(mContext, Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, true);
+                    onDisconnectedFromLauncher();
+                } else {
+                    hide(false);
+                }
+            }
+        }
+    };
+
+    private final View.OnAttachStateChangeListener mOnAttachStateChangeListener
+            = new View.OnAttachStateChangeListener() {
+        @Override
+        public void onViewAttachedToWindow(View view) {
+            if (view == mLayout) {
+                mLayoutAttachedToWindow = true;
+            }
+        }
+
+        @Override
+        public void onViewDetachedFromWindow(View view) {
+            if (view == mLayout) {
+                mLayoutAttachedToWindow = false;
+            }
+        }
+    };
+
+    public SwipeUpOnboarding(Context context) {
+        mContext = context;
+        final Resources res = context.getResources();
+        mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+        mLayout = LayoutInflater.from(mContext).inflate(R.layout.recents_swipe_up_onboarding, null);
+        mTextView = (TextView) mLayout.findViewById(R.id.onboarding_text);
+        mDismissView = (ImageView) mLayout.findViewById(R.id.dismiss);
+        mDarkBackgroundColor = res.getColor(android.R.color.background_dark);
+        mLightBackgroundColor = res.getColor(android.R.color.background_light);
+        mDarkContentColor = res.getColor(R.color.primary_text_default_material_light);
+        mLightContentColor = res.getColor(R.color.primary_text_default_material_dark);
+        mDarkRipple = new RippleDrawable(res.getColorStateList(R.color.ripple_material_light),
+                null, null);
+        mLightRipple = new RippleDrawable(res.getColorStateList(R.color.ripple_material_dark),
+                null, null);
+        mBackgroundDrawable = new ColorDrawable(mDarkBackgroundColor);
+
+        mLayout.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
+        mLayout.setBackground(mBackgroundDrawable);
+        mDismissView.setOnClickListener(v -> hide(true));
+
+        if (RESET_PREFS_FOR_DEBUG) {
+            Prefs.putBoolean(mContext, Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, false);
+            Prefs.putInt(mContext, Prefs.Key.NUM_APPS_LAUNCHED, 0);
+        }
+    }
+
+    public void onConnectedToLauncher(ComponentName launcherComponent) {
+        mLauncherComponent = launcherComponent;
+        boolean alreadyLearnedSwipeUpForRecents = Prefs.getBoolean(mContext,
+                Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, false);
+        if (!mTaskListenerRegistered && !alreadyLearnedSwipeUpForRecents) {
+            ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskListener);
+            mTaskListenerRegistered = true;
+        }
+    }
+
+    public void onDisconnectedFromLauncher() {
+        if (mTaskListenerRegistered) {
+            ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskListener);
+            mTaskListenerRegistered = false;
+        }
+        hide(false);
+    }
+
+    public void onConfigurationChanged(Configuration newConfiguration) {
+        if (newConfiguration.orientation != Configuration.ORIENTATION_PORTRAIT) {
+            hide(false);
+        }
+    }
+
+    public void show() {
+        // Only show in portrait.
+        int orientation = mContext.getResources().getConfiguration().orientation;
+        if (!mLayoutAttachedToWindow && orientation == Configuration.ORIENTATION_PORTRAIT) {
+            mLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+            mWindowManager.addView(mLayout, getWindowLayoutParams());
+            int layoutHeight = mLayout.getHeight();
+            if (layoutHeight == 0) {
+                mLayout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+                layoutHeight = mLayout.getMeasuredHeight();
+            }
+            mLayout.setTranslationY(layoutHeight);
+            mLayout.setAlpha(0);
+            mLayout.animate()
+                    .translationY(0)
+                    .alpha(1f)
+                    .withLayer()
+                    .setStartDelay(SHOW_DELAY_MS)
+                    .setDuration(SHOW_HIDE_DURATION_MS)
+                    .setInterpolator(new DecelerateInterpolator())
+                    .start();
+        }
+    }
+
+    public void hide(boolean animate) {
+        if (mLayoutAttachedToWindow) {
+            if (animate) {
+                mLayout.animate()
+                        .translationY(mLayout.getHeight())
+                        .alpha(0f)
+                        .withLayer()
+                        .setDuration(SHOW_HIDE_DURATION_MS)
+                        .setInterpolator(new AccelerateInterpolator())
+                        .withEndAction(() -> mWindowManager.removeView(mLayout))
+                        .start();
+            } else {
+                mWindowManager.removeView(mLayout);
+            }
+        }
+    }
+
+    public void setContentDarkIntensity(float contentDarkIntensity) {
+        boolean backgroundIsLight = contentDarkIntensity > 0.5f;
+        if (backgroundIsLight != mBackgroundIsLight) {
+            mBackgroundIsLight = backgroundIsLight;
+            mBackgroundDrawable.setColor(mBackgroundIsLight
+                    ? mLightBackgroundColor : mDarkBackgroundColor);
+            int contentColor = mBackgroundIsLight ? mDarkContentColor : mLightContentColor;
+            mTextView.setTextColor(contentColor);
+            mTextView.getCompoundDrawables()[3].setColorFilter(contentColor,
+                    PorterDuff.Mode.SRC_IN);
+            mDismissView.setColorFilter(contentColor);
+            mDismissView.setBackground(mBackgroundIsLight ? mDarkRipple : mLightRipple);
+        }
+    }
+
+    private WindowManager.LayoutParams getWindowLayoutParams() {
+        int flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
+                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG,
+                flags,
+                PixelFormat.TRANSLUCENT);
+        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+        lp.setTitle("SwipeUpOnboarding");
+        lp.gravity = Gravity.BOTTOM;
+        return lp;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 00bc62e..79e9f7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -89,6 +89,7 @@
     private static final int MSG_FINGERPRINT_HELP              = 41 << MSG_SHIFT;
     private static final int MSG_FINGERPRINT_ERROR             = 42 << MSG_SHIFT;
     private static final int MSG_FINGERPRINT_HIDE              = 43 << MSG_SHIFT;
+    private static final int MSG_SHOW_CHARGING_ANIMATION       = 44 << MSG_SHIFT;
 
     public static final int FLAG_EXCLUDE_NONE = 0;
     public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -150,6 +151,8 @@
         default void handleShowGlobalActionsMenu() { }
         default void handleShowShutdownUi(boolean isReboot, String reason) { }
 
+        default void showChargingAnimation(int batteryLevel) {  }
+
         default void onRotationProposal(int rotation, boolean isValid) { }
 
         default void showFingerprintDialog(Bundle bundle, IFingerprintDialogReceiver receiver) { }
@@ -474,6 +477,13 @@
     }
 
     @Override
+    public void showChargingAnimation(int batteryLevel) {
+        mHandler.removeMessages(MSG_SHOW_CHARGING_ANIMATION);
+        mHandler.obtainMessage(MSG_SHOW_CHARGING_ANIMATION, batteryLevel, 0)
+                .sendToTarget();
+    }
+
+    @Override
     public void onProposedRotationChanged(int rotation, boolean isValid) {
         synchronized (mLock) {
             mHandler.removeMessages(MSG_ROTATION_PROPOSAL);
@@ -751,6 +761,12 @@
                     for (int i = 0; i < mCallbacks.size(); i++) {
                         mCallbacks.get(i).hideFingerprintDialog();
                     }
+                    break;
+                case MSG_SHOW_CHARGING_ANIMATION:
+                    for (int i = 0; i < mCallbacks.size(); i++) {
+                        mCallbacks.get(i).showChargingAnimation(msg.arg1);
+                    }
+                    break;
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 64df92c..a4c17e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.os.Build;
+import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -31,6 +32,7 @@
 import android.view.ViewTreeObserver;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
+import android.widget.LinearLayout;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.NotificationColorUtil;
@@ -42,6 +44,7 @@
 import com.android.systemui.statusbar.notification.NotificationViewWrapper;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.policy.RemoteInputView;
+import com.android.systemui.statusbar.policy.SmartReplyView;
 
 /**
  * A frame layout containing the actual payload of the notification, including the contracted,
@@ -72,6 +75,7 @@
 
     private RemoteInputView mExpandedRemoteInput;
     private RemoteInputView mHeadsUpRemoteInput;
+    private SmartReplyView mExpandedSmartReplyView;
 
     private NotificationViewWrapper mContractedWrapper;
     private NotificationViewWrapper mExpandedWrapper;
@@ -1125,7 +1129,7 @@
         if (mAmbientChild != null) {
             mAmbientWrapper.onContentUpdated(entry.row);
         }
-        applyRemoteInput(entry);
+        applyRemoteInputAndSmartReply(entry);
         updateLegacy();
         mForceSelectNextLayout = true;
         setDark(mDark, false /* animate */, 0 /* delay */);
@@ -1157,20 +1161,34 @@
         }
     }
 
-    private void applyRemoteInput(final NotificationData.Entry entry) {
+    private void applyRemoteInputAndSmartReply(final NotificationData.Entry entry) {
         if (mRemoteInputController == null) {
             return;
         }
 
+        boolean enableSmartReplies = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.ENABLE_SMART_REPLIES_IN_NOTIFICATIONS, 0) != 0;
+
         boolean hasRemoteInput = false;
+        RemoteInput remoteInputWithChoices = null;
+        PendingIntent pendingIntentWithChoices = null;
 
         Notification.Action[] actions = entry.notification.getNotification().actions;
         if (actions != null) {
             for (Notification.Action a : actions) {
                 if (a.getRemoteInputs() != null) {
                     for (RemoteInput ri : a.getRemoteInputs()) {
-                        if (ri.getAllowFreeFormInput()) {
+                        boolean showRemoteInputView = ri.getAllowFreeFormInput();
+                        boolean showSmartReplyView = enableSmartReplies && ri.getChoices() != null
+                                && ri.getChoices().length > 0;
+                        if (showRemoteInputView) {
                             hasRemoteInput = true;
+                        }
+                        if (showSmartReplyView) {
+                            remoteInputWithChoices = ri;
+                            pendingIntentWithChoices = a.actionIntent;
+                        }
+                        if (showRemoteInputView || showSmartReplyView) {
                             break;
                         }
                     }
@@ -1178,6 +1196,11 @@
             }
         }
 
+        applyRemoteInput(entry, hasRemoteInput);
+        applySmartReplyView(remoteInputWithChoices, pendingIntentWithChoices);
+    }
+
+    private void applyRemoteInput(NotificationData.Entry entry, boolean hasRemoteInput) {
         View bigContentView = mExpandedChild;
         if (bigContentView != null) {
             mExpandedRemoteInput = applyRemoteInput(bigContentView, entry, hasRemoteInput,
@@ -1274,6 +1297,40 @@
         return null;
     }
 
+    private void applySmartReplyView(RemoteInput remoteInput, PendingIntent pendingIntent) {
+        mExpandedSmartReplyView = mExpandedChild == null ?
+                null : applySmartReplyView(mExpandedChild, remoteInput, pendingIntent);
+    }
+
+    private SmartReplyView applySmartReplyView(
+            View view, RemoteInput remoteInput, PendingIntent pendingIntent) {
+        View smartReplyContainerCandidate = view.findViewById(
+                com.android.internal.R.id.smart_reply_container);
+        if (!(smartReplyContainerCandidate instanceof LinearLayout)) {
+            return null;
+        }
+        LinearLayout smartReplyContainer = (LinearLayout) smartReplyContainerCandidate;
+        if (remoteInput == null || pendingIntent == null) {
+            smartReplyContainer.setVisibility(View.GONE);
+            return null;
+        }
+        SmartReplyView smartReplyView = null;
+        if (smartReplyContainer.getChildCount() == 0) {
+            smartReplyView = SmartReplyView.inflate(mContext, smartReplyContainer);
+            smartReplyContainer.addView(smartReplyView);
+        } else if (smartReplyContainer.getChildCount() == 1) {
+            View child = smartReplyContainer.getChildAt(0);
+            if (child instanceof SmartReplyView) {
+                smartReplyView = (SmartReplyView) child;
+            }
+        }
+        if (smartReplyView != null) {
+            smartReplyView.setRepliesFromRemoteInput(remoteInput, pendingIntent);
+            smartReplyContainer.setVisibility(View.VISIBLE);
+        }
+        return smartReplyView;
+    }
+
     public void closeRemoteInput() {
         if (mHeadsUpRemoteInput != null) {
             mHeadsUpRemoteInput.close();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 368b36b..dc51b1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -60,6 +60,7 @@
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
+import android.view.Surface;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
@@ -830,10 +831,13 @@
             // window in response to the orientation change.
             Handler h = getView().getHandler();
             Message msg = Message.obtain(h, () -> {
-                // If the screen rotation changes while locked, update lock rotation to flow with
+
+                // If the screen rotation changes while locked, potentially update lock to flow with
                 // new screen rotation and hide any showing suggestions.
                 if (mRotationLockController.isRotationLocked()) {
-                    mRotationLockController.setRotationLockedAtAngle(true, rotation);
+                    if (shouldOverrideUserLockPrefs(rotation)) {
+                        mRotationLockController.setRotationLockedAtAngle(true, rotation);
+                    }
                     setRotateSuggestionButtonState(false, true);
                 }
 
@@ -845,6 +849,12 @@
             msg.setAsynchronous(true);
             h.sendMessageAtFrontOfQueue(msg);
         }
+
+        private boolean shouldOverrideUserLockPrefs(final int rotation) {
+            // Only override user prefs when returning to portrait.
+            // Don't let apps that force landscape or 180 alter user lock.
+            return rotation == Surface.ROTATION_0;
+        }
     };
 
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 059ce92..9bef0ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -57,10 +57,11 @@
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.statusbar.phone.NavGesture;
 import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
+import com.android.systemui.recents.SwipeUpOnboarding;
 import com.android.systemui.stackdivider.Divider;
-import com.android.systemui.statusbar.policy.TintedKeyButtonDrawable;
 import com.android.systemui.statusbar.policy.DeadZone;
 import com.android.systemui.statusbar.policy.KeyButtonDrawable;
+import com.android.systemui.statusbar.policy.TintedKeyButtonDrawable;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -124,6 +125,7 @@
     private NavigationBarInflaterView mNavigationInflaterView;
     private RecentsComponent mRecentsComponent;
     private Divider mDivider;
+    private SwipeUpOnboarding mSwipeUpOnboarding;
 
     private class NavTransitionListener implements TransitionListener {
         private boolean mBackTransitioning;
@@ -206,6 +208,7 @@
     private final OverviewProxyListener mOverviewProxyListener = isConnected -> {
         setSlippery(!isConnected);
         setDisabledFlags(mDisabledFlags, true);
+        setUpSwipeUpOnboarding(isConnected);
     };
 
     public NavigationBarView(Context context, AttributeSet attrs) {
@@ -237,6 +240,7 @@
                 new ButtonDispatcher(R.id.rotate_suggestion));
 
         mOverviewProxyService = Dependency.get(OverviewProxyService.class);
+        mSwipeUpOnboarding = new SwipeUpOnboarding(context);
     }
 
     public BarTransitions getBarTransitions() {
@@ -630,6 +634,9 @@
         if (mGestureHelper != null) {
             mGestureHelper.onDarkIntensityChange(intensity);
         }
+        if (mSwipeUpOnboarding != null) {
+            mSwipeUpOnboarding.setContentDarkIntensity(intensity);
+        }
     }
 
     @Override
@@ -740,6 +747,7 @@
         updateTaskSwitchHelper();
         updateIcons(getContext(), mConfiguration, newConfig);
         updateRecentsIcon();
+        mSwipeUpOnboarding.onConfigurationChanged(newConfig);
         if (uiCarModeChanged || mConfiguration.densityDpi != newConfig.densityDpi
                 || mConfiguration.getLayoutDirection() != newConfig.getLayoutDirection()) {
             // If car mode or density changes, we need to reset the icons.
@@ -829,6 +837,7 @@
         Dependency.get(PluginManager.class).addPluginListener(this,
                 NavGesture.class, false /* Only one */);
         mOverviewProxyService.addCallback(mOverviewProxyListener);
+        setUpSwipeUpOnboarding(mOverviewProxyService.getProxy() != null);
     }
 
     @Override
@@ -839,6 +848,15 @@
             mGestureHelper.destroy();
         }
         mOverviewProxyService.removeCallback(mOverviewProxyListener);
+        setUpSwipeUpOnboarding(false);
+    }
+
+    private void setUpSwipeUpOnboarding(boolean connectedToOverviewProxy) {
+        if (connectedToOverviewProxy) {
+            mSwipeUpOnboarding.onConnectedToLauncher(mOverviewProxyService.getLauncherComponent());
+        } else {
+            mSwipeUpOnboarding.onDisconnectedFromLauncher();
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 3b783c2..d13ecae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -85,6 +85,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.RemoteException;
@@ -138,6 +139,7 @@
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.ActivityStarterDelegate;
 import com.android.systemui.AutoReinflateContainer;
+import com.android.systemui.charging.WirelessChargingAnimation;
 import com.android.systemui.DemoMode;
 import com.android.systemui.Dependency;
 import com.android.systemui.EventLogTags;
@@ -1419,7 +1421,6 @@
         mQSPanel.clickTile(tile);
     }
 
-
     private void updateClearAll() {
         if (!mClearAllEnabled) {
             return;
@@ -2422,6 +2423,18 @@
                 mask, fullscreenStackBounds, dockedStackBounds, sbModeChanged, mStatusBarMode);
     }
 
+    @Override
+    public void showChargingAnimation(int batteryLevel) {
+        if (mDozing) {
+            // ambient
+        } else if (mKeyguardManager.isKeyguardLocked()) {
+            // lockscreen
+        } else {
+            WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null,
+                    batteryLevel).show();
+        }
+    }
+
     void touchAutoHide() {
         // update transient bar autohide
         if (mStatusBarMode == MODE_SEMI_TRANSPARENT || (mNavigationBar != null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index c377feb..b63c1da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -137,6 +137,7 @@
         Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent,
                 results);
+        RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_FREE_FORM_INPUT);
 
         mEditText.setEnabled(false);
         mSendButton.setVisibility(INVISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index 1dcdf63..2d829af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -52,6 +52,7 @@
             results.putString(remoteInput.getResultKey(), choice.toString());
             Intent intent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
             RemoteInput.addResultsToIntent(new RemoteInput[]{remoteInput}, intent, results);
+            RemoteInput.setResultsSource(intent, RemoteInput.SOURCE_CHOICE);
             try {
                 pendingIntent.send(context, 0, intent);
             } catch (PendingIntent.CanceledException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
index e0af9ba..e3c8503 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
@@ -22,6 +22,7 @@
 
 import static com.android.settingslib.bluetooth.Utils.getBtClassDrawableWithDescription;
 
+import android.app.Dialog;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
@@ -30,6 +31,8 @@
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.net.wifi.WifiManager;
@@ -43,12 +46,16 @@
 import android.telecom.TelecomManager;
 import android.util.Log;
 import android.util.Pair;
+import android.view.Window;
+import android.view.WindowManager;
 
 import com.android.settingslib.Utils;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.systemui.Dependency;
+import com.android.systemui.HardwareUiLayout;
+import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.statusbar.policy.BluetoothController;
 
 import java.io.IOException;
@@ -58,7 +65,7 @@
 import java.util.Comparator;
 import java.util.List;
 
-public class OutputChooserDialog extends SystemUIDialog
+public class OutputChooserDialog extends Dialog
         implements DialogInterface.OnDismissListener, OutputChooserLayout.Callback {
 
     private static final String TAG = Util.logTag(OutputChooserDialog.class);
@@ -82,9 +89,11 @@
     private Drawable mTvIcon;
     private Drawable mSpeakerIcon;
     private Drawable mSpeakerGroupIcon;
+    private HardwareUiLayout mHardwareLayout;
+    private final VolumeDialogController mController;
 
     public OutputChooserDialog(Context context, MediaRouterWrapper router) {
-        super(context);
+        super(context, com.android.systemui.R.style.qs_theme);
         mContext = context;
         mBluetoothController = Dependency.get(BluetoothController.class);
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
@@ -98,6 +107,22 @@
 
         final IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
         context.registerReceiver(mReceiver, filter);
+
+        mController = Dependency.get(VolumeDialogController.class);
+
+        // Window initialization
+        Window window = getWindow();
+        window.requestFeature(Window.FEATURE_NO_TITLE);
+        window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+        window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND
+                | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
+        window.addFlags(
+                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                        | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+                        | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+        window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
     }
 
     protected void setIsInCall(boolean inCall) {
@@ -112,6 +137,9 @@
         setOnDismissListener(this::onDismiss);
 
         mView = findViewById(R.id.output_chooser);
+        mHardwareLayout = HardwareUiLayout.get(mView);
+        mHardwareLayout.setOutsideTouchListener(view -> dismiss());
+        mHardwareLayout.setSwapOrientation(false);
         mView.setCallback(this);
 
         if (mIsInCall) {
@@ -151,6 +179,7 @@
                     MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
         }
         mBluetoothController.addCallback(mCallback);
+        mController.addCallback(mControllerCallbackH, mHandler);
         isAttached = true;
     }
 
@@ -158,6 +187,7 @@
     public void onDetachedFromWindow() {
         isAttached = false;
         mRouter.removeCallback(mRouterCallback);
+        mController.removeCallback(mControllerCallbackH);
         mBluetoothController.removeCallback(mCallback);
         super.onDetachedFromWindow();
     }
@@ -169,6 +199,38 @@
     }
 
     @Override
+    public void show() {
+        super.show();
+        mHardwareLayout.setTranslationX(getAnimTranslation());
+        mHardwareLayout.setAlpha(0);
+        mHardwareLayout.animate()
+                .alpha(1)
+                .translationX(0)
+                .setDuration(300)
+                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+                .withEndAction(() -> getWindow().getDecorView().requestAccessibilityFocus())
+                .start();
+    }
+
+    @Override
+    public void dismiss() {
+        mHardwareLayout.setTranslationX(0);
+        mHardwareLayout.setAlpha(1);
+        mHardwareLayout.animate()
+                .alpha(0)
+                .translationX(getAnimTranslation())
+                .setDuration(300)
+                .withEndAction(() -> super.dismiss())
+                .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
+                .start();
+    }
+
+    private float getAnimTranslation() {
+        return getContext().getResources().getDimension(
+                com.android.systemui.R.dimen.output_chooser_panel_width) / 2;
+    }
+
+    @Override
     public void onDetailItemClick(OutputChooserLayout.Item item) {
         if (item == null || item.tag == null) return;
         if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_BT) {
@@ -416,4 +478,41 @@
             }
         }
     };
+
+    private final VolumeDialogController.Callbacks mControllerCallbackH
+            = new VolumeDialogController.Callbacks() {
+        @Override
+        public void onShowRequested(int reason) {
+            dismiss();
+        }
+
+        @Override
+        public void onDismissRequested(int reason) {}
+
+        @Override
+        public void onScreenOff() {
+            dismiss();
+        }
+
+        @Override
+        public void onStateChanged(VolumeDialogController.State state) {}
+
+        @Override
+        public void onLayoutDirectionChanged(int layoutDirection) {}
+
+        @Override
+        public void onConfigurationChanged() {}
+
+        @Override
+        public void onShowVibrateHint() {}
+
+        @Override
+        public void onShowSilentHint() {}
+
+        @Override
+        public void onShowSafetyWarning(int flags) {}
+
+        @Override
+        public void onAccessibilityModeChanged(Boolean showA11yStream) {}
+    };
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java
index 1c9cbc1..368194e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java
@@ -25,6 +25,7 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.util.AttributeSet;
+import android.util.Slog;
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
@@ -127,17 +128,26 @@
         rotate(mChild, from, to, true);
         ViewGroup rows = mChild.findViewById(R.id.volume_dialog_rows);
         rotate(rows, from, to, true);
+        swapOrientation((LinearLayout) rows);
         int rowCount = rows.getChildCount();
         for (int i = 0; i < rowCount; i++) {
-            View child = rows.getChildAt(i);
+            View row = rows.getChildAt(i);
             if (to == ROTATION_SEASCAPE) {
-                rotateSeekBars(to, 0);
+                rotateSeekBars(row, to, 180);
             } else if (to == ROTATION_LANDSCAPE) {
-                rotateSeekBars(to, 180);
+                rotateSeekBars(row, to, 0);
             } else {
-                rotateSeekBars(to, 270);
+                rotateSeekBars(row, to, 270);
             }
-            rotate(child, from, to, true);
+            rotate(row, from, to, true);
+        }
+    }
+
+    private void swapOrientation(LinearLayout layout) {
+        if(layout.getOrientation() == LinearLayout.HORIZONTAL) {
+            layout.setOrientation(LinearLayout.VERTICAL);
+        } else {
+            layout.setOrientation(LinearLayout.HORIZONTAL);
         }
     }
 
@@ -152,13 +162,13 @@
         v.setLayoutParams(params);
     }
 
-    private void rotateSeekBars(int to, int rotation) {
-        SeekBar seekbar = mChild.findViewById(R.id.volume_row_slider);
+    private void rotateSeekBars(View row, int to, int rotation) {
+        SeekBar seekbar = row.findViewById(R.id.volume_row_slider);
         if (seekbar != null) {
             seekbar.setRotation((float) rotation);
         }
 
-        View parent = mChild.findViewById(R.id.volume_row_slider_frame);
+        View parent = row.findViewById(R.id.volume_row_slider_frame);
         swapDimens(parent);
         ViewGroup.LayoutParams params = seekbar.getLayoutParams();
         ViewGroup.LayoutParams parentParams = parent.getLayoutParams();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/chooser/ChooserHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/chooser/ChooserHelperTest.java
new file mode 100644
index 0000000..8e0426a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/chooser/ChooserHelperTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2016 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.systemui.chooser;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Intent;
+import android.os.Binder;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import com.android.systemui.chooser.ChooserHelper;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyFloat;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ChooserHelperTest extends SysuiTestCase {
+
+    @Test
+    public void testOnChoose_CallsStartActivityAsCallerWithToken() {
+        final Intent intent = new Intent();
+        final Binder token = new Binder();
+        intent.putExtra(ActivityManager.EXTRA_PERMISSION_TOKEN, token);
+
+        final Activity mockActivity = mock(Activity.class);
+        when(mockActivity.getIntent()).thenReturn(intent);
+
+        ChooserHelper.onChoose(mockActivity);
+        verify(mockActivity, times(1)).startActivityAsCaller(
+                any(), any(), eq(token), anyBoolean(), anyInt());
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BlockingQueueIntentReceiver.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BlockingQueueIntentReceiver.java
new file mode 100644
index 0000000..76a3c95
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BlockingQueueIntentReceiver.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/** A simple receiver to wait for broadcast intents in tests. */
+public class BlockingQueueIntentReceiver extends BroadcastReceiver {
+    private final BlockingQueue<Intent> mQueue = new ArrayBlockingQueue<Intent>(1);
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        mQueue.add(intent);
+    }
+
+    public Intent waitForIntent() throws InterruptedException {
+        return mQueue.poll(10, TimeUnit.SECONDS);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
new file mode 100644
index 0000000..63920a4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import static junit.framework.Assert.assertEquals;
+
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ShortcutManager;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.widget.EditText;
+import android.widget.ImageButton;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationTestHelper;
+import com.android.systemui.statusbar.RemoteInputController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class RemoteInputViewTest extends SysuiTestCase {
+
+    private static final String TEST_RESULT_KEY = "test_result_key";
+    private static final String TEST_REPLY = "hello";
+    private static final String TEST_ACTION = "com.android.ACTION";
+
+    @Mock private RemoteInputController mController;
+    @Mock private ShortcutManager mShortcutManager;
+    private BlockingQueueIntentReceiver mReceiver;
+    private RemoteInputView mView;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mReceiver = new BlockingQueueIntentReceiver();
+        mContext.registerReceiver(mReceiver, new IntentFilter(TEST_ACTION));
+
+        // Avoid SecurityException RemoteInputView#sendRemoteInput().
+        mContext.addMockSystemService(ShortcutManager.class, mShortcutManager);
+
+        ExpandableNotificationRow row = new NotificationTestHelper(mContext).createRow();
+        mView = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
+    }
+
+    @Test
+    public void testSendRemoteInput_intentContainsResultsAndSource() throws InterruptedException {
+        PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
+                new Intent(TEST_ACTION), 0);
+        RemoteInput input = new RemoteInput.Builder(TEST_RESULT_KEY).build();
+
+        mView.setPendingIntent(pendingIntent);
+        mView.setRemoteInput(new RemoteInput[]{input}, input);
+        mView.focus();
+
+        EditText editText = mView.findViewById(R.id.remote_input_text);
+        editText.setText(TEST_REPLY);
+        ImageButton sendButton = mView.findViewById(R.id.remote_input_send);
+        sendButton.performClick();
+
+        Intent resultIntent = mReceiver.waitForIntent();
+        assertEquals(TEST_REPLY,
+                RemoteInput.getResultsFromIntent(resultIntent).get(TEST_RESULT_KEY));
+        assertEquals(RemoteInput.SOURCE_FREE_FORM_INPUT,
+                RemoteInput.getResultsSource(resultIntent));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
new file mode 100644
index 0000000..0c3637d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import static junit.framework.Assert.assertEquals;
+
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class SmartReplyViewTest extends SysuiTestCase {
+
+    private static final String TEST_RESULT_KEY = "test_result_key";
+    private static final String TEST_ACTION = "com.android.ACTION";
+    private static final String[] TEST_CHOICES = new String[]{"Hello", "What's up?", "I'm here"};
+
+    private BlockingQueueIntentReceiver mReceiver;
+    private SmartReplyView mView;
+
+    @Before
+    public void setUp() {
+        mReceiver = new BlockingQueueIntentReceiver();
+        mContext.registerReceiver(mReceiver, new IntentFilter(TEST_ACTION));
+
+        mView = SmartReplyView.inflate(mContext, null);
+    }
+
+    @Test
+    public void testSendSmartReply_intentContainsResultsAndSource() throws InterruptedException {
+        PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
+                new Intent(TEST_ACTION), 0);
+        RemoteInput input = new RemoteInput.Builder(TEST_RESULT_KEY).setChoices(
+                TEST_CHOICES).build();
+
+        mView.setRepliesFromRemoteInput(input, pendingIntent);
+
+        mView.getChildAt(2).performClick();
+
+        Intent resultIntent = mReceiver.waitForIntent();
+        assertEquals(TEST_CHOICES[2],
+                RemoteInput.getResultsFromIntent(resultIntent).get(TEST_RESULT_KEY));
+        assertEquals(RemoteInput.SOURCE_CHOICE, RemoteInput.getResultsSource(resultIntent));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java
index 537d606..de99d71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java
@@ -38,6 +38,7 @@
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.statusbar.policy.BluetoothController;
 
 import org.junit.After;
@@ -54,6 +55,8 @@
     OutputChooserDialog mDialog;
 
     @Mock
+    private VolumeDialogController mVolumeController;
+    @Mock
     private BluetoothController mController;
     @Mock
     private WifiManager mWifiManager;
@@ -69,6 +72,7 @@
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
 
+        mVolumeController = mDependency.injectMockDependency(VolumeDialogController.class);
         mController = mDependency.injectMockDependency(BluetoothController.class);
         when(mWifiManager.isWifiEnabled()).thenReturn(true);
 
@@ -78,20 +82,10 @@
         mDialog = new OutputChooserDialog(getContext(), mRouter);
     }
 
-    @After
-    @UiThreadTest
-    public void tearDown() throws Exception {
-        mDialog.dismiss();
-    }
-
-    private void showDialog() {
-        mDialog.show();
-    }
-
     @Test
     @UiThreadTest
     public void testClickMediaRouterItemConnectsMedia() {
-        showDialog();
+        mDialog.show();
 
         OutputChooserLayout.Item item = new OutputChooserLayout.Item();
         item.deviceType = OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER;
@@ -102,12 +96,13 @@
         mDialog.onDetailItemClick(item);
         verify(info, times(1)).select();
         verify(mController, never()).connect(any());
+        mDialog.dismiss();
     }
 
     @Test
     @UiThreadTest
     public void testClickBtItemConnectsBt() {
-        showDialog();
+        mDialog.show();
 
         OutputChooserLayout.Item item = new OutputChooserLayout.Item();
         item.deviceType = OutputChooserLayout.Item.DEVICE_TYPE_BT;
@@ -117,25 +112,28 @@
 
         mDialog.onDetailItemClick(item);
         verify(mController, times(1)).connect(any());
+        mDialog.dismiss();
     }
 
     @Test
     @UiThreadTest
     public void testTitleNotInCall() {
-        showDialog();
+        mDialog.show();
 
         assertTrue(((TextView) mDialog.findViewById(R.id.title))
                 .getText().toString().contains("Media"));
+        mDialog.dismiss();
     }
 
     @Test
     @UiThreadTest
     public void testTitleInCall() {
         mDialog.setIsInCall(true);
-        showDialog();
+        mDialog.show();
 
         assertTrue(((TextView) mDialog.findViewById(R.id.title))
                 .getText().toString().contains("Phone"));
+        mDialog.dismiss();
     }
 
     @Test
@@ -155,4 +153,26 @@
 
         verify(mRouter, times(1)).addCallback(any(), any(), anyInt());
     }
+
+    @Test
+    @UiThreadTest
+    public void testRegisterCallbacks() {
+        mDialog.setIsInCall(false);
+        mDialog.onAttachedToWindow();
+
+        verify(mRouter, times(1)).addCallback(any(), any(), anyInt());
+        verify(mController, times(1)).addCallback(any());
+        verify(mVolumeController, times(1)).addCallback(any(), any());
+    }
+
+    @Test
+    @UiThreadTest
+    public void testUnregisterCallbacks() {
+        mDialog.setIsInCall(false);
+        mDialog.onDetachedFromWindow();
+
+        verify(mRouter, times(1)).removeCallback(any());
+        verify(mController, times(1)).removeCallback(any());
+        verify(mVolumeController, times(1)).removeCallback(any());
+    }
 }
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 4144bbd..bfec88c 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -4685,7 +4685,8 @@
     // OS: O MR
     AUTOFILL_SERVICE_DISABLED_SELF = 1135;
 
-    // Counter showing how long it took (in ms) to show the autofill UI after a field was focused
+    // Reports how long it took to show the autofill UI after a field was focused
+    // Tag FIELD_AUTOFILL_DURATION: Duration in ms
     // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
     // Package: Package of the autofill service
     // OS: O MR
@@ -4724,6 +4725,9 @@
     // logged when we cancel an app transition.
     APP_TRANSITION_CANCELLED = 1144;
 
+    // Tag of a field representing a duration on autofill-related metrics.
+    FIELD_AUTOFILL_DURATION = 1145;
+
     // ---- End O-MR1 Constants, all O-MR1 constants go above this line ----
 
     // OPEN: Settings > Network & Internet > Mobile network
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index e1fb3a7..6b44fa5 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1860,7 +1860,7 @@
                 mUiLatencyHistory.log(historyLog.toString());
 
                 final LogMaker metricsLog = newLogMaker(MetricsEvent.AUTOFILL_UI_LATENCY)
-                        .setCounterValue((int) duration);
+                        .addTaggedData(MetricsEvent.FIELD_AUTOFILL_DURATION, duration);
                 mMetricsLogger.write(metricsLog);
             }
         }
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index c2adbfa..fe4ac6d7 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -65,7 +65,6 @@
 import java.net.InetSocketAddress;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
-import java.util.BitSet;
 import java.util.List;
 
 import libcore.io.IoUtils;
@@ -596,6 +595,10 @@
             return mSpi;
         }
 
+        public EncapSocketRecord getSocketRecord() {
+            return mSocket;
+        }
+
         /** always guarded by IpSecService#this */
         @Override
         public void freeUnderlyingResources() {
@@ -806,9 +809,29 @@
         /** always guarded by IpSecService#this */
         @Override
         public void freeUnderlyingResources() {
-            // TODO: Add calls to netd
+            // Calls to netd
             //       Teardown VTI
             //       Delete global policies
+            try {
+                mSrvConfig.getNetdInstance().removeVirtualTunnelInterface(mInterfaceName);
+
+                for (int direction : DIRECTIONS) {
+                    int mark = (direction == IpSecManager.DIRECTION_IN) ? mIkey : mOkey;
+                    mSrvConfig
+                            .getNetdInstance()
+                            .ipSecDeleteSecurityPolicy(
+                                    0, direction, mLocalAddress, mRemoteAddress, mark, 0xffffffff);
+                }
+            } catch (ServiceSpecificException e) {
+                // FIXME: get the error code and throw is at an IOException from Errno Exception
+            } catch (RemoteException e) {
+                Log.e(
+                        TAG,
+                        "Failed to delete VTI with interface name: "
+                                + mInterfaceName
+                                + " and id: "
+                                + mResourceId);
+            }
 
             getResourceTracker().give();
             releaseNetId(mIkey);
@@ -1229,24 +1252,57 @@
         final int okey = reserveNetId();
         String intfName = String.format("%s%d", TUNNEL_INTERFACE_PREFIX, resourceId);
 
-        // TODO: Add calls to netd:
-        //       Create VTI
-        //       Add inbound/outbound global policies
-        //              (use reqid = 0)
+        try {
+            // Calls to netd:
+            //       Create VTI
+            //       Add inbound/outbound global policies
+            //              (use reqid = 0)
+            mSrvConfig
+                    .getNetdInstance()
+                    .addVirtualTunnelInterface(intfName, localAddr, remoteAddr, ikey, okey);
 
-        userRecord.mTunnelInterfaceRecords.put(
-                resourceId,
-                new RefcountedResource<TunnelInterfaceRecord>(
-                        new TunnelInterfaceRecord(
-                                resourceId,
-                                intfName,
-                                underlyingNetwork,
-                                localAddr,
-                                remoteAddr,
-                                ikey,
-                                okey),
-                        binder));
-        return new IpSecTunnelInterfaceResponse(IpSecManager.Status.OK, resourceId, intfName);
+            for (int direction : DIRECTIONS) {
+                int mark = (direction == IpSecManager.DIRECTION_OUT) ? okey : ikey;
+
+                mSrvConfig
+                        .getNetdInstance()
+                        .ipSecAddSecurityPolicy(
+                                0, // Use 0 for reqId
+                                direction,
+                                "",
+                                "",
+                                0,
+                                mark,
+                                0xffffffff);
+            }
+
+            userRecord.mTunnelInterfaceRecords.put(
+                    resourceId,
+                    new RefcountedResource<TunnelInterfaceRecord>(
+                            new TunnelInterfaceRecord(
+                                    resourceId,
+                                    intfName,
+                                    underlyingNetwork,
+                                    localAddr,
+                                    remoteAddr,
+                                    ikey,
+                                    okey),
+                            binder));
+            return new IpSecTunnelInterfaceResponse(IpSecManager.Status.OK, resourceId, intfName);
+        } catch (RemoteException e) {
+            // Release keys if we got an error.
+            releaseNetId(ikey);
+            releaseNetId(okey);
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            // FIXME: get the error code and throw is at an IOException from Errno Exception
+        }
+
+        // If we make it to here, then something has gone wrong and we couldn't create a VTI.
+        // Release the keys that we reserved, and return an error status.
+        releaseNetId(ikey);
+        releaseNetId(okey);
+        return new IpSecTunnelInterfaceResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
     }
 
     /**
@@ -1381,12 +1437,50 @@
         }
     }
 
+    private void createOrUpdateTransform(
+            IpSecConfig c, int resourceId, SpiRecord spiRecord, EncapSocketRecord socketRecord)
+            throws RemoteException {
+
+        int encapType = c.getEncapType(), encapLocalPort = 0, encapRemotePort = 0;
+        if (encapType != IpSecTransform.ENCAP_NONE) {
+            encapLocalPort = socketRecord.getPort();
+            encapRemotePort = c.getEncapRemotePort();
+        }
+
+        IpSecAlgorithm auth = c.getAuthentication();
+        IpSecAlgorithm crypt = c.getEncryption();
+        IpSecAlgorithm authCrypt = c.getAuthenticatedEncryption();
+
+        mSrvConfig
+                .getNetdInstance()
+                .ipSecAddSecurityAssociation(
+                        resourceId,
+                        c.getMode(),
+                        c.getSourceAddress(),
+                        c.getDestinationAddress(),
+                        (c.getNetwork() != null) ? c.getNetwork().netId : 0,
+                        spiRecord.getSpi(),
+                        c.getMarkValue(),
+                        c.getMarkMask(),
+                        (auth != null) ? auth.getName() : "",
+                        (auth != null) ? auth.getKey() : new byte[] {},
+                        (auth != null) ? auth.getTruncationLengthBits() : 0,
+                        (crypt != null) ? crypt.getName() : "",
+                        (crypt != null) ? crypt.getKey() : new byte[] {},
+                        (crypt != null) ? crypt.getTruncationLengthBits() : 0,
+                        (authCrypt != null) ? authCrypt.getName() : "",
+                        (authCrypt != null) ? authCrypt.getKey() : new byte[] {},
+                        (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0,
+                        encapType,
+                        encapLocalPort,
+                        encapRemotePort);
+    }
+
     /**
-     * Create a transport mode transform, which represent two security associations (one in each
-     * direction) in the kernel. The transform will be cached by the system server and must be freed
-     * when no longer needed. It is possible to free one, deleting the SA from underneath sockets
-     * that are using it, which will result in all of those sockets becoming unable to send or
-     * receive data.
+     * Create a IPsec transform, which represents a single security association in the kernel. The
+     * transform will be cached by the system server and must be freed when no longer needed. It is
+     * possible to free one, deleting the SA from underneath sockets that are using it, which will
+     * result in all of those sockets becoming unable to send or receive data.
      */
     @Override
     public synchronized IpSecTransformResponse createTransform(IpSecConfig c, IBinder binder)
@@ -1402,58 +1496,28 @@
             return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
         }
 
-        int encapType, encapLocalPort = 0, encapRemotePort = 0;
         EncapSocketRecord socketRecord = null;
-        encapType = c.getEncapType();
-        if (encapType != IpSecTransform.ENCAP_NONE) {
+        if (c.getEncapType() != IpSecTransform.ENCAP_NONE) {
             RefcountedResource<EncapSocketRecord> refcountedSocketRecord =
                     userRecord.mEncapSocketRecords.getRefcountedResourceOrThrow(
                             c.getEncapSocketResourceId());
             dependencies.add(refcountedSocketRecord);
-
             socketRecord = refcountedSocketRecord.getResource();
-            encapLocalPort = socketRecord.getPort();
-            encapRemotePort = c.getEncapRemotePort();
         }
 
-        IpSecAlgorithm auth = c.getAuthentication();
-        IpSecAlgorithm crypt = c.getEncryption();
-        IpSecAlgorithm authCrypt = c.getAuthenticatedEncryption();
-
         RefcountedResource<SpiRecord> refcountedSpiRecord =
                 userRecord.mSpiRecords.getRefcountedResourceOrThrow(c.getSpiResourceId());
         dependencies.add(refcountedSpiRecord);
         SpiRecord spiRecord = refcountedSpiRecord.getResource();
 
         try {
-            mSrvConfig
-                    .getNetdInstance()
-                    .ipSecAddSecurityAssociation(
-                            resourceId,
-                            c.getMode(),
-                            c.getSourceAddress(),
-                            c.getDestinationAddress(),
-                            (c.getNetwork() != null) ? c.getNetwork().netId : 0,
-                            spiRecord.getSpi(),
-                            c.getMarkValue(),
-                            c.getMarkMask(),
-                            (auth != null) ? auth.getName() : "",
-                            (auth != null) ? auth.getKey() : new byte[] {},
-                            (auth != null) ? auth.getTruncationLengthBits() : 0,
-                            (crypt != null) ? crypt.getName() : "",
-                            (crypt != null) ? crypt.getKey() : new byte[] {},
-                            (crypt != null) ? crypt.getTruncationLengthBits() : 0,
-                            (authCrypt != null) ? authCrypt.getName() : "",
-                            (authCrypt != null) ? authCrypt.getKey() : new byte[] {},
-                            (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0,
-                            encapType,
-                            encapLocalPort,
-                            encapRemotePort);
+            createOrUpdateTransform(c, resourceId, spiRecord, socketRecord);
         } catch (ServiceSpecificException e) {
             // FIXME: get the error code and throw is at an IOException from Errno Exception
             return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
         }
-        // Both SAs were created successfully, time to construct a record and lock it away
+
+        // SA was created successfully, time to construct a record and lock it away
         userRecord.mTransformRecords.put(
                 resourceId,
                 new RefcountedResource<TransformRecord>(
@@ -1561,14 +1625,48 @@
                 c.getMode() == IpSecTransform.MODE_TUNNEL,
                 "Transform mode was not Tunnel mode; cannot be applied to a tunnel interface");
 
+        EncapSocketRecord socketRecord = null;
+        if (c.getEncapType() != IpSecTransform.ENCAP_NONE) {
+            socketRecord =
+                    userRecord.mEncapSocketRecords.getResourceOrThrow(c.getEncapSocketResourceId());
+        }
+        SpiRecord spiRecord = userRecord.mSpiRecords.getResourceOrThrow(c.getSpiResourceId());
+
         int mark =
                 (direction == IpSecManager.DIRECTION_IN)
                         ? tunnelInterfaceInfo.getIkey()
                         : tunnelInterfaceInfo.getOkey();
 
-        // TODO: Add calls to netd:
-        //       Update SA with tunnel mark (ikey or okey based on direction)
-        //       If outbound, add SPI to policy
+        try {
+            c.setMarkValue(mark);
+            c.setMarkMask(0xffffffff);
+
+            if (direction == IpSecManager.DIRECTION_OUT) {
+                // Set output mark via underlying network (output only)
+                c.setNetwork(tunnelInterfaceInfo.getUnderlyingNetwork());
+
+                // If outbound, also add SPI to the policy.
+                mSrvConfig
+                        .getNetdInstance()
+                        .ipSecUpdateSecurityPolicy(
+                                0, // Use 0 for reqId
+                                direction,
+                                "",
+                                "",
+                                transformInfo.getSpiRecord().getSpi(),
+                                mark,
+                                0xffffffff);
+            }
+
+            // Update SA with tunnel mark (ikey or okey based on direction)
+            createOrUpdateTransform(c, transformResourceId, spiRecord, socketRecord);
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode == EINVAL) {
+                throw new IllegalArgumentException(e.toString());
+            } else {
+                throw e;
+            }
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 2f7d4c1..266abf8 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1042,20 +1042,14 @@
                         throw new SecurityException("Instant app " + r.appInfo.packageName
                                 + " does not have permission to create foreground services");
                     default:
-                        try {
-                            if (AppGlobals.getPackageManager().checkPermission(
-                                    android.Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE,
-                                    r.appInfo.packageName, UserHandle.getUserId(r.appInfo.uid))
-                                            != PackageManager.PERMISSION_GRANTED) {
-                                throw new SecurityException("Instant app " + r.appInfo.packageName
-                                        + " does not have permission to create foreground"
-                                        + "services");
-                            }
-                        } catch (RemoteException e) {
-                            throw new SecurityException("Failed to check instant app permission." ,
-                                    e);
-                        }
+                        mAm.enforcePermission(
+                                android.Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE,
+                                r.app.pid, r.appInfo.uid, "startForeground");
                 }
+            } else if (r.appInfo.targetSdkVersion >= Build.VERSION_CODES.P) {
+                mAm.enforcePermission(
+                        android.Manifest.permission.FOREGROUND_SERVICE,
+                        r.app.pid, r.appInfo.uid, "startForeground");
             }
             if (r.fgRequired) {
                 if (DEBUG_SERVICE || DEBUG_BACKGROUND_CHECK) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5eb5b14..8397615 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -26,6 +26,7 @@
 import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
 import static android.Manifest.permission.READ_FRAME_BUFFER;
 import static android.Manifest.permission.REMOVE_TASKS;
+import static android.Manifest.permission.START_ACTIVITY_AS_CALLER;
 import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
 import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_WINDOW;
@@ -564,6 +565,23 @@
     // could take much longer than usual.
     static final int PROC_START_TIMEOUT_WITH_WRAPPER = 1200*1000;
 
+    // Permission tokens are used to temporarily granted a trusted app the ability to call
+    // #startActivityAsCaller.  A client is expected to dump its token after this time has elapsed,
+    // showing any appropriate error messages to the user.
+    private static final long START_AS_CALLER_TOKEN_TIMEOUT =
+            10 * DateUtils.MINUTE_IN_MILLIS;
+
+    // How long before the service actually expires a token.  This is slightly longer than
+    // START_AS_CALLER_TOKEN_TIMEOUT, to provide a buffer so clients will rarely encounter the
+    // expiration exception.
+    private static final long START_AS_CALLER_TOKEN_TIMEOUT_IMPL =
+            START_AS_CALLER_TOKEN_TIMEOUT + 2*1000;
+
+    // How long the service will remember expired tokens, for the purpose of providing error
+    // messaging when a client uses an expired token.
+    private static final long START_AS_CALLER_TOKEN_EXPIRED_TIMEOUT =
+            START_AS_CALLER_TOKEN_TIMEOUT_IMPL + 20 * DateUtils.MINUTE_IN_MILLIS;
+
     // How long we allow a receiver to run before giving up on it.
     static final int BROADCAST_FG_TIMEOUT = 10*1000;
     static final int BROADCAST_BG_TIMEOUT = 60*1000;
@@ -672,6 +690,13 @@
 
     final ArrayList<ActiveInstrumentation> mActiveInstrumentation = new ArrayList<>();
 
+    // Activity tokens of system activities that are delegating their call to
+    // #startActivityByCaller, keyed by the permissionToken granted to the delegate.
+    final HashMap<IBinder, IBinder> mStartActivitySources = new HashMap<>();
+
+    // Permission tokens that have expired, but we remember for error reporting.
+    final ArrayList<IBinder> mExpiredStartAsCallerTokens = new ArrayList<>();
+
     public final IntentFirewall mIntentFirewall;
 
     // Whether we should show our dialogs (ANR, crash, etc) or just perform their
@@ -1842,6 +1867,8 @@
     static final int PUSH_TEMP_WHITELIST_UI_MSG = 68;
     static final int SERVICE_FOREGROUND_CRASH_MSG = 69;
     static final int DISPATCH_OOM_ADJ_OBSERVER_MSG = 70;
+    static final int EXPIRE_START_AS_CALLER_TOKEN_MSG = 75;
+    static final int FORGET_START_AS_CALLER_TOKEN_MSG = 76;
 
     static final int FIRST_ACTIVITY_STACK_MSG = 100;
     static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -2506,6 +2533,19 @@
                     }
                 }
             } break;
+            case EXPIRE_START_AS_CALLER_TOKEN_MSG: {
+                synchronized (ActivityManagerService.this) {
+                    final IBinder permissionToken = (IBinder)msg.obj;
+                    mStartActivitySources.remove(permissionToken);
+                    mExpiredStartAsCallerTokens.add(permissionToken);
+                }
+            } break;
+            case FORGET_START_AS_CALLER_TOKEN_MSG: {
+                synchronized (ActivityManagerService.this) {
+                    final IBinder permissionToken = (IBinder)msg.obj;
+                    mExpiredStartAsCallerTokens.remove(permissionToken);
+                }
+            } break;
             }
         }
     };
@@ -4774,16 +4814,54 @@
 
     }
 
+    /**
+     * Only callable from the system. This token grants a temporary permission to call
+     * #startActivityAsCallerWithToken. The token will time out after
+     * START_AS_CALLER_TOKEN_TIMEOUT if it is not used.
+     *
+     * @param delegatorToken The Binder token referencing the system Activity that wants to delegate
+     *        the #startActivityAsCaller to another app. The "caller" will be the caller of this
+     *        activity's token, not the delegate's caller (which is probably the delegator itself).
+     *
+     * @return Returns a token that can be given to a "delegate" app that may call
+     *         #startActivityAsCaller
+     */
     @Override
-    public final int startActivityAsCaller(IApplicationThread caller, String callingPackage,
-            Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
-            int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, boolean ignoreTargetSecurity,
-            int userId) {
+    public IBinder requestStartActivityPermissionToken(IBinder delegatorToken) {
+        int callingUid = Binder.getCallingUid();
+        if (UserHandle.getAppId(callingUid) != SYSTEM_UID) {
+            throw new SecurityException("Only the system process can request a permission token, " +
+                    "received request from uid: " + callingUid);
+        }
+        IBinder permissionToken = new Binder();
+        synchronized (this) {
+            mStartActivitySources.put(permissionToken, delegatorToken);
+        }
 
+        Message expireMsg = mHandler.obtainMessage(EXPIRE_START_AS_CALLER_TOKEN_MSG,
+                permissionToken);
+        mHandler.sendMessageDelayed(expireMsg, START_AS_CALLER_TOKEN_TIMEOUT_IMPL);
+
+        Message forgetMsg = mHandler.obtainMessage(FORGET_START_AS_CALLER_TOKEN_MSG,
+                permissionToken);
+        mHandler.sendMessageDelayed(forgetMsg, START_AS_CALLER_TOKEN_EXPIRED_TIMEOUT);
+
+        return permissionToken;
+    }
+
+    @Override
+    public final int startActivityAsCaller(IApplicationThread caller,
+            String callingPackage, Intent intent, String resolvedType, IBinder resultTo,
+            String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo,
+            Bundle bOptions, IBinder permissionToken, boolean ignoreTargetSecurity, int userId) {
         // This is very dangerous -- it allows you to perform a start activity (including
-        // permission grants) as any app that may launch one of your own activities.  So
-        // we will only allow this to be done from activities that are part of the core framework,
-        // and then only when they are running as the system.
+        // permission grants) as any app that may launch one of your own activities.  So we only
+        // allow this in two cases:
+        // 1)  The caller is an activity that is part of the core framework, and then only when it
+        //     is running as the system.
+        // 2)  The caller provides a valid permissionToken.  Permission tokens are one-time use and
+        //     can only be requested by a system activity, which may then delegate this call to
+        //     another app.
         final ActivityRecord sourceRecord;
         final int targetUid;
         final String targetPackage;
@@ -4791,17 +4869,47 @@
             if (resultTo == null) {
                 throw new SecurityException("Must be called from an activity");
             }
-            sourceRecord = mStackSupervisor.isInAnyStackLocked(resultTo);
-            if (sourceRecord == null) {
-                throw new SecurityException("Called with bad activity token: " + resultTo);
+
+            final IBinder sourceToken;
+            if (permissionToken != null) {
+                // To even attempt to use a permissionToken, an app must also have this signature
+                // permission.
+                enforceCallingPermission(android.Manifest.permission.START_ACTIVITY_AS_CALLER,
+                        "startActivityAsCaller");
+                // If called with a permissionToken, we want the sourceRecord from the delegator
+                // activity that requested this token.
+                sourceToken =
+                        mStartActivitySources.remove(permissionToken);
+                if (sourceToken == null) {
+                    // Invalid permissionToken, check if it recently expired.
+                    if (mExpiredStartAsCallerTokens.contains(permissionToken)) {
+                        throw new SecurityException("Called with expired permission token: "
+                                + permissionToken);
+                    } else {
+                        throw new SecurityException("Called with invalid permission token: "
+                                + permissionToken);
+                    }
+                }
+            } else {
+                // This method was called directly by the source.
+                sourceToken = resultTo;
             }
-            if (!sourceRecord.info.packageName.equals("android")) {
-                throw new SecurityException(
-                        "Must be called from an activity that is declared in the android package");
+
+            sourceRecord = mStackSupervisor.isInAnyStackLocked(sourceToken);
+            if (sourceRecord == null) {
+                throw new SecurityException("Called with bad activity token: " + sourceToken);
             }
             if (sourceRecord.app == null) {
                 throw new SecurityException("Called without a process attached to activity");
             }
+
+            // Whether called directly or from a delegate, the source activity must be from the
+            // android package.
+            if (!sourceRecord.info.packageName.equals("android")) {
+                throw new SecurityException("Must be called from an activity that is " +
+                        "declared in the android package");
+            }
+
             if (UserHandle.getAppId(sourceRecord.app.uid) != SYSTEM_UID) {
                 // This is still okay, as long as this activity is running under the
                 // uid of the original calling activity.
@@ -4812,6 +4920,7 @@
                                     + sourceRecord.launchedFromUid);
                 }
             }
+
             if (ignoreTargetSecurity) {
                 if (intent.getComponent() == null) {
                     throw new SecurityException(
@@ -8775,6 +8884,20 @@
     /**
      * This can be called with or without the global lock held.
      */
+    void enforcePermission(String permission, int pid, int uid, String func) {
+        if (checkPermission(permission, pid, uid) == PackageManager.PERMISSION_GRANTED) {
+            return;
+        }
+
+        String msg = "Permission Denial: " + func + " from pid=" + pid + ", uid=" + uid
+                + " requires " + permission;
+        Slog.w(TAG, msg);
+        throw new SecurityException(msg);
+    }
+
+    /**
+     * This can be called with or without the global lock held.
+     */
     void enforceCallerIsRecentsOrHasPermission(String permission, String func) {
         if (!mRecentTasks.isCallerRecents(Binder.getCallingUid())) {
             enforceCallingPermission(permission, func);
diff --git a/services/core/java/com/android/server/am/ActivityStartController.java b/services/core/java/com/android/server/am/ActivityStartController.java
index f9932b2..5551914 100644
--- a/services/core/java/com/android/server/am/ActivityStartController.java
+++ b/services/core/java/com/android/server/am/ActivityStartController.java
@@ -220,7 +220,7 @@
         }
     }
 
-    final int startActivityInPackage(int uid, int realCallingUid, int realCallingPid,
+    final int startActivityInPackage(int uid, int realCallingPid, int realCallingUid,
             String callingPackage, Intent intent, String resolvedType, IBinder resultTo,
             String resultWho, int requestCode, int startFlags, SafeActivityOptions options,
             int userId, TaskRecord inTask, String reason) {
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 8595aa3..4dc30dd 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -1252,7 +1252,7 @@
                     outActivity[0] = reusedActivity;
                 }
 
-                return START_DELIVERED_TO_TOP;
+                return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;
             }
         }
 
diff --git a/services/core/java/com/android/server/am/AppErrorDialog.java b/services/core/java/com/android/server/am/AppErrorDialog.java
index 5412266..68c63a2 100644
--- a/services/core/java/com/android/server/am/AppErrorDialog.java
+++ b/services/core/java/com/android/server/am/AppErrorDialog.java
@@ -38,9 +38,7 @@
     private final ActivityManagerService mService;
     private final AppErrorResult mResult;
     private final ProcessRecord mProc;
-    private final boolean mRepeating;
     private final boolean mIsRestartable;
-    private CharSequence mName;
 
     static int CANT_SHOW = -1;
     static int BACKGROUND_USER = -2;
@@ -53,6 +51,7 @@
     static final int MUTE = 5;
     static final int TIMEOUT = 6;
     static final int CANCEL = 7;
+    static final int APP_INFO = 8;
 
     // 5-minute timeout, then we automatically dismiss the crash dialog
     static final long DISMISS_TIMEOUT = 1000 * 60 * 5;
@@ -64,23 +63,25 @@
         mService = service;
         mProc = data.proc;
         mResult = data.result;
-        mRepeating = data.repeating;
-        mIsRestartable = data.task != null || data.isRestartableForService;
+        mIsRestartable = (data.task != null || data.isRestartableForService)
+                && Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.SHOW_RESTART_IN_CRASH_DIALOG, 0) != 0;
         BidiFormatter bidi = BidiFormatter.getInstance();
 
+        CharSequence name;
         if ((mProc.pkgList.size() == 1) &&
-                (mName = context.getPackageManager().getApplicationLabel(mProc.info)) != null) {
+                (name = context.getPackageManager().getApplicationLabel(mProc.info)) != null) {
             setTitle(res.getString(
-                    mRepeating ? com.android.internal.R.string.aerr_application_repeated
+                    data.repeating ? com.android.internal.R.string.aerr_application_repeated
                             : com.android.internal.R.string.aerr_application,
-                    bidi.unicodeWrap(mName.toString()),
+                    bidi.unicodeWrap(name.toString()),
                     bidi.unicodeWrap(mProc.info.processName)));
         } else {
-            mName = mProc.processName;
+            name = mProc.processName;
             setTitle(res.getString(
-                    mRepeating ? com.android.internal.R.string.aerr_process_repeated
+                    data.repeating ? com.android.internal.R.string.aerr_process_repeated
                             : com.android.internal.R.string.aerr_process,
-                    bidi.unicodeWrap(mName.toString())));
+                    bidi.unicodeWrap(name.toString())));
         }
 
         setCancelable(true);
@@ -118,11 +119,14 @@
         report.setOnClickListener(this);
         report.setVisibility(hasReceiver ? View.VISIBLE : View.GONE);
         final TextView close = findViewById(com.android.internal.R.id.aerr_close);
-        close.setVisibility(mRepeating ? View.VISIBLE : View.GONE);
         close.setOnClickListener(this);
+        final TextView appInfo = findViewById(com.android.internal.R.id.aerr_app_info);
+        appInfo.setOnClickListener(this);
 
         boolean showMute = !Build.IS_USER && Settings.Global.getInt(context.getContentResolver(),
-                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
+                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0
+                && Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.SHOW_MUTE_IN_CRASH_DIALOG, 0) != 0;
         final TextView mute = findViewById(com.android.internal.R.id.aerr_mute);
         mute.setOnClickListener(this);
         mute.setVisibility(showMute ? View.VISIBLE : View.GONE);
@@ -183,6 +187,9 @@
             case com.android.internal.R.id.aerr_close:
                 mHandler.obtainMessage(FORCE_QUIT).sendToTarget();
                 break;
+            case com.android.internal.R.id.aerr_app_info:
+                mHandler.obtainMessage(APP_INFO).sendToTarget();
+                break;
             case com.android.internal.R.id.aerr_mute:
                 mHandler.obtainMessage(MUTE).sendToTarget();
                 break;
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index c7d93be..9776c4d 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -34,6 +34,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
+import android.net.Uri;
 import android.os.Binder;
 import android.os.Message;
 import android.os.Process;
@@ -500,6 +501,11 @@
                     Binder.restoreCallingIdentity(orig);
                 }
             }
+            if (res == AppErrorDialog.APP_INFO) {
+                appErrorIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+                appErrorIntent.setData(Uri.parse("package:" + r.info.packageName));
+                appErrorIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            }
             if (res == AppErrorDialog.FORCE_QUIT_AND_REPORT) {
                 appErrorIntent = createAppErrorIntentLocked(r, timeMillis, crashInfo);
             }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index f4c99f5..bedf0431 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -138,7 +138,6 @@
 import java.io.PrintWriter;
 import java.lang.reflect.Field;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -771,7 +770,7 @@
         // Register for device connection intent broadcasts.
         IntentFilter intentFilter =
                 new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
-        intentFilter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
+        intentFilter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
         intentFilter.addAction(Intent.ACTION_DOCK_EVENT);
         intentFilter.addAction(Intent.ACTION_SCREEN_ON);
         intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
@@ -2967,14 +2966,28 @@
     }
 
     public void setBluetoothScoOnInt(boolean on, String eventSource) {
+        if (DEBUG_DEVICES) {
+            Log.d(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource);
+        }
         if (on) {
             // do not accept SCO ON if SCO audio is not connected
-            synchronized(mScoClients) {
-                if ((mBluetoothHeadset != null) &&
-                    (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
-                             != BluetoothHeadset.STATE_AUDIO_CONNECTED)) {
-                    mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
-                    return;
+            synchronized (mScoClients) {
+                if (mBluetoothHeadset != null) {
+                    if (mBluetoothHeadsetDevice == null) {
+                        BluetoothDevice activeDevice = mBluetoothHeadset.getActiveDevice();
+                        if (activeDevice != null) {
+                            // setBtScoActiveDevice() might trigger resetBluetoothSco() which
+                            // will call setBluetoothScoOnInt(false, "resetBluetoothSco")
+                            setBtScoActiveDevice(activeDevice);
+                        }
+                    }
+                    if (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
+                            != BluetoothHeadset.STATE_AUDIO_CONNECTED) {
+                        mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
+                        Log.w(TAG, "setBluetoothScoOnInt(true) failed because "
+                                + mBluetoothHeadsetDevice + " is not in audio connected mode");
+                        return;
+                    }
                 }
             }
             mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
@@ -3362,24 +3375,23 @@
         }
     }
 
-    void setBtScoDeviceConnectionState(BluetoothDevice btDevice, int state) {
+    private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) {
         if (btDevice == null) {
-            return;
+            return true;
         }
-
         String address = btDevice.getAddress();
         BluetoothClass btClass = btDevice.getBluetoothClass();
         int outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO;
         int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET;
         if (btClass != null) {
             switch (btClass.getDeviceClass()) {
-            case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
-            case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
-                outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET;
-                break;
-            case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
-                outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT;
-                break;
+                case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
+                case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
+                    outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET;
+                    break;
+                case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
+                    outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT;
+                    break;
             }
         }
 
@@ -3387,34 +3399,33 @@
             address = "";
         }
 
-        boolean connected = (state == BluetoothProfile.STATE_CONNECTED);
-
         String btDeviceName =  btDevice.getName();
-        boolean success =
-            handleDeviceConnection(connected, outDevice, address, btDeviceName) &&
-            handleDeviceConnection(connected, inDevice, address, btDeviceName);
+        boolean result = handleDeviceConnection(isActive, outDevice, address, btDeviceName);
+        // handleDeviceConnection() && result to make sure the method get executed
+        result = handleDeviceConnection(isActive, inDevice, address, btDeviceName) && result;
+        return result;
+    }
 
-        if (!success) {
-          return;
+    void setBtScoActiveDevice(BluetoothDevice btDevice) {
+        if (DEBUG_DEVICES) {
+            Log.d(TAG, "setBtScoActiveDevice(" + btDevice + ")");
         }
-
-        /* When one BT headset is disconnected while another BT headset
-         * is connected, don't mess with the headset device.
-         */
-        if ((state == BluetoothProfile.STATE_DISCONNECTED ||
-            state == BluetoothProfile.STATE_DISCONNECTING) &&
-            mBluetoothHeadset != null &&
-            mBluetoothHeadset.getAudioState(btDevice) == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
-            Log.w(TAG, "SCO connected through another device, returning");
-            return;
-        }
-
         synchronized (mScoClients) {
-            if (connected) {
+            final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice;
+            if (!Objects.equals(btDevice, previousActiveDevice)) {
+                if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) {
+                    Log.w(TAG, "setBtScoActiveDevice() failed to remove previous device "
+                            + previousActiveDevice);
+                }
+                if (!handleBtScoActiveDeviceChange(btDevice, true)) {
+                    Log.e(TAG, "setBtScoActiveDevice() failed to add new device " + btDevice);
+                    // set mBluetoothHeadsetDevice to null when failing to add new device
+                    btDevice = null;
+                }
                 mBluetoothHeadsetDevice = btDevice;
-            } else {
-                mBluetoothHeadsetDevice = null;
-                resetBluetoothSco();
+                if (mBluetoothHeadsetDevice == null) {
+                    resetBluetoothSco();
+                }
             }
         }
     }
@@ -3469,12 +3480,7 @@
                     // Discard timeout message
                     mAudioHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED);
                     mBluetoothHeadset = (BluetoothHeadset) proxy;
-                    deviceList = mBluetoothHeadset.getConnectedDevices();
-                    if (deviceList.size() > 0) {
-                        mBluetoothHeadsetDevice = deviceList.get(0);
-                    } else {
-                        mBluetoothHeadsetDevice = null;
-                    }
+                    setBtScoActiveDevice(mBluetoothHeadset.getActiveDevice());
                     // Refresh SCO audio state
                     checkScoAudioState();
                     // Continue pending action if any
@@ -3595,10 +3601,7 @@
 
     void disconnectHeadset() {
         synchronized (mScoClients) {
-            if (mBluetoothHeadsetDevice != null) {
-                setBtScoDeviceConnectionState(mBluetoothHeadsetDevice,
-                        BluetoothProfile.STATE_DISCONNECTED);
-            }
+            setBtScoActiveDevice(null);
             mBluetoothHeadset = null;
         }
     }
@@ -5788,11 +5791,9 @@
                     AudioSystem.setForceUse(AudioSystem.FOR_DOCK, config);
                 }
                 mDockState = dockState;
-            } else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
-                state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
-                                               BluetoothProfile.STATE_DISCONNECTED);
+            } else if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
                 BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-                setBtScoDeviceConnectionState(btDevice, state);
+                setBtScoActiveDevice(btDevice);
             } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
                 boolean broadcast = false;
                 int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
@@ -6634,7 +6635,19 @@
     // Inform AudioFlinger of our device's low RAM attribute
     private static void readAndSetLowRamDevice()
     {
-        int status = AudioSystem.setLowRamDevice(ActivityManager.isLowRamDeviceStatic());
+        boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
+        long totalMemory = 1024 * 1024 * 1024; // 1GB is the default if ActivityManager fails.
+
+        try {
+            final ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo();
+            ActivityManager.getService().getMemoryInfo(info);
+            totalMemory = info.totalMem;
+        } catch (RemoteException e) {
+            Log.w(TAG, "Cannot obtain MemoryInfo from ActivityManager, assume low memory device");
+            isLowRamDevice = true;
+        }
+
+        final int status = AudioSystem.setLowRamDevice(isLowRamDevice, totalMemory);
         if (status != 0) {
             Log.w(TAG, "AudioFlinger informed of device's low RAM attribute; status " + status);
         }
diff --git a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
index 10e6cad..4289a25 100644
--- a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
@@ -20,6 +20,8 @@
 import android.Manifest;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
 import android.hardware.radio.IRadioService;
 import android.hardware.radio.ITuner;
 import android.hardware.radio.ITunerCallback;
@@ -28,14 +30,18 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.internal.util.Preconditions;
 import com.android.server.SystemService;
+import com.android.server.broadcastradio.hal2.AnnouncementAggregator;
 
+import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
 import java.util.OptionalInt;
 
 public class BroadcastRadioService extends SystemService {
     private static final String TAG = "BcRadioSrv";
+    private static final boolean DEBUG = false;
 
     private final ServiceImpl mServiceImpl = new ServiceImpl();
 
@@ -88,7 +94,7 @@
         @Override
         public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig,
                 boolean withAudio, ITunerCallback callback) throws RemoteException {
-            Slog.i(TAG, "openTuner(" + moduleId + ", _, " + withAudio + ", _)");
+            if (DEBUG) Slog.i(TAG, "Opening module " + moduleId);
             enforcePolicyAccess();
             if (callback == null) {
                 throw new IllegalArgumentException("Callback must not be empty");
@@ -101,5 +107,25 @@
                 }
             }
         }
+
+        @Override
+        public ICloseHandle addAnnouncementListener(int[] enabledTypes,
+                IAnnouncementListener listener) {
+            if (DEBUG) {
+                Slog.i(TAG, "Adding announcement listener for " + Arrays.toString(enabledTypes));
+            }
+            Objects.requireNonNull(enabledTypes);
+            Objects.requireNonNull(listener);
+            enforcePolicyAccess();
+
+            synchronized (mLock) {
+                if (!mHal2.hasAnyModules()) {
+                    Slog.i(TAG, "There are no HAL 2.x modules registered");
+                    return new AnnouncementAggregator(listener);
+                }
+
+                return mHal2.addAnnouncementListener(enabledTypes, listener);
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java b/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java
new file mode 100644
index 0000000..0bbaf25
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java
@@ -0,0 +1,128 @@
+/**
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio.hal2;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.radio.Announcement;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+
+public class AnnouncementAggregator extends ICloseHandle.Stub {
+    private static final String TAG = "BcRadio2Srv.AnnAggr";
+
+    private final Object mLock = new Object();
+    @NonNull private final IAnnouncementListener mListener;
+    private final IBinder.DeathRecipient mDeathRecipient = new DeathRecipient();
+
+    @GuardedBy("mLock")
+    private final Collection<ModuleWatcher> mModuleWatchers = new ArrayList<>();
+
+    @GuardedBy("mLock")
+    private boolean mIsClosed = false;
+
+    public AnnouncementAggregator(@NonNull IAnnouncementListener listener) {
+        mListener = Objects.requireNonNull(listener);
+        try {
+            listener.asBinder().linkToDeath(mDeathRecipient, 0);
+        } catch (RemoteException ex) {
+            ex.rethrowFromSystemServer();
+        }
+    }
+
+    private class ModuleWatcher extends IAnnouncementListener.Stub {
+        private @Nullable ICloseHandle mCloseHandle;
+        public @NonNull List<Announcement> currentList = new ArrayList<>();
+
+        public void onListUpdated(List<Announcement> active) {
+            currentList = Objects.requireNonNull(active);
+            AnnouncementAggregator.this.onListUpdated();
+        }
+
+        public void setCloseHandle(@NonNull ICloseHandle closeHandle) {
+            mCloseHandle = Objects.requireNonNull(closeHandle);
+        }
+
+        public void close() throws RemoteException {
+            if (mCloseHandle != null) mCloseHandle.close();
+        }
+    }
+
+    private class DeathRecipient implements IBinder.DeathRecipient {
+        public void binderDied() {
+            try {
+                close();
+            } catch (RemoteException ex) {}
+        }
+    }
+
+    private void onListUpdated() {
+        synchronized (mLock) {
+            if (mIsClosed) {
+                Slog.e(TAG, "Announcement aggregator is closed, it shouldn't receive callbacks");
+                return;
+            }
+            List<Announcement> combined = new ArrayList<>();
+            for (ModuleWatcher watcher : mModuleWatchers) {
+                combined.addAll(watcher.currentList);
+            }
+            TunerCallback.dispatch(() -> mListener.onListUpdated(combined));
+        }
+    }
+
+    public void watchModule(@NonNull RadioModule module, @NonNull int[] enabledTypes) {
+        synchronized (mLock) {
+            if (mIsClosed) throw new IllegalStateException();
+
+            ModuleWatcher watcher = new ModuleWatcher();
+            ICloseHandle closeHandle;
+            try {
+                closeHandle = module.addAnnouncementListener(enabledTypes, watcher);
+            } catch (RemoteException ex) {
+                Slog.e(TAG, "Failed to add announcement listener", ex);
+                return;
+            }
+            watcher.setCloseHandle(closeHandle);
+            mModuleWatchers.add(watcher);
+        }
+    }
+
+    @Override
+    public void close() throws RemoteException {
+        synchronized (mLock) {
+            if (mIsClosed) return;
+            mIsClosed = true;
+
+            mListener.asBinder().unlinkToDeath(mDeathRecipient, 0);
+
+            for (ModuleWatcher watcher : mModuleWatchers) {
+                watcher.close();
+            }
+            mModuleWatchers.clear();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
index fc9a5d6..406231a 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
@@ -18,6 +18,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
 import android.hardware.radio.ITuner;
 import android.hardware.radio.ITunerCallback;
 import android.hardware.radio.RadioManager;
@@ -81,6 +83,10 @@
         return mModules.containsKey(id);
     }
 
+    public boolean hasAnyModules() {
+        return !mModules.isEmpty();
+    }
+
     public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig,
         boolean withAudio, @NonNull ITunerCallback callback) throws RemoteException {
         Objects.requireNonNull(callback);
@@ -98,4 +104,22 @@
         session.setConfiguration(legacyConfig);
         return session;
     }
+
+    public ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes,
+            @NonNull IAnnouncementListener listener) {
+        AnnouncementAggregator aggregator = new AnnouncementAggregator(listener);
+        boolean anySupported = false;
+        for (RadioModule module : mModules.values()) {
+            try {
+                aggregator.watchModule(module, enabledTypes);
+                anySupported = true;
+            } catch (UnsupportedOperationException ex) {
+                Slog.v(TAG, "Announcements not supported for this module", ex);
+            }
+        }
+        if (!anySupported) {
+            Slog.i(TAG, "There are no HAL modules that support announcements");
+        }
+        return aggregator;
+    }
 }
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
index 60a927c..7a95971 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
@@ -20,6 +20,8 @@
 import android.annotation.Nullable;
 import android.hardware.broadcastradio.V2_0.AmFmBandRange;
 import android.hardware.broadcastradio.V2_0.AmFmRegionConfig;
+import android.hardware.broadcastradio.V2_0.Announcement;
+import android.hardware.broadcastradio.V2_0.IdentifierType;
 import android.hardware.broadcastradio.V2_0.ProgramFilter;
 import android.hardware.broadcastradio.V2_0.ProgramIdentifier;
 import android.hardware.broadcastradio.V2_0.ProgramInfo;
@@ -36,6 +38,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -218,31 +221,38 @@
         return hwId;
     }
 
-    static @NonNull ProgramSelector.Identifier programIdentifierFromHal(@NonNull ProgramIdentifier id) {
+    static @Nullable ProgramSelector.Identifier programIdentifierFromHal(
+            @NonNull ProgramIdentifier id) {
+        if (id.type == IdentifierType.INVALID) return null;
         return new ProgramSelector.Identifier(id.type, id.value);
     }
 
     static @NonNull ProgramSelector programSelectorFromHal(
             @NonNull android.hardware.broadcastradio.V2_0.ProgramSelector sel) {
-        ProgramSelector.Identifier[] secondaryIds = sel.secondaryIds.stream().map(
-            id -> programIdentifierFromHal(id)).toArray(ProgramSelector.Identifier[]::new);
+        ProgramSelector.Identifier[] secondaryIds = sel.secondaryIds.stream().
+                map(id -> Objects.requireNonNull(programIdentifierFromHal(id))).
+                toArray(ProgramSelector.Identifier[]::new);
 
         return new ProgramSelector(
-            identifierTypeToProgramType(sel.primaryId.type),
-            programIdentifierFromHal(sel.primaryId),
-            secondaryIds, null);
+                identifierTypeToProgramType(sel.primaryId.type),
+                Objects.requireNonNull(programIdentifierFromHal(sel.primaryId)),
+                secondaryIds, null);
     }
 
     static @NonNull RadioManager.ProgramInfo programInfoFromHal(@NonNull ProgramInfo info) {
+        Collection<ProgramSelector.Identifier> relatedContent = info.relatedContent.stream().
+                map(id -> Objects.requireNonNull(programIdentifierFromHal(id))).
+                collect(Collectors.toList());
+
         return new RadioManager.ProgramInfo(
-            programSelectorFromHal(info.selector),
-            (info.infoFlags & ProgramInfoFlags.TUNED) != 0,
-            (info.infoFlags & ProgramInfoFlags.STEREO) != 0,
-            false,  // TODO(b/69860743): digital
-            info.signalQuality,
-            null,  // TODO(b/69860743): metadata
-            info.infoFlags,
-            vendorInfoFromHal(info.vendorInfo)
+                programSelectorFromHal(info.selector),
+                programIdentifierFromHal(info.logicallyTunedTo),
+                programIdentifierFromHal(info.physicallyTunedTo),
+                relatedContent,
+                info.infoFlags,
+                info.signalQuality,
+                null,  // TODO(b/69860743): metadata
+                vendorInfoFromHal(info.vendorInfo)
         );
     }
 
@@ -259,11 +269,21 @@
     }
 
     static @NonNull ProgramList.Chunk programListChunkFromHal(@NonNull ProgramListChunk chunk) {
-        Set<RadioManager.ProgramInfo> modified = chunk.modified.stream().map(
-            info -> programInfoFromHal(info)).collect(Collectors.toSet());
-        Set<ProgramSelector.Identifier> removed = chunk.removed.stream().map(
-            id -> programIdentifierFromHal(id)).collect(Collectors.toSet());
+        Set<RadioManager.ProgramInfo> modified = chunk.modified.stream().
+                map(info -> programInfoFromHal(info)).collect(Collectors.toSet());
+        Set<ProgramSelector.Identifier> removed = chunk.removed.stream().
+                map(id -> Objects.requireNonNull(programIdentifierFromHal(id))).
+                collect(Collectors.toSet());
 
         return new ProgramList.Chunk(chunk.purge, chunk.complete, modified, removed);
     }
+
+    public static @NonNull android.hardware.radio.Announcement announcementFromHal(
+            @NonNull Announcement hwAnnouncement) {
+        return new android.hardware.radio.Announcement(
+            programSelectorFromHal(hwAnnouncement.selector),
+            hwAnnouncement.type,
+            vendorInfoFromHal(hwAnnouncement.vendorInfo)
+        );
+    }
 }
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
index c8e15c1..4dff9e0 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
@@ -21,7 +21,10 @@
 import android.hardware.radio.ITuner;
 import android.hardware.radio.RadioManager;
 import android.hardware.broadcastradio.V2_0.AmFmRegionConfig;
+import android.hardware.broadcastradio.V2_0.Announcement;
+import android.hardware.broadcastradio.V2_0.IAnnouncementListener;
 import android.hardware.broadcastradio.V2_0.IBroadcastRadio;
+import android.hardware.broadcastradio.V2_0.ICloseHandle;
 import android.hardware.broadcastradio.V2_0.ITunerSession;
 import android.hardware.broadcastradio.V2_0.Result;
 import android.os.ParcelableException;
@@ -29,7 +32,11 @@
 import android.util.MutableInt;
 import android.util.Slog;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 import java.util.Objects;
+import java.util.stream.Collectors;
 
 class RadioModule {
     private static final String TAG = "BcRadio2Srv.module";
@@ -79,4 +86,37 @@
 
         return new TunerSession(hwSession.value, cb);
     }
+
+    public android.hardware.radio.ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes,
+            @NonNull android.hardware.radio.IAnnouncementListener listener) throws RemoteException {
+        ArrayList<Byte> enabledList = new ArrayList<>();
+        for (int type : enabledTypes) {
+            enabledList.add((byte)type);
+        }
+
+        MutableInt halResult = new MutableInt(Result.UNKNOWN_ERROR);
+        Mutable<ICloseHandle> hwCloseHandle = new Mutable<>();
+        IAnnouncementListener hwListener = new IAnnouncementListener.Stub() {
+            public void onListUpdated(ArrayList<Announcement> hwAnnouncements)
+                    throws RemoteException {
+                listener.onListUpdated(hwAnnouncements.stream().
+                    map(a -> Convert.announcementFromHal(a)).collect(Collectors.toList()));
+            }
+        };
+        mService.registerAnnouncementListener(enabledList, hwListener, (result, closeHandle) -> {
+            halResult.value = result;
+            hwCloseHandle.value = closeHandle;
+        });
+        Convert.throwOnError("addAnnouncementListener", halResult.value);
+
+        return new android.hardware.radio.ICloseHandle.Stub() {
+            public void close() {
+                try {
+                    hwCloseHandle.value.close();
+                } catch (RemoteException ex) {
+                    Slog.e(TAG, "Failed closing announcement listener", ex);
+                }
+            }
+        };
+    }
 }
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index 3e1958d..b5f94b1 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -45,6 +45,7 @@
 import android.hardware.fingerprint.IFingerprintServiceLockoutResetCallback;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.DeadObjectException;
 import android.os.Environment;
@@ -58,6 +59,7 @@
 import android.os.SELinux;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.security.KeyStore;
@@ -1419,8 +1421,17 @@
             try {
                 userId = getUserOrWorkProfileId(clientPackage, userId);
                 if (userId != mCurrentUserId) {
-                    final File systemDir = Environment.getUserSystemDirectory(userId);
-                    final File fpDir = new File(systemDir, FP_DATA_DIR);
+                    File baseDir;
+                    if (Build.VERSION.FIRST_SDK_INT <= Build.VERSION_CODES.O_MR1
+                            && !SystemProperties.getBoolean(
+                                "ro.treble.supports_vendor_data", false)) {
+                        // TODO(b/72405644) remove the override when possible.
+                        baseDir = Environment.getUserSystemDirectory(userId);
+                    } else {
+                        baseDir = Environment.getDataVendorDeDirectory(userId);
+                    }
+
+                    File fpDir = new File(baseDir, FP_DATA_DIR);
                     if (!fpDir.exists()) {
                         if (!fpDir.mkdir()) {
                             Slog.v(TAG, "Cannot make directory: " + fpDir.getAbsolutePath());
@@ -1434,6 +1445,7 @@
                             return;
                         }
                     }
+
                     daemon.setActiveGroup(userId, fpDir.getAbsolutePath());
                     mCurrentUserId = userId;
                 }
diff --git a/services/core/java/com/android/server/media/MediaUpdateService.java b/services/core/java/com/android/server/media/MediaUpdateService.java
index 016d062..6921ccd 100644
--- a/services/core/java/com/android/server/media/MediaUpdateService.java
+++ b/services/core/java/com/android/server/media/MediaUpdateService.java
@@ -53,13 +53,10 @@
 
     @Override
     public void onStart() {
-        // TODO: Uncomment below once sepolicy change is landed.
-        /*
         if ("userdebug".equals(android.os.Build.TYPE) || "eng".equals(android.os.Build.TYPE)) {
             connect();
             registerBroadcastReceiver();
         }
-        */
     }
 
     private void connect() {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index efe54fa..4c9da89 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.notification;
 
+import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED;
 import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED;
 import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
@@ -1881,6 +1882,18 @@
                 cancelAllNotificationsInt(MY_UID, MY_PID, pkg, null, 0, 0, true,
                         UserHandle.getUserId(uid), REASON_PACKAGE_BANNED, null);
             }
+
+            try {
+                getContext().sendBroadcastAsUser(
+                        new Intent(ACTION_APP_BLOCK_STATE_CHANGED)
+                                .putExtra(NotificationManager.EXTRA_BLOCKED_STATE, !enabled)
+                                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+                                .setPackage(pkg),
+                        UserHandle.of(UserHandle.getUserId(uid)), null);
+            } catch (SecurityException e) {
+                Slog.w(TAG, "Can't notify app about app block change", e);
+            }
+
             savePolicyFile();
         }
 
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 4b3758d..cdc79c7 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -16,7 +16,9 @@
 
 package com.android.server.pm;
 
+import android.annotation.AppIdInt;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.content.Context;
 import android.content.pm.PackageStats;
 import android.os.Build;
@@ -58,6 +60,9 @@
     public static final int DEXOPT_STORAGE_DE     = 1 << 8;
     /** Indicates that dexopt is invoked from the background service. */
     public static final int DEXOPT_IDLE_BACKGROUND_JOB = 1 << 9;
+    /* Indicates that dexopt should not restrict access to private APIs.
+     * Must be kept in sync with com.android.internal.os.ZygoteInit. */
+    public static final int DEXOPT_DISABLE_HIDDEN_API_CHECKS = 1 << 10;
 
     // NOTE: keep in sync with installd
     public static final int FLAG_CLEAR_CACHE_ONLY = 1 << 8;
@@ -535,6 +540,17 @@
         }
     }
 
+    public boolean prepareAppProfile(String pkg, @UserIdInt int userId, @AppIdInt int appId,
+            String profileName, String codePath, String dexMetadataPath) throws InstallerException {
+        if (!checkBeforeRemote()) return false;
+        try {
+            return mInstalld.prepareAppProfile(pkg, userId, appId, profileName, codePath,
+                    dexMetadataPath);
+        } catch (Exception e) {
+            throw InstallerException.from(e);
+        }
+    }
+
     private static void assertValidInstructionSet(String instructionSet)
             throws InstallerException {
         for (String abi : Build.SUPPORTED_ABIS) {
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 91df87b..6a08e1b 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -55,6 +55,7 @@
 import static com.android.server.pm.Installer.DEXOPT_STORAGE_CE;
 import static com.android.server.pm.Installer.DEXOPT_STORAGE_DE;
 import static com.android.server.pm.Installer.DEXOPT_IDLE_BACKGROUND_JOB;
+import static com.android.server.pm.Installer.DEXOPT_DISABLE_HIDDEN_API_CHECKS;
 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
 
@@ -509,12 +510,18 @@
         boolean isProfileGuidedFilter = isProfileGuidedCompilerFilter(compilerFilter);
         boolean isPublic = !info.isForwardLocked() && !isProfileGuidedFilter;
         int profileFlag = isProfileGuidedFilter ? DEXOPT_PROFILE_GUIDED : 0;
+        // System apps are invoked with a runtime flag which exempts them from
+        // restrictions on hidden API usage. We dexopt with the same runtime flag
+        // otherwise offending methods would have to be re-verified at runtime
+        // and we want to avoid the performance overhead of that.
+        int hiddenApiFlag = info.isAllowedToUseHiddenApi() ? DEXOPT_DISABLE_HIDDEN_API_CHECKS : 0;
         int dexFlags =
                 (isPublic ? DEXOPT_PUBLIC : 0)
                 | (debuggable ? DEXOPT_DEBUGGABLE : 0)
                 | profileFlag
                 | (options.isBootComplete() ? DEXOPT_BOOTCOMPLETE : 0)
-                | (options.isDexoptIdleBackgroundJob() ? DEXOPT_IDLE_BACKGROUND_JOB : 0);
+                | (options.isDexoptIdleBackgroundJob() ? DEXOPT_IDLE_BACKGROUND_JOB : 0)
+                | hiddenApiFlag;
         return adjustDexoptFlags(dexFlags);
     }
 
@@ -629,6 +636,9 @@
         if ((flags & DEXOPT_IDLE_BACKGROUND_JOB) == DEXOPT_IDLE_BACKGROUND_JOB) {
             flagsList.add("idle_background_job");
         }
+        if ((flags & DEXOPT_DISABLE_HIDDEN_API_CHECKS) == DEXOPT_DISABLE_HIDDEN_API_CHECKS) {
+            flagsList.add("disable_hidden_api_checks");
+        }
 
         return String.join(",", flagsList);
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 78200f2..a73ad8d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2430,6 +2430,7 @@
                 installer, mInstallLock);
         mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock,
                 dexManagerListener);
+        mArtManagerService = new ArtManagerService(this, installer, mInstallLock);
         mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());
 
         mOnPermissionChangeListeners = new OnPermissionChangeListeners(
@@ -3086,7 +3087,6 @@
             }
 
             mInstallerService = new PackageInstallerService(context, this);
-            mArtManagerService = new ArtManagerService(this, mInstaller, mInstallLock);
             final Pair<ComponentName, String> instantAppResolverComponent =
                     getInstantAppResolverLPr();
             if (instantAppResolverComponent != null) {
@@ -17019,6 +17019,11 @@
             }
         }
 
+        // Prepare the application profiles for the new code paths.
+        // This needs to be done before invoking dexopt so that any install-time profile
+        // can be used for optimizations.
+        mArtManagerService.prepareAppProfiles(pkg, args.user.getIdentifier());
+
         // Check whether we need to dexopt the app.
         //
         // NOTE: it is IMPORTANT to call dexopt:
@@ -22033,6 +22038,8 @@
                 Slog.e(TAG, "Failed to create app data for " + packageName + ": " + e);
             }
         }
+        // Prepare the application profiles.
+        mArtManagerService.prepareAppProfiles(pkg, userId);
 
         if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && ceDataInode != -1) {
             // TODO: mark this structure as dirty so we persist it!
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index cc07d82..a42fcbd 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -561,6 +561,25 @@
                         }
                     }
                     break;
+                case UserManager.DISALLOW_AMBIENT_DISPLAY:
+                    if (newValue) {
+                        android.provider.Settings.Secure.putString(
+                                context.getContentResolver(),
+                                Settings.Secure.DOZE_ENABLED, "0");
+                        android.provider.Settings.Secure.putString(
+                                context.getContentResolver(),
+                                Settings.Secure.DOZE_ALWAYS_ON, "0");
+                        android.provider.Settings.Secure.putString(
+                                context.getContentResolver(),
+                                Settings.Secure.DOZE_PULSE_ON_PICK_UP, "0");
+                        android.provider.Settings.Secure.putString(
+                                context.getContentResolver(),
+                                Settings.Secure.DOZE_PULSE_ON_LONG_PRESS, "0");
+                        android.provider.Settings.Secure.putString(
+                                context.getContentResolver(),
+                                Settings.Secure.DOZE_PULSE_ON_DOUBLE_TAP, "0");
+                    }
+                    break;
             }
         } finally {
             Binder.restoreCallingIdentity(id);
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index 2dbb34d..92d159b 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -17,9 +17,13 @@
 package com.android.server.pm.dex;
 
 import android.Manifest;
+import android.annotation.UserIdInt;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
 import android.content.pm.dex.ArtManager;
+import android.content.pm.dex.DexMetadataHelper;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.Handler;
@@ -29,10 +33,12 @@
 import android.content.pm.dex.ISnapshotRuntimeProfileCallback;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.util.ArrayMap;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
 import com.android.server.pm.Installer;
 import com.android.server.pm.Installer.InstallerException;
@@ -230,4 +236,52 @@
             // Should not happen.
         }
     }
+
+    /**
+     * Prepare the application profiles.
+     * For all code paths:
+     *   - create the current primary profile to save time at app startup time.
+     *   - copy the profiles from the associated dex metadata file to the reference profile.
+     */
+    public void prepareAppProfiles(PackageParser.Package pkg, @UserIdInt int user) {
+        final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);
+        try {
+            ArrayMap<String, String> codePathsProfileNames = getPackageProfileNames(pkg);
+            for (int i = codePathsProfileNames.size() - 1; i >= 0; i--) {
+                String codePath = codePathsProfileNames.keyAt(i);
+                String profileName = codePathsProfileNames.valueAt(i);
+                File dexMetadata = DexMetadataHelper.findDexMetadataForFile(new File(codePath));
+                String dexMetadataPath = dexMetadata == null ? null : dexMetadata.getAbsolutePath();
+                synchronized (mInstaller) {
+                    boolean result = mInstaller.prepareAppProfile(pkg.packageName, user, appId,
+                            profileName, codePath, dexMetadataPath);
+                    if (!result) {
+                        Slog.e(TAG, "Failed to prepare profile for " +
+                                pkg.packageName + ":" + codePath);
+                    }
+                }
+            }
+        } catch (InstallerException e) {
+            Slog.e(TAG, "Failed to prepare profile for " + pkg.packageName, e);
+        }
+    }
+
+    /**
+     * Build the profiles names for all the package code paths (excluding resource only paths).
+     * Return the map [code path -> profile name].
+     */
+    private ArrayMap<String, String> getPackageProfileNames(PackageParser.Package pkg) {
+        ArrayMap<String, String> result = new ArrayMap<>();
+        if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0) {
+            result.put(pkg.baseCodePath, ArtManager.getProfileName(null));
+        }
+        if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
+            for (int i = 0; i < pkg.splitCodePaths.length; i++) {
+                if ((pkg.splitFlags[i] & ApplicationInfo.FLAG_HAS_CODE) != 0) {
+                    result.put(pkg.splitCodePaths[i], ArtManager.getProfileName(pkg.splitNames[i]));
+                }
+            }
+        }
+        return result;
+    }
 }
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 2a153ec..a536270 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -19,15 +19,6 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManagerInternal;
 import android.app.AppOpsManager;
-
-import com.android.internal.app.IAppOpsService;
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.server.EventLogTags;
-import com.android.server.LocalServices;
-import com.android.server.policy.WindowManagerPolicy;
-
 import android.app.trust.TrustManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -54,6 +45,15 @@
 import android.util.Slog;
 import android.view.inputmethod.InputMethodManagerInternal;
 
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.server.EventLogTags;
+import com.android.server.LocalServices;
+import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.policy.WindowManagerPolicy;
+
 /**
  * Sends broadcasts about important power state changes.
  * <p>
@@ -96,6 +96,7 @@
     private final ActivityManagerInternal mActivityManagerInternal;
     private final InputManagerInternal mInputManagerInternal;
     private final InputMethodManagerInternal mInputMethodManagerInternal;
+    private final StatusBarManagerInternal mStatusBarManagerInternal;
     private final TrustManager mTrustManager;
 
     private final NotifierHandler mHandler;
@@ -142,6 +143,7 @@
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
         mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
         mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class);
+        mStatusBarManagerInternal = LocalServices.getService(StatusBarManagerInternal.class);
         mTrustManager = mContext.getSystemService(TrustManager.class);
 
         mHandler = new NotifierHandler(looper);
@@ -545,9 +547,19 @@
     }
 
     /**
-     * Called when wireless charging has started so as to provide user feedback.
+     * Called when profile screen lock timeout has expired.
      */
-    public void onWirelessChargingStarted() {
+    public void onProfileTimeout(@UserIdInt int userId) {
+        final Message msg = mHandler.obtainMessage(MSG_PROFILE_TIMED_OUT);
+        msg.setAsynchronous(true);
+        msg.arg1 = userId;
+        mHandler.sendMessage(msg);
+    }
+
+    /**
+     * Called when wireless charging has started so as to provide user feedback (sound and visual).
+     */
+    public void onWirelessChargingStarted(int batteryLevel) {
         if (DEBUG) {
             Slog.d(TAG, "onWirelessChargingStarted");
         }
@@ -555,16 +567,7 @@
         mSuspendBlocker.acquire();
         Message msg = mHandler.obtainMessage(MSG_WIRELESS_CHARGING_STARTED);
         msg.setAsynchronous(true);
-        mHandler.sendMessage(msg);
-    }
-
-    /**
-     * Called when profile screen lock timeout has expired.
-     */
-    public void onProfileTimeout(@UserIdInt int userId) {
-        final Message msg = mHandler.obtainMessage(MSG_PROFILE_TIMED_OUT);
-        msg.setAsynchronous(true);
-        msg.arg1 = userId;
+        msg.arg1 = batteryLevel;
         mHandler.sendMessage(msg);
     }
 
@@ -715,7 +718,11 @@
                 }
             }
         }
+    }
 
+    private void showWirelessChargingStarted(int batteryLevel) {
+        playWirelessChargingStartedSound();
+        mStatusBarManagerInternal.showChargingAnimation(batteryLevel);
         mSuspendBlocker.release();
     }
 
@@ -738,7 +745,7 @@
                     sendNextBroadcast();
                     break;
                 case MSG_WIRELESS_CHARGING_STARTED:
-                    playWirelessChargingStartedSound();
+                    showWirelessChargingStarted(msg.arg1);
                     break;
                 case MSG_SCREEN_BRIGHTNESS_BOOST_CHANGED:
                     sendBrightnessBoostChangedBroadcast();
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 7273f62..fbdedce 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -16,6 +16,11 @@
 
 package com.android.server.power;
 
+import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
+import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
+import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING;
+import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING;
+
 import android.annotation.IntDef;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
@@ -103,11 +108,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 
-import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
-import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
-import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING;
-import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING;
-
 /**
  * The power manager service is responsible for coordinating power management
  * functions on the device.
@@ -119,6 +119,9 @@
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_SPEW = DEBUG && true;
 
+    // if DEBUG_WIRELESS=true, plays wireless charging animation w/ sound on every plug + unplug
+    private static final boolean DEBUG_WIRELESS = false;
+
     // Message: Sent when a user activity timeout occurs to update the power state.
     private static final int MSG_USER_ACTIVITY_TIMEOUT = 1;
     // Message: Sent when the device enters or exits a dreaming or dozing state.
@@ -1794,8 +1797,8 @@
 
                 // Tell the notifier whether wireless charging has started so that
                 // it can provide feedback to the user.
-                if (dockedOnWirelessCharger) {
-                    mNotifier.onWirelessChargingStarted();
+                if (dockedOnWirelessCharger || DEBUG_WIRELESS) {
+                    mNotifier.onWirelessChargingStarted(mBatteryLevel);
                 }
             }
 
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 95006ff..3ab771b 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -37,6 +37,8 @@
     void dismissKeyboardShortcutsMenu();
     void toggleKeyboardShortcutsMenu(int deviceId);
 
+    void showChargingAnimation(int batteryLevel);
+
     /**
      * Show picture-in-picture menu.
      */
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index d67f2d7..adb368b 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -319,6 +319,16 @@
         }
 
         @Override
+        public void showChargingAnimation(int batteryLevel) {
+            if (mBar != null) {
+                try {
+                    mBar.showChargingAnimation(batteryLevel);
+                } catch (RemoteException ex){
+                }
+            }
+        }
+
+        @Override
         public void showPictureInPictureMenu() {
             if (mBar != null) {
                 try {
diff --git a/services/core/jni/BroadcastRadio/convert.cpp b/services/core/jni/BroadcastRadio/convert.cpp
index be1ad72..8c38e0a 100644
--- a/services/core/jni/BroadcastRadio/convert.cpp
+++ b/services/core/jni/BroadcastRadio/convert.cpp
@@ -49,6 +49,12 @@
 using V1_1::ProgramSelector;
 using V1_1::VendorKeyValue;
 
+// HAL 2.0 flags that have equivalent HAL 1.x fields
+enum class ProgramInfoFlagsExt {
+    TUNED = 1 << 4,
+    STEREO = 1 << 5,
+};
+
 static JavaRef<jobject> BandDescriptorFromHal(JNIEnv *env, const RegionalBandConfig &config);
 static JavaRef<jobject> BandDescriptorFromHal(JNIEnv *env, const V1_0::BandConfig &config, Region region);
 
@@ -614,9 +620,14 @@
     auto jVendorInfo = info11 ? VendorInfoFromHal(env, info11->vendorInfo) : nullptr;
     auto jSelector = ProgramSelectorFromHal(env, selector);
 
+    jint flags = info11 ? info11->flags : 0;
+    if (info10.tuned) flags |= static_cast<jint>(ProgramInfoFlagsExt::TUNED);
+    if (info10.stereo) flags |= static_cast<jint>(ProgramInfoFlagsExt::STEREO);
+    // info10.digital is dropped, because it has no equivalent in the new APIs
+
     return make_javaref(env, env->NewObject(gjni.ProgramInfo.clazz, gjni.ProgramInfo.cstor,
-            jSelector.get(), info10.tuned, info10.stereo, info10.digital, info10.signalStrength,
-            jMetadata.get(), info11 ? info11->flags : 0, jVendorInfo.get()));
+            jSelector.get(), nullptr, nullptr, nullptr, flags, info10.signalStrength,
+            jMetadata.get(), jVendorInfo.get()));
 }
 
 JavaRef<jobject> ProgramInfoFromHal(JNIEnv *env, const V1_0::ProgramInfo &info, V1_0::Band band) {
@@ -705,9 +716,15 @@
 
     auto programInfoClass = FindClassOrDie(env, "android/hardware/radio/RadioManager$ProgramInfo");
     gjni.ProgramInfo.clazz = MakeGlobalRefOrDie(env, programInfoClass);
-    gjni.ProgramInfo.cstor = GetMethodIDOrDie(env, programInfoClass, "<init>",
-            "(Landroid/hardware/radio/ProgramSelector;ZZZILandroid/hardware/radio/RadioMetadata;I"
-            "Ljava/util/Map;)V");
+    gjni.ProgramInfo.cstor = GetMethodIDOrDie(env, programInfoClass, "<init>", "("
+            "Landroid/hardware/radio/ProgramSelector;"
+            "Landroid/hardware/radio/ProgramSelector$Identifier;"
+            "Landroid/hardware/radio/ProgramSelector$Identifier;"
+            "Ljava/util/Collection;"  // relatedContent
+            "II"  // flags, signalQuality
+            "Landroid/hardware/radio/RadioMetadata;"
+            "Ljava/util/Map;"  // vendorInfo
+            ")V");
 
     auto programSelectorClass = FindClassOrDie(env, "android/hardware/radio/ProgramSelector");
     gjni.ProgramSelector.clazz = MakeGlobalRefOrDie(env, programSelectorClass);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 886747cc..a8e8237 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -70,6 +70,10 @@
 
     public void transferOwnership(ComponentName admin, ComponentName target, PersistableBundle bundle) {}
 
+    public PersistableBundle getTransferOwnershipBundle() {
+        return null;
+    }
+
     public boolean generateKeyPair(ComponentName who, String callerPackage, String algorithm,
             ParcelableKeyGenParameterSpec keySpec, int idAttestationFlags,
             KeymasterCertificateChain attestationChain) {
@@ -135,11 +139,6 @@
     }
 
     @Override
-    public CharSequence getPrintingDisabledReason() {
-        return null;
-    }
-
-    @Override
     public List<String> setMeteredDataDisabled(ComponentName admin, List<String> packageNames) {
         return packageNames;
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index cb346cc..99712a5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -20,7 +20,7 @@
 import static android.Manifest.permission.MANAGE_CA_CERTIFICATES;
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
 import static android.app.ActivityManager.USER_OP_SUCCESS;
-import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE;
+import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE;
 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER;
 import static android.app.admin.DevicePolicyManager.CODE_ACCOUNTS_NOT_EMPTY;
 import static android.app.admin.DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED;
@@ -58,6 +58,7 @@
 import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE;
 import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA;
 import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
+
 import static android.provider.Telephony.Carriers.DPC_URI;
 import static android.provider.Telephony.Carriers.ENFORCE_KEY;
 import static android.provider.Telephony.Carriers.ENFORCE_MANAGED_URI;
@@ -67,6 +68,12 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker
         .STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
 
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_ENTRY_POINT_ADB;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
+
+import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_DEVICE_OWNER;
+import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_PROFILE_OWNER;
+
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
 import static org.xmlpull.v1.XmlPullParser.END_TAG;
 import static org.xmlpull.v1.XmlPullParser.TEXT;
@@ -181,6 +188,7 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.AtomicFile;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
@@ -240,6 +248,7 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Objects;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -256,6 +265,9 @@
 
     private static final String DEVICE_POLICIES_XML = "device_policies.xml";
 
+    private static final String TRANSFER_OWNERSHIP_PARAMETERS_XML =
+            "transfer-ownership-parameters.xml";
+
     private static final String TAG_ACCEPTED_CA_CERTIFICATES = "accepted-ca-certificate";
 
     private static final String TAG_LOCK_TASK_COMPONENTS = "lock-task-component";
@@ -460,6 +472,9 @@
 
     private SetupContentObserver mSetupContentObserver;
 
+    @VisibleForTesting
+    final TransferOwnershipMetadataManager mTransferOwnershipMetadataManager;
+
     private final Runnable mRemoteBugreportTimeoutRunnable = new Runnable() {
         @Override
         public void run() {
@@ -2023,6 +2038,10 @@
         void postOnSystemServerInitThreadPool(Runnable runnable) {
             SystemServerInitThreadPool.get().submit(runnable, LOG_TAG);
         }
+
+        public TransferOwnershipMetadataManager newTransferOwnershipMetadataManager() {
+            return new TransferOwnershipMetadataManager();
+        }
     }
 
     /**
@@ -2067,6 +2086,8 @@
 
         mOverlayPackagesProvider = new OverlayPackagesProvider(mContext);
 
+        mTransferOwnershipMetadataManager = mInjector.newTransferOwnershipMetadataManager();
+
         if (!mHasFeature) {
             // Skip the rest of the initialization
             return;
@@ -3308,6 +3329,29 @@
                 activityManagerInternal.setSwitchingToSystemUserMessage(
                         deviceOwner.endUserSessionMessage);
             }
+
+            revertTransferOwnershipIfNecessaryLocked();
+        }
+    }
+
+    private void revertTransferOwnershipIfNecessaryLocked() {
+        if (!mTransferOwnershipMetadataManager.metadataFileExists()) {
+            return;
+        }
+        Slog.e(LOG_TAG, "Owner transfer metadata file exists! Reverting transfer.");
+        final TransferOwnershipMetadataManager.Metadata metadata =
+                mTransferOwnershipMetadataManager.loadMetadataFile();
+        // Revert transfer
+        if (metadata.adminType.equals(ADMIN_TYPE_PROFILE_OWNER)) {
+            transferProfileOwnershipLocked(metadata.targetComponent, metadata.sourceComponent,
+                    metadata.userId);
+            deleteTransferOwnershipMetadataFileLocked();
+            deleteTransferOwnershipBundleLocked(metadata.userId);
+        } else if (metadata.adminType.equals(ADMIN_TYPE_DEVICE_OWNER)) {
+            transferDeviceOwnershipLocked(metadata.targetComponent, metadata.sourceComponent,
+                    metadata.userId);
+            deleteTransferOwnershipMetadataFileLocked();
+            deleteTransferOwnershipBundleLocked(metadata.userId);
         }
     }
 
@@ -3568,6 +3612,11 @@
     private void transferActiveAdminUncheckedLocked(ComponentName incomingReceiver,
             ComponentName outgoingReceiver, int userHandle) {
         final DevicePolicyData policy = getUserData(userHandle);
+        if (!policy.mAdminMap.containsKey(outgoingReceiver)
+                && policy.mAdminMap.containsKey(incomingReceiver)) {
+            // Nothing to transfer - the incoming receiver is already the active admin.
+            return;
+        }
         final DeviceAdminInfo incomingDeviceInfo = findAdmin(incomingReceiver, userHandle,
             /* throwForMissingPermission= */ true);
         final ActiveAdmin adminToTransfer = policy.mAdminMap.get(outgoingReceiver);
@@ -3581,7 +3630,6 @@
         }
 
         saveSettingsLocked(userHandle);
-        //TODO: Make sure we revert back when we detect a failure.
         sendAdminCommandLocked(adminToTransfer, DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED,
                 null, null);
     }
@@ -7331,6 +7379,7 @@
         mInjector.securityLogSetLoggingEnabledProperty(false);
         mSecurityLogMonitor.stop();
         setNetworkLoggingActiveInternal(false);
+        deleteTransferOwnershipBundleLocked(userId);
 
         try {
             if (mInjector.getIBackupManager() != null) {
@@ -7433,6 +7482,7 @@
         clearUserPoliciesLocked(userId);
         mOwners.removeProfileOwner(userId);
         mOwners.writeProfileOwner(userId);
+        deleteTransferOwnershipBundleLocked(userId);
     }
 
     @Override
@@ -7902,6 +7952,37 @@
         enforceSystemUserOrPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
     }
 
+    private boolean canUserUseLockTaskLocked(int userId) {
+        if (isUserAffiliatedWithDeviceLocked(userId)) {
+            return true;
+        }
+
+        // Unaffiliated profile owners are not allowed to use lock when there is a device owner.
+        if (mOwners.hasDeviceOwner()) {
+            return false;
+        }
+
+        final ComponentName profileOwner = getProfileOwner(userId);
+        if (profileOwner == null) {
+            return false;
+        }
+
+        // Managed profiles are not allowed to use lock task
+        if (isManagedProfile(userId)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private void enforceCanCallLockTaskLocked(ComponentName who) {
+        getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+        final int userId =  mInjector.userHandleGetCallingUserId();
+        if (!canUserUseLockTaskLocked(userId)) {
+            throw new SecurityException("User " + userId + " is not allowed to use lock task");
+        }
+    }
+
     private void ensureCallerPackage(@Nullable String packageName) {
         if (packageName == null) {
             Preconditions.checkState(isCallerWithSystemUid(),
@@ -9608,14 +9689,9 @@
         Preconditions.checkNotNull(packages, "packages is null");
 
         synchronized (this) {
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            enforceCanCallLockTaskLocked(who);
             final int userHandle = mInjector.userHandleGetCallingUserId();
-            if (isUserAffiliatedWithDeviceLocked(userHandle)) {
-                setLockTaskPackagesLocked(userHandle, new ArrayList<>(Arrays.asList(packages)));
-            } else {
-                throw new SecurityException("Admin " + who +
-                    " is neither the device owner or affiliated user's profile owner.");
-            }
+            setLockTaskPackagesLocked(userHandle, new ArrayList<>(Arrays.asList(packages)));
         }
     }
 
@@ -9634,12 +9710,7 @@
 
         final int userHandle = mInjector.binderGetCallingUserHandle().getIdentifier();
         synchronized (this) {
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-            if (!isUserAffiliatedWithDeviceLocked(userHandle)) {
-                throw new SecurityException("Admin " + who +
-                    " is neither the device owner or affiliated user's profile owner.");
-            }
-
+            enforceCanCallLockTaskLocked(who);
             final List<String> packages = getUserData(userHandle).mLockTaskPackages;
             return packages.toArray(new String[packages.size()]);
         }
@@ -9658,11 +9729,7 @@
         Preconditions.checkNotNull(who, "ComponentName is null");
         final int userHandle = mInjector.userHandleGetCallingUserId();
         synchronized (this) {
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-            if (!isUserAffiliatedWithDeviceLocked(userHandle)) {
-                throw new SecurityException("Admin " + who +
-                        " is neither the device owner or affiliated user's profile owner.");
-            }
+            enforceCanCallLockTaskLocked(who);
             setLockTaskFeaturesLocked(userHandle, flags);
         }
     }
@@ -9679,11 +9746,7 @@
         Preconditions.checkNotNull(who, "ComponentName is null");
         final int userHandle = mInjector.userHandleGetCallingUserId();
         synchronized (this) {
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-            if (!isUserAffiliatedWithDeviceLocked(userHandle)) {
-                throw new SecurityException("Admin " + who +
-                        " is neither the device owner or affiliated user's profile owner.");
-            }
+            enforceCanCallLockTaskLocked(who);
             return getUserData(userHandle).mLockTaskFeatures;
         }
     }
@@ -9694,7 +9757,7 @@
             final List<UserInfo> userInfos = mUserManager.getUsers(/*excludeDying=*/ true);
             for (int i = userInfos.size() - 1; i >= 0; i--) {
                 int userId = userInfos.get(i).id;
-                if (isUserAffiliatedWithDeviceLocked(userId)) {
+                if (canUserUseLockTaskLocked(userId)) {
                     continue;
                 }
 
@@ -10251,6 +10314,45 @@
         public boolean canUserHaveUntrustedCredentialReset(@UserIdInt int userId) {
             return DevicePolicyManagerService.this.canUserHaveUntrustedCredentialReset(userId);
         }
+
+        @Override
+        public CharSequence getPrintingDisabledReasonForUser(@UserIdInt int userId) {
+            synchronized (DevicePolicyManagerService.this) {
+                DevicePolicyData policy = getUserData(userId);
+                if (policy.mPrintingEnabled) {
+                    Log.e(LOG_TAG, "printing is enabled");
+                    return null;
+                }
+                String ownerPackage = mOwners.getProfileOwnerPackage(userId);
+                if (ownerPackage == null) {
+                    ownerPackage = mOwners.getDeviceOwnerPackageName();
+                }
+                PackageManager pm = mInjector.getPackageManager();
+                PackageInfo packageInfo;
+                try {
+                    packageInfo = pm.getPackageInfo(ownerPackage, 0);
+                } catch (NameNotFoundException e) {
+                    Log.e(LOG_TAG, "getPackageInfo error", e);
+                    return null;
+                }
+                if (packageInfo == null) {
+                    Log.e(LOG_TAG, "packageInfo is inexplicably null");
+                    return null;
+                }
+                ApplicationInfo appInfo = packageInfo.applicationInfo;
+                if (appInfo == null) {
+                    Log.e(LOG_TAG, "appInfo is inexplicably null");
+                    return null;
+                }
+                CharSequence appLabel = pm.getApplicationLabel(appInfo);
+                if (appLabel == null) {
+                    Log.e(LOG_TAG, "appLabel is inexplicably null");
+                    return null;
+                }
+                return ((Context) ActivityThread.currentActivityThread().getSystemUiContext())
+                        .getResources().getString(R.string.printing_disabled_by, appLabel);
+            }
+        }
     }
 
     private Intent createShowAdminSupportIntent(ComponentName admin, int userId) {
@@ -11232,10 +11334,12 @@
             // of a split user device.
             return true;
         }
+
         final ComponentName profileOwner = getProfileOwner(userId);
         if (profileOwner == null) {
             return false;
         }
+        
         final Set<String> userAffiliationIds = getUserData(userId).mAffiliationIds;
         final Set<String> deviceAffiliationIds =
                 getUserData(UserHandle.USER_SYSTEM).mAffiliationIds;
@@ -12271,10 +12375,9 @@
                 mOverlayPackagesProvider.getNonRequiredApps(admin, userId, provisioningAction));
     }
 
-    //TODO: Add callback information to the javadoc once it is completed.
-    //TODO: Make transferOwnership atomic.
     @Override
-    public void transferOwnership(ComponentName admin, ComponentName target, PersistableBundle bundle) {
+    public void transferOwnership(@NonNull ComponentName admin, @NonNull ComponentName target,
+            @Nullable PersistableBundle bundle) {
         if (!mHasFeature) {
             return;
         }
@@ -12307,12 +12410,41 @@
 
         final long id = mInjector.binderClearCallingIdentity();
         try {
-            //STOPSHIP add support for COMP, edge cases when device is rebooted/work mode off
             synchronized (this) {
+                /*
+                * We must ensure the whole process is atomic to prevent the device from ending up
+                * in an invalid state (e.g. no active admin). This could happen if the device
+                * is rebooted or work mode is turned off mid-transfer.
+                * In order to guarantee atomicity, we:
+                *
+                * 1. Save an atomic journal file describing the transfer process
+                * 2. Perform the transfer itself
+                * 3. Delete the journal file
+                *
+                * That way if the journal file exists on device boot, we know that the transfer
+                * must be reverted back to the original administrator. This logic is implemented in
+                * revertTransferOwnershipIfNecessaryLocked.
+                * */
+                if (bundle == null) {
+                    bundle = new PersistableBundle();
+                }
                 if (isProfileOwner(admin, callingUserId)) {
-                    transferProfileOwnerLocked(admin, target, callingUserId, bundle);
+                    prepareTransfer(admin, target, bundle, callingUserId,
+                            ADMIN_TYPE_PROFILE_OWNER);
+                    transferProfileOwnershipLocked(admin, target, callingUserId);
+                    sendProfileOwnerCommand(DeviceAdminReceiver.ACTION_TRANSFER_OWNERSHIP_COMPLETE,
+                            getTransferOwnershipAdminExtras(bundle), callingUserId);
+                    postTransfer(DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED, callingUserId);
+                    if (isUserAffiliatedWithDeviceLocked(callingUserId)) {
+                        notifyAffiliatedProfileTransferOwnershipComplete(callingUserId);
+                    }
                 } else if (isDeviceOwner(admin, callingUserId)) {
-                    transferDeviceOwnerLocked(admin, target, callingUserId, bundle);
+                    prepareTransfer(admin, target, bundle, callingUserId,
+                            ADMIN_TYPE_DEVICE_OWNER);
+                    transferDeviceOwnershipLocked(admin, target, callingUserId);
+                    sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_TRANSFER_OWNERSHIP_COMPLETE,
+                            getTransferOwnershipAdminExtras(bundle));
+                    postTransfer(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED, callingUserId);
                 }
             }
         } finally {
@@ -12320,43 +12452,55 @@
         }
     }
 
+    private void prepareTransfer(ComponentName admin, ComponentName target,
+            PersistableBundle bundle, int callingUserId, String adminType) {
+        saveTransferOwnershipBundleLocked(bundle, callingUserId);
+        mTransferOwnershipMetadataManager.saveMetadataFile(
+                new TransferOwnershipMetadataManager.Metadata(admin, target,
+                        callingUserId, adminType));
+    }
+
+    private void postTransfer(String broadcast, int callingUserId) {
+        deleteTransferOwnershipMetadataFileLocked();
+        sendOwnerChangedBroadcast(broadcast, callingUserId);
+    }
+
+    private void notifyAffiliatedProfileTransferOwnershipComplete(int callingUserId) {
+        final Bundle extras = new Bundle();
+        extras.putParcelable(Intent.EXTRA_USER, UserHandle.of(callingUserId));
+        sendDeviceOwnerCommand(
+                DeviceAdminReceiver.ACTION_AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE, extras);
+    }
+
     /**
      * Transfers the profile owner for user with id profileOwnerUserId from admin to target.
      */
-    private void transferProfileOwnerLocked(ComponentName admin, ComponentName target,
-            int profileOwnerUserId, PersistableBundle bundle) {
+    private void transferProfileOwnershipLocked(ComponentName admin, ComponentName target,
+            int profileOwnerUserId) {
         transferActiveAdminUncheckedLocked(target, admin, profileOwnerUserId);
         mOwners.transferProfileOwner(target, profileOwnerUserId);
         Slog.i(LOG_TAG, "Profile owner set: " + target + " on user " + profileOwnerUserId);
         mOwners.writeProfileOwner(profileOwnerUserId);
         mDeviceAdminServiceController.startServiceForOwner(
                 target.getPackageName(), profileOwnerUserId, "transfer-profile-owner");
-        sendProfileOwnerCommand(DeviceAdminReceiver.ACTION_TRANSFER_OWNERSHIP_COMPLETE,
-                getTransferOwnerAdminExtras(bundle), profileOwnerUserId);
-        sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED,
-                profileOwnerUserId);
     }
 
     /**
      * Transfers the device owner for user with id userId from admin to target.
      */
-    private void transferDeviceOwnerLocked(ComponentName admin, ComponentName target, int userId,
-            PersistableBundle bundle) {
+    private void transferDeviceOwnershipLocked(ComponentName admin, ComponentName target, int userId) {
         transferActiveAdminUncheckedLocked(target, admin, userId);
-        mOwners.transferDeviceOwner(target);
+        mOwners.transferDeviceOwnership(target);
         Slog.i(LOG_TAG, "Device owner set: " + target + " on user " + userId);
         mOwners.writeDeviceOwner();
         mDeviceAdminServiceController.startServiceForOwner(
                 target.getPackageName(), userId, "transfer-device-owner");
-        sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_TRANSFER_OWNERSHIP_COMPLETE,
-                getTransferOwnerAdminExtras(bundle));
-        sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED, userId);
     }
 
-    private Bundle getTransferOwnerAdminExtras(PersistableBundle bundle) {
+    private Bundle getTransferOwnershipAdminExtras(PersistableBundle bundle) {
         Bundle extras = new Bundle();
         if (bundle != null) {
-            extras.putParcelable(EXTRA_TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE, bundle);
+            extras.putParcelable(EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE, bundle);
         }
         return extras;
     }
@@ -12461,6 +12605,34 @@
         }
     }
 
+    private void deleteTransferOwnershipMetadataFileLocked() {
+        mTransferOwnershipMetadataManager.deleteMetadataFile();
+    }
+
+    @Override
+    @Nullable
+    public PersistableBundle getTransferOwnershipBundle() {
+        synchronized (this) {
+            final int callingUserId = mInjector.userHandleGetCallingUserId();
+            getActiveAdminForCallerLocked(null, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            final File bundleFile = new File(
+                    mInjector.environmentGetUserSystemDirectory(callingUserId),
+                    TRANSFER_OWNERSHIP_PARAMETERS_XML);
+            if (!bundleFile.exists()) {
+                return null;
+            }
+            try (FileInputStream stream = new FileInputStream(bundleFile)) {
+                XmlPullParser parser = Xml.newPullParser();
+                parser.setInput(stream, null);
+                return PersistableBundle.restoreFromXml(parser);
+            } catch (IOException | XmlPullParserException | IllegalArgumentException e) {
+                Slog.e(LOG_TAG, "Caught exception while trying to load the "
+                        + "owner transfer parameters from file " + bundleFile, e);
+                return null;
+            }
+        }
+    }
+
     /**
      * Returns whether printing is enabled for current user.
      * @hide
@@ -12480,55 +12652,6 @@
         }
     }
 
-    /**
-     * Returns text of error message if printing is disabled.
-     * Only to be called by Print Service.
-     * @hide
-     */
-    @Override
-    public CharSequence getPrintingDisabledReason() {
-        if (!hasPrinting() || !mHasFeature) {
-            Log.e(LOG_TAG, "no printing or no management");
-            return null;
-        }
-        synchronized (this) {
-            final int userHandle = mInjector.userHandleGetCallingUserId();
-            DevicePolicyData policy = getUserData(userHandle);
-            if (policy.mPrintingEnabled) {
-                Log.e(LOG_TAG, "printing is enabled");
-                return null;
-            }
-            String ownerPackage = mOwners.getProfileOwnerPackage(userHandle);
-            if (ownerPackage == null) {
-                ownerPackage = mOwners.getDeviceOwnerPackageName();
-            }
-            PackageManager pm = mInjector.getPackageManager();
-            PackageInfo packageInfo;
-            try {
-                packageInfo = pm.getPackageInfo(ownerPackage, 0);
-            } catch (NameNotFoundException e) {
-                Log.e(LOG_TAG, "getPackageInfo error", e);
-                return null;
-            }
-            if (packageInfo == null) {
-                Log.e(LOG_TAG, "packageInfo is inexplicably null");
-                return null;
-            }
-            ApplicationInfo appInfo = packageInfo.applicationInfo;
-            if (appInfo == null) {
-                Log.e(LOG_TAG, "appInfo is inexplicably null");
-                return null;
-            }
-            CharSequence appLabel = pm.getApplicationLabel(appInfo);
-            if (appLabel == null) {
-                Log.e(LOG_TAG, "appLabel is inexplicably null");
-                return null;
-            }
-            return ((Context) ActivityThread.currentActivityThread().getSystemUiContext())
-                    .getResources().getString(R.string.printing_disabled_by, appLabel);
-        }
-    }
-
     @Override
     public int addOverrideApn(@NonNull ComponentName who, @NonNull ApnSetting apnSetting) {
         if (!mHasFeature) {
@@ -12709,4 +12832,32 @@
         }
         return false;
     }
+
+    @VisibleForTesting
+    void saveTransferOwnershipBundleLocked(PersistableBundle bundle, int userId) {
+        final File parametersFile = new File(
+                mInjector.environmentGetUserSystemDirectory(userId),
+                TRANSFER_OWNERSHIP_PARAMETERS_XML);
+        final AtomicFile atomicFile = new AtomicFile(parametersFile);
+        FileOutputStream stream = null;
+        try {
+            stream = atomicFile.startWrite();
+            final XmlSerializer serializer = new FastXmlSerializer();
+            serializer.setOutput(stream, StandardCharsets.UTF_8.name());
+            serializer.startDocument(null, true);
+            bundle.saveToXml(serializer);
+            atomicFile.finishWrite(stream);
+        } catch (IOException | XmlPullParserException e) {
+            Slog.e(LOG_TAG, "Caught exception while trying to save the "
+                    + "owner transfer parameters to file " + parametersFile, e);
+            parametersFile.delete();
+            atomicFile.failWrite(stream);
+        }
+    }
+
+    void deleteTransferOwnershipBundleLocked(int userId) {
+        final File parametersFile = new File(mInjector.environmentGetUserSystemDirectory(userId),
+                TRANSFER_OWNERSHIP_PARAMETERS_XML);
+        parametersFile.delete();
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 2a23888..d2151ed 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -34,6 +34,7 @@
 import android.util.SparseArray;
 import android.util.Xml;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FastXmlSerializer;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -110,13 +111,23 @@
     private SystemUpdateInfo mSystemUpdateInfo;
 
     private final Object mLock = new Object();
+    private final Injector mInjector;
 
     public Owners(UserManager userManager,
             UserManagerInternal userManagerInternal,
             PackageManagerInternal packageManagerInternal) {
+        this(userManager, userManagerInternal, packageManagerInternal, new Injector());
+    }
+
+    @VisibleForTesting
+    Owners(UserManager userManager,
+            UserManagerInternal userManagerInternal,
+            PackageManagerInternal packageManagerInternal,
+            Injector injector) {
         mUserManager = userManager;
         mUserManagerInternal = userManagerInternal;
         mPackageManagerInternal = packageManagerInternal;
+        mInjector = injector;
     }
 
     /**
@@ -125,7 +136,7 @@
     void load() {
         synchronized (mLock) {
             // First, try to read from the legacy file.
-            final File legacy = getLegacyConfigFileWithTestOverride();
+            final File legacy = getLegacyConfigFile();
 
             final List<UserInfo> users = mUserManager.getUsers(true);
 
@@ -288,7 +299,7 @@
         }
     }
 
-    void transferDeviceOwner(ComponentName target) {
+    void transferDeviceOwnership(ComponentName target) {
         synchronized (mLock) {
             // We don't set a name because it's not used anyway.
             // See DevicePolicyManagerService#getDeviceOwnerName
@@ -642,7 +653,7 @@
     private class DeviceOwnerReadWriter extends FileReadWriter {
 
         protected DeviceOwnerReadWriter() {
-            super(getDeviceOwnerFileWithTestOverride());
+            super(getDeviceOwnerFile());
         }
 
         @Override
@@ -713,7 +724,7 @@
         private final int mUserId;
 
         ProfileOwnerReadWriter(int userId) {
-            super(getProfileOwnerFileWithTestOverride(userId));
+            super(getProfileOwnerFile(userId));
             mUserId = userId;
         }
 
@@ -870,15 +881,29 @@
         }
     }
 
-    File getLegacyConfigFileWithTestOverride() {
-        return new File(Environment.getDataSystemDirectory(), DEVICE_OWNER_XML_LEGACY);
+    @VisibleForTesting
+    File getLegacyConfigFile() {
+        return new File(mInjector.environmentGetDataSystemDirectory(), DEVICE_OWNER_XML_LEGACY);
     }
 
-    File getDeviceOwnerFileWithTestOverride() {
-        return new File(Environment.getDataSystemDirectory(), DEVICE_OWNER_XML);
+    @VisibleForTesting
+    File getDeviceOwnerFile() {
+        return new File(mInjector.environmentGetDataSystemDirectory(), DEVICE_OWNER_XML);
     }
 
-    File getProfileOwnerFileWithTestOverride(int userId) {
-        return new File(Environment.getUserSystemDirectory(userId), PROFILE_OWNER_XML);
+    @VisibleForTesting
+    File getProfileOwnerFile(int userId) {
+        return new File(mInjector.environmentGetUserSystemDirectory(userId), PROFILE_OWNER_XML);
+    }
+
+    @VisibleForTesting
+    public static class Injector {
+        File environmentGetDataSystemDirectory() {
+            return Environment.getDataSystemDirectory();
+        }
+
+        File environmentGetUserSystemDirectory(int userId) {
+            return Environment.getUserSystemDirectory(userId);
+        }
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/TransferOwnershipMetadataManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/TransferOwnershipMetadataManager.java
new file mode 100644
index 0000000..1addeb6
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/TransferOwnershipMetadataManager.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.os.Environment;
+import android.text.TextUtils;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.Preconditions;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Handles reading and writing of the owner transfer metadata file.
+ *
+ * Before we perform a device or profile owner transfer, we save this xml file with information
+ * about the current admin, target admin, user id and admin type (device owner or profile owner).
+ * After {@link DevicePolicyManager#transferOwnership} completes, we delete the file. If after
+ * device boot the file is still there, this indicates that the transfer was interrupted by a
+ * reboot.
+ *
+ * Note that this class is not thread safe.
+ */
+class TransferOwnershipMetadataManager {
+    final static String ADMIN_TYPE_DEVICE_OWNER = "device-owner";
+    final static String ADMIN_TYPE_PROFILE_OWNER = "profile-owner";
+    private final static String TAG_USER_ID = "user-id";
+    private final static String TAG_SOURCE_COMPONENT = "source-component";
+    private final static String TAG_TARGET_COMPONENT = "target-component";
+    private final static String TAG_ADMIN_TYPE = "admin-type";
+    private final static String TAG = TransferOwnershipMetadataManager.class.getName();
+    public static final String OWNER_TRANSFER_METADATA_XML = "owner-transfer-metadata.xml";
+
+    private final Injector mInjector;
+
+    TransferOwnershipMetadataManager() {
+        this(new Injector());
+    }
+
+    @VisibleForTesting
+    TransferOwnershipMetadataManager(Injector injector) {
+        mInjector = injector;
+    }
+
+    boolean saveMetadataFile(Metadata params) {
+        final File transferOwnershipMetadataFile = new File(mInjector.getOwnerTransferMetadataDir(),
+                OWNER_TRANSFER_METADATA_XML);
+        final AtomicFile atomicFile = new AtomicFile(transferOwnershipMetadataFile);
+        FileOutputStream stream = null;
+        try {
+            stream = atomicFile.startWrite();
+            final XmlSerializer serializer = new FastXmlSerializer();
+            serializer.setOutput(stream, StandardCharsets.UTF_8.name());
+            serializer.startDocument(null, true);
+            insertSimpleTag(serializer, TAG_USER_ID, Integer.toString(params.userId));
+            insertSimpleTag(serializer,
+                    TAG_SOURCE_COMPONENT, params.sourceComponent.flattenToString());
+            insertSimpleTag(serializer,
+                    TAG_TARGET_COMPONENT, params.targetComponent.flattenToString());
+            insertSimpleTag(serializer, TAG_ADMIN_TYPE, params.adminType);
+            serializer.endDocument();
+            atomicFile.finishWrite(stream);
+            return true;
+        } catch (IOException e) {
+            Slog.e(TAG, "Caught exception while trying to save Owner Transfer "
+                    + "Params to file " + transferOwnershipMetadataFile, e);
+            transferOwnershipMetadataFile.delete();
+            atomicFile.failWrite(stream);
+        }
+        return false;
+    }
+
+    private void insertSimpleTag(XmlSerializer serializer, String tagName, String value)
+            throws IOException {
+        serializer.startTag(null, tagName);
+        serializer.text(value);
+        serializer.endTag(null, tagName);
+    }
+
+    @Nullable
+    Metadata loadMetadataFile() {
+        final File transferOwnershipMetadataFile =
+                new File(mInjector.getOwnerTransferMetadataDir(), OWNER_TRANSFER_METADATA_XML);
+        if (!transferOwnershipMetadataFile.exists()) {
+            return null;
+        }
+        Slog.d(TAG, "Loading TransferOwnershipMetadataManager from "
+                + transferOwnershipMetadataFile);
+        try (FileInputStream stream = new FileInputStream(transferOwnershipMetadataFile)) {
+            final XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(stream, null);
+            return parseMetadataFile(parser);
+        } catch (IOException | XmlPullParserException | IllegalArgumentException e) {
+            Slog.e(TAG, "Caught exception while trying to load the "
+                    + "owner transfer params from file " + transferOwnershipMetadataFile, e);
+        }
+        return null;
+    }
+
+    private Metadata parseMetadataFile(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        int type;
+        final int outerDepth = parser.getDepth();
+        int userId = 0;
+        String adminComponent = null;
+        String targetComponent = null;
+        String adminType = null;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+            switch (parser.getName()) {
+                case TAG_USER_ID:
+                    parser.next();
+                    userId = Integer.parseInt(parser.getText());
+                    break;
+                case TAG_TARGET_COMPONENT:
+                    parser.next();
+                    targetComponent = parser.getText();
+                    break;
+                case TAG_SOURCE_COMPONENT:
+                    parser.next();
+                    adminComponent = parser.getText();
+                    break;
+                case TAG_ADMIN_TYPE:
+                    parser.next();
+                    adminType = parser.getText();
+                    break;
+            }
+        }
+        return new Metadata(adminComponent, targetComponent, userId, adminType);
+    }
+
+    void deleteMetadataFile() {
+        new File(mInjector.getOwnerTransferMetadataDir(), OWNER_TRANSFER_METADATA_XML).delete();
+    }
+
+    boolean metadataFileExists() {
+        return new File(mInjector.getOwnerTransferMetadataDir(),
+                OWNER_TRANSFER_METADATA_XML).exists();
+    }
+
+    static class Metadata {
+        final int userId;
+        final ComponentName sourceComponent;
+        final ComponentName targetComponent;
+        final String adminType;
+
+        Metadata(@NonNull String sourceComponent, @NonNull String targetComponent,
+                @NonNull int userId, @NonNull String adminType) {
+            this.sourceComponent = ComponentName.unflattenFromString(sourceComponent);
+            this.targetComponent = ComponentName.unflattenFromString(targetComponent);
+            Preconditions.checkNotNull(sourceComponent);
+            Preconditions.checkNotNull(targetComponent);
+            Preconditions.checkStringNotEmpty(adminType);
+            this.userId = userId;
+            this.adminType = adminType;
+        }
+
+        Metadata(@NonNull ComponentName sourceComponent, @NonNull ComponentName targetComponent,
+                @NonNull int userId, @NonNull String adminType) {
+            this(sourceComponent.flattenToString(), targetComponent.flattenToString(),
+                    userId, adminType);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof Metadata)) {
+                return false;
+            }
+            Metadata params = (Metadata) obj;
+
+            return userId == params.userId
+                    && sourceComponent.equals(params.sourceComponent)
+                    && targetComponent.equals(params.targetComponent)
+                    && TextUtils.equals(adminType, params.adminType);
+        }
+
+        @Override
+        public int hashCode() {
+            int hashCode = 1;
+            hashCode = 31 * hashCode + userId;
+            hashCode = 31 * hashCode + sourceComponent.hashCode();
+            hashCode = 31 * hashCode + targetComponent.hashCode();
+            hashCode = 31 * hashCode + adminType.hashCode();
+            return hashCode;
+        }
+    }
+
+    @VisibleForTesting
+    static class Injector {
+        public File getOwnerTransferMetadataDir() {
+            return Environment.getDataSystemDirectory();
+        }
+    }
+}
diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java
index 89a5fe1..d6cc805 100644
--- a/services/print/java/com/android/server/print/PrintManagerService.java
+++ b/services/print/java/com/android/server/print/PrintManagerService.java
@@ -22,6 +22,7 @@
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManagerInternal;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -64,6 +65,7 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
+import com.android.server.LocalServices;
 import com.android.server.SystemService;
 
 import java.io.FileDescriptor;
@@ -113,12 +115,12 @@
 
         private final SparseArray<UserState> mUserStates = new SparseArray<>();
 
-        private final DevicePolicyManager mDpc;
+        private final DevicePolicyManager mDpm;
 
         PrintManagerImpl(Context context) {
             mContext = context;
             mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-            mDpc = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+            mDpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
             registerContentObservers();
             registerBroadcastReceivers();
         }
@@ -128,7 +130,16 @@
                 PrintAttributes attributes, String packageName, int appId, int userId) {
             adapter = Preconditions.checkNotNull(adapter);
             if (!isPrintingEnabled()) {
-                final CharSequence disabledMessage = mDpc.getPrintingDisabledReason();
+                CharSequence disabledMessage = null;
+                DevicePolicyManagerInternal dpmi =
+                        LocalServices.getService(DevicePolicyManagerInternal.class);
+                final int callingUserId = UserHandle.getCallingUserId();
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    disabledMessage = dpmi.getPrintingDisabledReasonForUser(callingUserId);
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
                 if (disabledMessage != null) {
                     Toast.makeText(mContext, Looper.getMainLooper(), disabledMessage,
                             Toast.LENGTH_LONG).show();
@@ -711,7 +722,7 @@
         }
 
         private boolean isPrintingEnabled() {
-            return mDpc == null || mDpc.isPrintingEnabled();
+            return mDpm == null || mDpm.isPrintingEnabled();
         }
 
         private void dump(@NonNull DualDumpOutputStream dumpStream,
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 0499bf0..372b5be 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -59,6 +59,8 @@
     <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
     <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
     <uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
     <!-- Uses API introduced in O (26) -->
     <uses-sdk android:minSdkVersion="1"
diff --git a/services/tests/servicestests/res/raw/active_admin_migrated.xml b/services/tests/servicestests/res/raw/active_admin_migrated.xml
new file mode 100644
index 0000000..47af30f
--- /dev/null
+++ b/services/tests/servicestests/res/raw/active_admin_migrated.xml
@@ -0,0 +1,11 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true" provisioning-state="3">
+    <admin name="com.another.package.name/whatever.random.class">
+        <policies flags="991"/>
+        <strong-auth-unlock-timeout value="0"/>
+        <user-restrictions no_add_managed_profile="true"/>
+        <default-enabled-user-restrictions>
+            <restriction value="no_add_managed_profile"/>
+        </default-enabled-user-restrictions>
+    </admin>
+</policies>
\ No newline at end of file
diff --git a/services/tests/servicestests/res/raw/active_admin_not_migrated.xml b/services/tests/servicestests/res/raw/active_admin_not_migrated.xml
new file mode 100644
index 0000000..54eba4c
--- /dev/null
+++ b/services/tests/servicestests/res/raw/active_admin_not_migrated.xml
@@ -0,0 +1,11 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true" provisioning-state="3">
+    <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1">
+        <policies flags="991"/>
+        <strong-auth-unlock-timeout value="0"/>
+        <user-restrictions no_add_managed_profile="true"/>
+        <default-enabled-user-restrictions>
+            <restriction value="no_add_managed_profile"/>
+        </default-enabled-user-restrictions>
+    </admin>
+</policies>
\ No newline at end of file
diff --git a/services/tests/servicestests/res/raw/device_owner_migrated.xml b/services/tests/servicestests/res/raw/device_owner_migrated.xml
new file mode 100644
index 0000000..4ee05bf
--- /dev/null
+++ b/services/tests/servicestests/res/raw/device_owner_migrated.xml
@@ -0,0 +1,9 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<root>
+<device-owner
+    package="com.another.package.name"
+    name=""
+    component="com.another.package.name/whatever.random.class"
+    userRestrictionsMigrated="true" />
+<device-owner-context userId="0" />
+</root>
\ No newline at end of file
diff --git a/services/tests/servicestests/res/raw/device_owner_not_migrated.xml b/services/tests/servicestests/res/raw/device_owner_not_migrated.xml
new file mode 100644
index 0000000..3a532af
--- /dev/null
+++ b/services/tests/servicestests/res/raw/device_owner_not_migrated.xml
@@ -0,0 +1,9 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<root>
+<device-owner
+    package="com.android.frameworks.servicestests"
+    name=""
+    component="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1"
+    userRestrictionsMigrated="true" />
+<device-owner-context userId="0" />
+</root>
\ No newline at end of file
diff --git a/services/tests/servicestests/res/raw/profile_owner_migrated.xml b/services/tests/servicestests/res/raw/profile_owner_migrated.xml
new file mode 100644
index 0000000..f73d2cd
--- /dev/null
+++ b/services/tests/servicestests/res/raw/profile_owner_migrated.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<root>
+    <profile-owner package="com.another.package.name"
+       name="com.another.package.name"
+       component="com.another.package.name/whatever.random.class"
+       userRestrictionsMigrated="true"/>
+</root>
\ No newline at end of file
diff --git a/services/tests/servicestests/res/raw/profile_owner_not_migrated.xml b/services/tests/servicestests/res/raw/profile_owner_not_migrated.xml
new file mode 100644
index 0000000..1ce3a47
--- /dev/null
+++ b/services/tests/servicestests/res/raw/profile_owner_not_migrated.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<root>
+    <profile-owner package="com.android.frameworks.servicestests"
+       name="com.android.frameworks.servicestests"
+       component="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1"
+       userRestrictionsMigrated="true"/>
+</root>
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 06f138b..00e27c9 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -60,40 +60,33 @@
      */
     public static class OwnersTestable extends Owners {
         public static final String LEGACY_FILE = "legacy.xml";
-        public static final String DEVICE_OWNER_FILE = "device_owner2.xml";
-        public static final String PROFILE_OWNER_FILE = "profile_owner.xml";
-
-        private final File mLegacyFile;
-        private final File mDeviceOwnerFile;
-        private final File mUsersDataDir;
 
         public OwnersTestable(MockSystemServices services) {
             super(services.userManager, services.userManagerInternal,
-                    services.packageManagerInternal);
-            mLegacyFile = new File(services.dataDir, LEGACY_FILE);
-            mDeviceOwnerFile = new File(services.dataDir, DEVICE_OWNER_FILE);
-            mUsersDataDir = new File(services.dataDir, "users");
+                    services.packageManagerInternal, new MockInjector(services));
         }
 
-        @Override
-        File getLegacyConfigFileWithTestOverride() {
-            return mLegacyFile;
-        }
+        static class MockInjector extends Injector {
+            private final MockSystemServices mServices;
 
-        @Override
-        File getDeviceOwnerFileWithTestOverride() {
-            return mDeviceOwnerFile;
-        }
+            private MockInjector(MockSystemServices services) {
+                mServices = services;
+            }
 
-        @Override
-        File getProfileOwnerFileWithTestOverride(int userId) {
-            final File userDir = new File(mUsersDataDir, String.valueOf(userId));
-            return new File(userDir, PROFILE_OWNER_FILE);
+            @Override
+            File environmentGetDataSystemDirectory() {
+                return mServices.dataDir;
+            }
+
+            @Override
+            File environmentGetUserSystemDirectory(int userId) {
+                return mServices.environment.getUserSystemDirectory(userId);
+            }
         }
     }
 
     public final DpmMockContext context;
-    private final MockInjector mMockInjector;
+    protected final MockInjector mMockInjector;
 
     public DevicePolicyManagerServiceTestable(MockSystemServices services, DpmMockContext context) {
         this(new MockInjector(services, context));
@@ -124,8 +117,7 @@
         }
     }
 
-
-    private static class MockInjector extends Injector {
+    static class MockInjector extends Injector {
 
         public final DpmMockContext context;
         private final MockSystemServices services;
@@ -133,7 +125,7 @@
         // Key is a pair of uri and userId
         private final Map<Pair<Uri, Integer>, ContentObserver> mContentObservers = new ArrayMap<>();
 
-        private MockInjector(MockSystemServices services, DpmMockContext context) {
+        public MockInjector(MockSystemServices services, DpmMockContext context) {
             super(context);
             this.services = services;
             this.context = context;
@@ -449,5 +441,11 @@
         void postOnSystemServerInitThreadPool(Runnable runnable) {
             runnable.run();
         }
+
+        @Override
+        public TransferOwnershipMetadataManager newTransferOwnershipMetadataManager() {
+            return new TransferOwnershipMetadataManager(
+                    new TransferOwnershipMetadataManagerTest.MockInjector());
+        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 5825a8b7..6b87ea9 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -50,6 +50,7 @@
 import static org.mockito.hamcrest.MockitoHamcrest.argThat;
 
 import android.Manifest.permission;
+import android.annotation.RawRes;
 import android.app.Activity;
 import android.app.Notification;
 import android.app.admin.DeviceAdminReceiver;
@@ -93,10 +94,13 @@
 
 import org.hamcrest.BaseMatcher;
 import org.hamcrest.Description;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
+import java.io.File;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -202,9 +206,14 @@
         setUpUserManager();
     }
 
+    private TransferOwnershipMetadataManager getMockTransferMetadataManager() {
+        return dpms.mTransferOwnershipMetadataManager;
+    }
+
     @Override
     protected void tearDown() throws Exception {
         flushTasks();
+        getMockTransferMetadataManager().deleteMetadataFile();
         super.tearDown();
     }
 
@@ -3653,15 +3662,47 @@
         MoreAsserts.assertEmpty(targetUsers);
     }
 
+    private void verifyLockTaskState(int userId) throws Exception {
+        verifyLockTaskState(userId, new String[0], DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
+    }
+
+    private void verifyLockTaskState(int userId, String[] packages, int flags) throws Exception {
+        verify(getServices().iactivityManager).updateLockTaskPackages(userId, packages);
+        verify(getServices().iactivityManager).updateLockTaskFeatures(userId, flags);
+    }
+
+    private void verifyCanSetLockTask(int uid, int userId, ComponentName who, String[] packages,
+            int flags) throws Exception {
+        mContext.binder.callingUid = uid;
+        dpm.setLockTaskPackages(who, packages);
+        MoreAsserts.assertEquals(packages, dpm.getLockTaskPackages(who));
+        for (String p : packages) {
+            assertTrue(dpm.isLockTaskPermitted(p));
+        }
+        assertFalse(dpm.isLockTaskPermitted("anotherPackage"));
+        // Test to see if set lock task features can be set
+        dpm.setLockTaskFeatures(who, flags);
+        verifyLockTaskState(userId, packages, flags);
+    }
+
+    private void verifyCanNotSetLockTask(int uid, ComponentName who, String[] packages,
+            int flags) throws Exception {
+        mContext.binder.callingUid = uid;
+        assertExpectException(SecurityException.class, /* messageRegex =*/ null,
+                () -> dpm.setLockTaskPackages(who, packages));
+        assertExpectException(SecurityException.class, /* messageRegex =*/ null,
+                () -> dpm.getLockTaskPackages(who));
+        assertFalse(dpm.isLockTaskPermitted("doPackage1"));
+        assertExpectException(SecurityException.class, /* messageRegex =*/ null,
+                () -> dpm.setLockTaskFeatures(who, flags));
+    }
+
     public void testLockTaskPolicyAllowedForAffiliatedUsers() throws Exception {
         // Setup a device owner.
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
         // Lock task policy is updated when loading user data.
-        verify(getServices().iactivityManager).updateLockTaskPackages(
-                UserHandle.USER_SYSTEM, new String[0]);
-        verify(getServices().iactivityManager).updateLockTaskFeatures(
-                UserHandle.USER_SYSTEM, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
+        verifyLockTaskState(UserHandle.USER_SYSTEM);
 
         // Set up a managed profile managed by different package (package name shouldn't matter)
         final int MANAGED_PROFILE_USER_ID = 15;
@@ -3669,40 +3710,30 @@
         final ComponentName adminDifferentPackage =
                 new ComponentName("another.package", "whatever.class");
         addManagedProfile(adminDifferentPackage, MANAGED_PROFILE_ADMIN_UID, admin2);
-        verify(getServices().iactivityManager).updateLockTaskPackages(
-                MANAGED_PROFILE_USER_ID, new String[0]);
-        verify(getServices().iactivityManager).updateLockTaskFeatures(
-                MANAGED_PROFILE_USER_ID, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
+        verifyLockTaskState(MANAGED_PROFILE_USER_ID);
+
+        // Setup a PO on the secondary user
+        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+        setAsProfileOwner(admin3);
+        verifyLockTaskState(DpmMockContext.CALLER_USER_HANDLE);
 
         // The DO can still set lock task packages
-        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         final String[] doPackages = {"doPackage1", "doPackage2"};
-        dpm.setLockTaskPackages(admin1, doPackages);
-        MoreAsserts.assertEquals(doPackages, dpm.getLockTaskPackages(admin1));
-        assertTrue(dpm.isLockTaskPermitted("doPackage1"));
-        assertFalse(dpm.isLockTaskPermitted("anotherPackage"));
-        verify(getServices().iactivityManager).updateLockTaskPackages(
-                UserHandle.USER_SYSTEM, doPackages);
-        // And the DO can still set lock task features
-        final int doFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
+        final int flags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
                 | DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS;
-        dpm.setLockTaskFeatures(admin1, doFlags);
-        verify(getServices().iactivityManager).updateLockTaskFeatures(
-                UserHandle.USER_SYSTEM, doFlags);
+        verifyCanSetLockTask(DpmMockContext.CALLER_SYSTEM_USER_UID, UserHandle.USER_SYSTEM, admin1, doPackages, flags);
+
+        final String[] secondaryPoPackages = {"secondaryPoPackage1", "secondaryPoPackage2"};
+        final int secondaryPoFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
+                | DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS;
+        verifyCanNotSetLockTask(DpmMockContext.CALLER_UID, admin3, secondaryPoPackages, secondaryPoFlags);
 
         // Managed profile is unaffiliated - shouldn't be able to setLockTaskPackages.
         mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
         final String[] poPackages = {"poPackage1", "poPackage2"};
-        assertExpectException(SecurityException.class, /* messageRegex =*/ null,
-                () -> dpm.setLockTaskPackages(adminDifferentPackage, poPackages));
-        assertExpectException(SecurityException.class, /* messageRegex =*/ null,
-                () -> dpm.getLockTaskPackages(adminDifferentPackage));
-        assertFalse(dpm.isLockTaskPermitted("doPackage1"));
-        // And it shouldn't be able to setLockTaskFeatures.
         final int poFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
                 | DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS;
-        assertExpectException(SecurityException.class, /* messageRegex =*/ null,
-                () -> dpm.setLockTaskFeatures(adminDifferentPackage, poFlags));
+        verifyCanNotSetLockTask(MANAGED_PROFILE_ADMIN_UID, adminDifferentPackage, poPackages, poFlags);
 
         // Setting same affiliation ids
         final Set<String> userAffiliationIds = Collections.singleton("some-affiliation-id");
@@ -3717,12 +3748,9 @@
         MoreAsserts.assertEquals(poPackages, dpm.getLockTaskPackages(adminDifferentPackage));
         assertTrue(dpm.isLockTaskPermitted("poPackage1"));
         assertFalse(dpm.isLockTaskPermitted("doPackage2"));
-        verify(getServices().iactivityManager).updateLockTaskPackages(
-                MANAGED_PROFILE_USER_ID, poPackages);
         // And it can set lock task features.
         dpm.setLockTaskFeatures(adminDifferentPackage, poFlags);
-        verify(getServices().iactivityManager).updateLockTaskFeatures(
-                MANAGED_PROFILE_USER_ID, poFlags);
+        verifyLockTaskState(MANAGED_PROFILE_USER_ID, poPackages, poFlags);
 
         // Unaffiliate the profile, lock task mode no longer available on the profile.
         dpm.setAffiliationIds(adminDifferentPackage, Collections.emptySet());
@@ -3733,8 +3761,38 @@
         verify(getServices().iactivityManager, times(2)).updateLockTaskFeatures(
                 MANAGED_PROFILE_USER_ID, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
 
+        // Verify that lock task packages were not cleared for the DO
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         assertTrue(dpm.isLockTaskPermitted("doPackage1"));
+
+    }
+
+    public void testLockTaskPolicyForProfileOwner() throws Exception {
+        // Setup a PO
+        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+        setAsProfileOwner(admin1);
+        verifyLockTaskState(DpmMockContext.CALLER_USER_HANDLE);
+
+        final String[] poPackages = {"poPackage1", "poPackage2"};
+        final int poFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
+                | DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS;
+        verifyCanSetLockTask(DpmMockContext.CALLER_UID, DpmMockContext.CALLER_USER_HANDLE, admin1,
+                poPackages, poFlags);
+
+        // Set up a managed profile managed by different package (package name shouldn't matter)
+        final int MANAGED_PROFILE_USER_ID = 15;
+        final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 20456);
+        final ComponentName adminDifferentPackage =
+                new ComponentName("another.package", "whatever.class");
+        addManagedProfile(adminDifferentPackage, MANAGED_PROFILE_ADMIN_UID, admin2);
+        verifyLockTaskState(MANAGED_PROFILE_USER_ID);
+
+        // Managed profile is unaffiliated - shouldn't be able to setLockTaskPackages.
+        mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+        final String[] mpoPackages = {"poPackage1", "poPackage2"};
+        final int mpoFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
+                | DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS;
+        verifyCanNotSetLockTask(MANAGED_PROFILE_ADMIN_UID, adminDifferentPackage, mpoPackages, mpoFlags);
     }
 
     public void testIsDeviceManaged() throws Exception {
@@ -4786,6 +4844,176 @@
                     AttestationUtils.ID_TYPE_MEID});
     }
 
+    public void testRevertDeviceOwnership_noMetadataFile() throws Exception {
+        setDeviceOwner();
+        initializeDpms();
+        assertFalse(getMockTransferMetadataManager().metadataFileExists());
+        assertTrue(dpms.isDeviceOwner(admin1, UserHandle.USER_SYSTEM));
+        assertTrue(dpms.isAdminActive(admin1, UserHandle.USER_SYSTEM));
+    }
+
+    public void testRevertDeviceOwnership_adminAndDeviceMigrated() throws Exception {
+        DpmTestUtils.writeInputStreamToFile(
+                getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated),
+                getDeviceOwnerPoliciesFile());
+        DpmTestUtils.writeInputStreamToFile(
+                getRawStream(com.android.frameworks.servicestests.R.raw.device_owner_migrated),
+                getDeviceOwnerFile());
+        assertDeviceOwnershipRevertedWithFakeTransferMetadata();
+    }
+
+    public void testRevertDeviceOwnership_deviceNotMigrated()
+            throws Exception {
+        DpmTestUtils.writeInputStreamToFile(
+                getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated),
+                getDeviceOwnerPoliciesFile());
+        DpmTestUtils.writeInputStreamToFile(
+                getRawStream(com.android.frameworks.servicestests.R.raw.device_owner_not_migrated),
+                getDeviceOwnerFile());
+        assertDeviceOwnershipRevertedWithFakeTransferMetadata();
+    }
+
+    public void testRevertDeviceOwnership_adminAndDeviceNotMigrated()
+            throws Exception {
+        DpmTestUtils.writeInputStreamToFile(
+                getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_not_migrated),
+                getDeviceOwnerPoliciesFile());
+        DpmTestUtils.writeInputStreamToFile(
+                getRawStream(com.android.frameworks.servicestests.R.raw.device_owner_not_migrated),
+                getDeviceOwnerFile());
+        assertDeviceOwnershipRevertedWithFakeTransferMetadata();
+    }
+
+    public void testRevertProfileOwnership_noMetadataFile() throws Exception {
+        setupProfileOwner();
+        initializeDpms();
+        assertFalse(getMockTransferMetadataManager().metadataFileExists());
+        assertTrue(dpms.isProfileOwner(admin1, DpmMockContext.CALLER_USER_HANDLE));
+        assertTrue(dpms.isAdminActive(admin1, DpmMockContext.CALLER_USER_HANDLE));
+        UserHandle userHandle = UserHandle.of(DpmMockContext.CALLER_USER_HANDLE);
+    }
+
+    public void testRevertProfileOwnership_adminAndProfileMigrated() throws Exception {
+        getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, UserInfo.FLAG_MANAGED_PROFILE,
+                UserHandle.USER_SYSTEM);
+        DpmTestUtils.writeInputStreamToFile(
+                getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated),
+                getProfileOwnerPoliciesFile());
+        DpmTestUtils.writeInputStreamToFile(
+                getRawStream(com.android.frameworks.servicestests.R.raw.profile_owner_migrated),
+                getProfileOwnerFile());
+        assertProfileOwnershipRevertedWithFakeTransferMetadata();
+    }
+
+    public void testRevertProfileOwnership_profileNotMigrated() throws Exception {
+        getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, UserInfo.FLAG_MANAGED_PROFILE,
+                UserHandle.USER_SYSTEM);
+        DpmTestUtils.writeInputStreamToFile(
+                getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated),
+                getProfileOwnerPoliciesFile());
+        DpmTestUtils.writeInputStreamToFile(
+                getRawStream(com.android.frameworks.servicestests.R.raw.profile_owner_not_migrated),
+                getProfileOwnerFile());
+        assertProfileOwnershipRevertedWithFakeTransferMetadata();
+    }
+
+    public void testRevertProfileOwnership_adminAndProfileNotMigrated() throws Exception {
+        getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, UserInfo.FLAG_MANAGED_PROFILE,
+                UserHandle.USER_SYSTEM);
+        DpmTestUtils.writeInputStreamToFile(
+                getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_not_migrated),
+                getProfileOwnerPoliciesFile());
+        DpmTestUtils.writeInputStreamToFile(
+                getRawStream(com.android.frameworks.servicestests.R.raw.profile_owner_not_migrated),
+                getProfileOwnerFile());
+        assertProfileOwnershipRevertedWithFakeTransferMetadata();
+    }
+
+    // admin1 is the outgoing DPC, adminAnotherPakcage is the incoming one.
+    private void assertDeviceOwnershipRevertedWithFakeTransferMetadata() throws Exception {
+        writeFakeTransferMetadataFile(UserHandle.USER_SYSTEM,
+                TransferOwnershipMetadataManager.ADMIN_TYPE_DEVICE_OWNER);
+
+        final long ident = mServiceContext.binder.clearCallingIdentity();
+        setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
+        setUpPackageManagerForFakeAdmin(adminAnotherPackage,
+                DpmMockContext.CALLER_SYSTEM_USER_UID, admin1);
+        // To simulate a reboot, we just reinitialize dpms and call systemReady
+        initializeDpms();
+
+        assertTrue(dpm.isDeviceOwnerApp(admin1.getPackageName()));
+        assertFalse(dpm.isDeviceOwnerApp(adminAnotherPackage.getPackageName()));
+        assertFalse(dpm.isAdminActive(adminAnotherPackage));
+        assertTrue(dpm.isAdminActive(admin1));
+        assertTrue(dpm.isDeviceOwnerAppOnCallingUser(admin1.getPackageName()));
+        assertEquals(admin1, dpm.getDeviceOwnerComponentOnCallingUser());
+
+        assertTrue(dpm.isDeviceOwnerAppOnAnyUser(admin1.getPackageName()));
+        assertEquals(admin1, dpm.getDeviceOwnerComponentOnAnyUser());
+        assertEquals(UserHandle.USER_SYSTEM, dpm.getDeviceOwnerUserId());
+        assertFalse(getMockTransferMetadataManager().metadataFileExists());
+
+        mServiceContext.binder.restoreCallingIdentity(ident);
+    }
+
+    // admin1 is the outgoing DPC, adminAnotherPakcage is the incoming one.
+    private void assertProfileOwnershipRevertedWithFakeTransferMetadata() throws Exception {
+        writeFakeTransferMetadataFile(DpmMockContext.CALLER_USER_HANDLE,
+                TransferOwnershipMetadataManager.ADMIN_TYPE_PROFILE_OWNER);
+
+        int uid = UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE,
+                DpmMockContext.CALLER_SYSTEM_USER_UID);
+        setUpPackageManagerForAdmin(admin1, uid);
+        setUpPackageManagerForFakeAdmin(adminAnotherPackage, uid, admin1);
+        // To simulate a reboot, we just reinitialize dpms and call systemReady
+        initializeDpms();
+
+        assertTrue(dpm.isProfileOwnerApp(admin1.getPackageName()));
+        assertTrue(dpm.isAdminActive(admin1));
+        assertFalse(dpm.isProfileOwnerApp(adminAnotherPackage.getPackageName()));
+        assertFalse(dpm.isAdminActive(adminAnotherPackage));
+        assertEquals(dpm.getProfileOwnerAsUser(DpmMockContext.CALLER_USER_HANDLE), admin1);
+        assertFalse(getMockTransferMetadataManager().metadataFileExists());
+    }
+
+    private void writeFakeTransferMetadataFile(int callerUserHandle, String adminType) {
+        TransferOwnershipMetadataManager metadataManager = getMockTransferMetadataManager();
+        metadataManager.deleteMetadataFile();
+
+        final TransferOwnershipMetadataManager.Metadata metadata =
+                new TransferOwnershipMetadataManager.Metadata(
+                        admin1.flattenToString(), adminAnotherPackage.flattenToString(),
+                        callerUserHandle,
+                        adminType);
+        metadataManager.saveMetadataFile(metadata);
+    }
+
+    private File getDeviceOwnerFile() {
+        return dpms.mOwners.getDeviceOwnerFile();
+    }
+
+    private File getProfileOwnerFile() {
+        return dpms.mOwners.getProfileOwnerFile(DpmMockContext.CALLER_USER_HANDLE);
+    }
+
+    private File getProfileOwnerPoliciesFile() {
+        File parentDir = dpms.mMockInjector.environmentGetUserSystemDirectory(
+                DpmMockContext.CALLER_USER_HANDLE);
+        return getPoliciesFile(parentDir);
+    }
+
+    private File getDeviceOwnerPoliciesFile() {
+        return getPoliciesFile(getServices().systemUserDataDir);
+    }
+
+    private File getPoliciesFile(File parentDir) {
+        return new File(parentDir, "device_policies.xml");
+    }
+
+    private InputStream getRawStream(@RawRes int id) {
+        return mRealTestContext.getResources().openRawResource(id);
+    }
+
     private void setUserSetupCompleteForUser(boolean isUserSetupComplete, int userhandle) {
         when(getServices().settings.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0,
                 userhandle)).thenReturn(isUserSetupComplete ? 1 : 0);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java
index cceb2d2..2882b88 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java
@@ -16,9 +16,6 @@
 
 package com.android.server.devicepolicy;
 
-import com.google.android.collect.Lists;
-import com.google.android.collect.Sets;
-
 import android.content.Context;
 import android.os.Bundle;
 import android.os.FileUtils;
@@ -28,21 +25,25 @@
 import android.util.Log;
 import android.util.Printer;
 
+import libcore.io.Streams;
+
+import com.google.android.collect.Lists;
+
+import junit.framework.AssertionFailedError;
+
 import org.junit.Assert;
 
 import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.FileWriter;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.InputStreamReader;
-import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-
-import junit.framework.AssertionFailedError;
 
 public class DpmTestUtils extends AndroidTestCase {
     public static void clearDir(File dir) {
@@ -136,6 +137,11 @@
         }
     }
 
+    public static void writeInputStreamToFile(InputStream stream, File file)
+            throws IOException {
+        Streams.copy(stream, new FileOutputStream(file));
+    }
+
     private static boolean checkAssertRestrictions(Bundle a, Bundle b) {
         try {
             assertRestrictions(a, b);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
index 85835f7..cb6a747 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
@@ -42,21 +42,21 @@
         {
             final OwnersTestable owners = new OwnersTestable(getServices());
 
-            DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+            DpmTestUtils.writeToFile(owners.getLegacyConfigFile(),
                     DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test01/input.xml"));
 
             owners.load();
 
             // The legacy file should be removed.
-            assertFalse(owners.getLegacyConfigFileWithTestOverride().exists());
+            assertFalse(owners.getLegacyConfigFile().exists());
 
             // File was empty, so no new files should be created.
-            assertFalse(owners.getDeviceOwnerFileWithTestOverride().exists());
+            assertFalse(owners.getDeviceOwnerFile().exists());
 
-            assertFalse(owners.getProfileOwnerFileWithTestOverride(10).exists());
-            assertFalse(owners.getProfileOwnerFileWithTestOverride(11).exists());
-            assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists());
-            assertFalse(owners.getProfileOwnerFileWithTestOverride(21).exists());
+            assertFalse(owners.getProfileOwnerFile(10).exists());
+            assertFalse(owners.getProfileOwnerFile(11).exists());
+            assertFalse(owners.getProfileOwnerFile(20).exists());
+            assertFalse(owners.getProfileOwnerFile(21).exists());
 
             assertFalse(owners.hasDeviceOwner());
             assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId());
@@ -95,20 +95,20 @@
         {
             final OwnersTestable owners = new OwnersTestable(getServices());
 
-            DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+            DpmTestUtils.writeToFile(owners.getLegacyConfigFile(),
                     DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test02/input.xml"));
 
             owners.load();
 
             // The legacy file should be removed.
-            assertFalse(owners.getLegacyConfigFileWithTestOverride().exists());
+            assertFalse(owners.getLegacyConfigFile().exists());
 
-            assertTrue(owners.getDeviceOwnerFileWithTestOverride().exists()); // TODO Check content
+            assertTrue(owners.getDeviceOwnerFile().exists()); // TODO Check content
 
-            assertFalse(owners.getProfileOwnerFileWithTestOverride(10).exists());
-            assertFalse(owners.getProfileOwnerFileWithTestOverride(11).exists());
-            assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists());
-            assertFalse(owners.getProfileOwnerFileWithTestOverride(21).exists());
+            assertFalse(owners.getProfileOwnerFile(10).exists());
+            assertFalse(owners.getProfileOwnerFile(11).exists());
+            assertFalse(owners.getProfileOwnerFile(20).exists());
+            assertFalse(owners.getProfileOwnerFile(21).exists());
 
             assertTrue(owners.hasDeviceOwner());
             assertEquals(null, owners.getDeviceOwnerName());
@@ -153,20 +153,20 @@
         {
             final OwnersTestable owners = new OwnersTestable(getServices());
 
-            DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+            DpmTestUtils.writeToFile(owners.getLegacyConfigFile(),
                     DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test03/input.xml"));
 
             owners.load();
 
             // The legacy file should be removed.
-            assertFalse(owners.getLegacyConfigFileWithTestOverride().exists());
+            assertFalse(owners.getLegacyConfigFile().exists());
 
-            assertFalse(owners.getDeviceOwnerFileWithTestOverride().exists());
+            assertFalse(owners.getDeviceOwnerFile().exists());
 
-            assertTrue(owners.getProfileOwnerFileWithTestOverride(10).exists());
-            assertTrue(owners.getProfileOwnerFileWithTestOverride(11).exists());
-            assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists());
-            assertFalse(owners.getProfileOwnerFileWithTestOverride(21).exists());
+            assertTrue(owners.getProfileOwnerFile(10).exists());
+            assertTrue(owners.getProfileOwnerFile(11).exists());
+            assertFalse(owners.getProfileOwnerFile(20).exists());
+            assertFalse(owners.getProfileOwnerFile(21).exists());
 
             assertFalse(owners.hasDeviceOwner());
             assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId());
@@ -231,20 +231,20 @@
         {
             final OwnersTestable owners = new OwnersTestable(getServices());
 
-            DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+            DpmTestUtils.writeToFile(owners.getLegacyConfigFile(),
                     DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test04/input.xml"));
 
             owners.load();
 
             // The legacy file should be removed.
-            assertFalse(owners.getLegacyConfigFileWithTestOverride().exists());
+            assertFalse(owners.getLegacyConfigFile().exists());
 
-            assertTrue(owners.getDeviceOwnerFileWithTestOverride().exists());
+            assertTrue(owners.getDeviceOwnerFile().exists());
 
-            assertTrue(owners.getProfileOwnerFileWithTestOverride(10).exists());
-            assertTrue(owners.getProfileOwnerFileWithTestOverride(11).exists());
-            assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists());
-            assertFalse(owners.getProfileOwnerFileWithTestOverride(21).exists());
+            assertTrue(owners.getProfileOwnerFile(10).exists());
+            assertTrue(owners.getProfileOwnerFile(11).exists());
+            assertFalse(owners.getProfileOwnerFile(20).exists());
+            assertFalse(owners.getProfileOwnerFile(21).exists());
 
             assertTrue(owners.hasDeviceOwner());
             assertEquals(null, owners.getDeviceOwnerName());
@@ -341,20 +341,20 @@
         {
             final OwnersTestable owners = new OwnersTestable(getServices());
 
-            DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+            DpmTestUtils.writeToFile(owners.getLegacyConfigFile(),
                     DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test05/input.xml"));
 
             owners.load();
 
             // The legacy file should be removed.
-            assertFalse(owners.getLegacyConfigFileWithTestOverride().exists());
+            assertFalse(owners.getLegacyConfigFile().exists());
 
             // Note device initializer is no longer supported.  No need to write the DO file.
-            assertFalse(owners.getDeviceOwnerFileWithTestOverride().exists());
+            assertFalse(owners.getDeviceOwnerFile().exists());
 
-            assertFalse(owners.getProfileOwnerFileWithTestOverride(10).exists());
-            assertFalse(owners.getProfileOwnerFileWithTestOverride(11).exists());
-            assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists());
+            assertFalse(owners.getProfileOwnerFile(10).exists());
+            assertFalse(owners.getProfileOwnerFile(11).exists());
+            assertFalse(owners.getProfileOwnerFile(20).exists());
 
             assertFalse(owners.hasDeviceOwner());
             assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId());
@@ -397,19 +397,19 @@
         {
             final OwnersTestable owners = new OwnersTestable(getServices());
 
-            DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+            DpmTestUtils.writeToFile(owners.getLegacyConfigFile(),
                     DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test06/input.xml"));
 
             owners.load();
 
             // The legacy file should be removed.
-            assertFalse(owners.getLegacyConfigFileWithTestOverride().exists());
+            assertFalse(owners.getLegacyConfigFile().exists());
 
-            assertTrue(owners.getDeviceOwnerFileWithTestOverride().exists());
+            assertTrue(owners.getDeviceOwnerFile().exists());
 
-            assertFalse(owners.getProfileOwnerFileWithTestOverride(10).exists());
-            assertFalse(owners.getProfileOwnerFileWithTestOverride(11).exists());
-            assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists());
+            assertFalse(owners.getProfileOwnerFile(10).exists());
+            assertFalse(owners.getProfileOwnerFile(11).exists());
+            assertFalse(owners.getProfileOwnerFile(20).exists());
 
             assertFalse(owners.hasDeviceOwner());
             assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId());
@@ -451,16 +451,16 @@
         final OwnersTestable owners = new OwnersTestable(getServices());
 
         // First, migrate to create new-style config files.
-        DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+        DpmTestUtils.writeToFile(owners.getLegacyConfigFile(),
                 DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test04/input.xml"));
 
         owners.load();
 
-        assertFalse(owners.getLegacyConfigFileWithTestOverride().exists());
+        assertFalse(owners.getLegacyConfigFile().exists());
 
-        assertTrue(owners.getDeviceOwnerFileWithTestOverride().exists());
-        assertTrue(owners.getProfileOwnerFileWithTestOverride(10).exists());
-        assertTrue(owners.getProfileOwnerFileWithTestOverride(11).exists());
+        assertTrue(owners.getDeviceOwnerFile().exists());
+        assertTrue(owners.getProfileOwnerFile(10).exists());
+        assertTrue(owners.getProfileOwnerFile(11).exists());
 
         // Then clear all information and save.
         owners.clearDeviceOwner();
@@ -475,8 +475,8 @@
         owners.writeProfileOwner(21);
 
         // Now all files should be removed.
-        assertFalse(owners.getDeviceOwnerFileWithTestOverride().exists());
-        assertFalse(owners.getProfileOwnerFileWithTestOverride(10).exists());
-        assertFalse(owners.getProfileOwnerFileWithTestOverride(11).exists());
+        assertFalse(owners.getDeviceOwnerFile().exists());
+        assertFalse(owners.getProfileOwnerFile(10).exists());
+        assertFalse(owners.getProfileOwnerFile(11).exists());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/TransferOwnershipMetadataManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/TransferOwnershipMetadataManagerTest.java
new file mode 100644
index 0000000..03cabb2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/TransferOwnershipMetadataManagerTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_DEVICE_OWNER;
+
+
+import static com.android.server.devicepolicy.TransferOwnershipMetadataManager
+        .OWNER_TRANSFER_METADATA_XML;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Environment;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.devicepolicy.TransferOwnershipMetadataManager.Injector;
+import com.android.server.devicepolicy.TransferOwnershipMetadataManager.Metadata;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/**
+* Unit tests for {@link TransferOwnershipMetadataManager}.
+ *
+ * bit FrameworksServicesTests:com.android.server.devicepolicy.TransferOwnershipMetadataManagerTest
+ * runtest -x frameworks/base/services/tests/servicestests/src/com/android/server/devicepolicy/TransferOwnershipMetadataManagerTest.java
+* */
+
+@RunWith(AndroidJUnit4.class)
+public class TransferOwnershipMetadataManagerTest {
+    private final static String ADMIN_PACKAGE = "com.dummy.admin.package";
+    private final static String TARGET_PACKAGE = "com.dummy.target.package";
+    private final static int USER_ID = 123;
+    private final static Metadata TEST_PARAMS = new Metadata(ADMIN_PACKAGE,
+            TARGET_PACKAGE, USER_ID, ADMIN_TYPE_DEVICE_OWNER);
+
+    private MockInjector mMockInjector;
+
+    @Before
+    public void setUp() {
+        mMockInjector = new MockInjector();
+        getOwnerTransferParams().deleteMetadataFile();
+    }
+
+    @Test
+    public void testSave() {
+        TransferOwnershipMetadataManager paramsManager = getOwnerTransferParams();
+        assertTrue(paramsManager.saveMetadataFile(TEST_PARAMS));
+        assertTrue(paramsManager.metadataFileExists());
+    }
+
+    @Test
+    public void testFileContentValid() {
+        TransferOwnershipMetadataManager paramsManager = getOwnerTransferParams();
+        assertTrue(paramsManager.saveMetadataFile(TEST_PARAMS));
+        Path path = Paths.get(new File(mMockInjector.getOwnerTransferMetadataDir(),
+                OWNER_TRANSFER_METADATA_XML).getAbsolutePath());
+        try {
+            String contents = new String(Files.readAllBytes(path), Charset.forName("UTF-8"));
+            assertEquals(
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                    + "<user-id>" + USER_ID + "</user-id>\n"
+                    + "<admin-component>" + ADMIN_PACKAGE + "</admin-component>\n"
+                    + "<target-component>" + TARGET_PACKAGE + "</target-component>\n"
+                    + "<admin-type>" + ADMIN_TYPE_DEVICE_OWNER + "</admin-type>\n",
+                contents);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Test
+    public void testLoad() {
+        TransferOwnershipMetadataManager paramsManager = getOwnerTransferParams();
+        paramsManager.saveMetadataFile(TEST_PARAMS);
+        assertEquals(TEST_PARAMS, paramsManager.loadMetadataFile());
+    }
+
+    @Test
+    public void testDelete() {
+        TransferOwnershipMetadataManager paramsManager = getOwnerTransferParams();
+        paramsManager.saveMetadataFile(TEST_PARAMS);
+        paramsManager.deleteMetadataFile();
+        assertFalse(paramsManager.metadataFileExists());
+    }
+
+    @After
+    public void tearDown() {
+        getOwnerTransferParams().deleteMetadataFile();
+    }
+
+    private TransferOwnershipMetadataManager getOwnerTransferParams() {
+        return new TransferOwnershipMetadataManager(mMockInjector);
+    }
+
+    static class MockInjector extends Injector {
+        @Override
+        public File getOwnerTransferMetadataDir() {
+            return Environment.getExternalStorageDirectory();
+        }
+    }
+
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index e0bebee..9ae6f00 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -1179,6 +1179,34 @@
     }
 
     @Test
+    public void testUpdateAppNotifyCreatorBlock() throws Exception {
+        mService.setRankingHelper(mRankingHelper);
+
+        mBinderService.setNotificationsEnabledForPackage(PKG, 0, false);
+        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+
+        assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED,
+                captor.getValue().getAction());
+        assertEquals(PKG, captor.getValue().getPackage());
+        assertTrue(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false));
+    }
+
+    @Test
+    public void testUpdateAppNotifyCreatorUnblock() throws Exception {
+        mService.setRankingHelper(mRankingHelper);
+
+        mBinderService.setNotificationsEnabledForPackage(PKG, 0, true);
+        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+
+        assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED,
+                captor.getValue().getAction());
+        assertEquals(PKG, captor.getValue().getPackage());
+        assertFalse(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true));
+    }
+
+    @Test
     public void testUpdateChannelNotifyCreatorBlock() throws Exception {
         mService.setRankingHelper(mRankingHelper);
         when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java
index 98ea451..0ee870a 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -251,7 +251,15 @@
      */
     public static final int LISTEN_USER_MOBILE_DATA_STATE                  = 0x00080000;
 
-     /*
+    /**
+     *  Listen for changes to the physical channel configuration.
+     *
+     *  @see #onPhysicalChannelConfigurationChanged
+     *  @hide
+     */
+    public static final int LISTEN_PHYSICAL_CHANNEL_CONFIGURATION          = 0x00100000;
+
+    /*
      * Subscription used to listen to the phone state changes
      * @hide
      */
@@ -362,7 +370,10 @@
                     case LISTEN_CARRIER_NETWORK_CHANGE:
                         PhoneStateListener.this.onCarrierNetworkChange((boolean)msg.obj);
                         break;
-
+                    case LISTEN_PHYSICAL_CHANNEL_CONFIGURATION:
+                        PhoneStateListener.this.onPhysicalChannelConfigurationChanged(
+                            (List<PhysicalChannelConfig>)msg.obj);
+                        break;
                 }
             }
         };
@@ -561,6 +572,16 @@
     }
 
     /**
+     * Callback invoked when the current physical channel configuration has changed
+     *
+     * @param configs List of the current {@link PhysicalChannelConfig}s
+     * @hide
+     */
+    public void onPhysicalChannelConfigurationChanged(List<PhysicalChannelConfig> configs) {
+        // default implementation empty
+    }
+
+    /**
      * Callback invoked when telephony has received notice from a carrier
      * app that a network action that could result in connectivity loss
      * has been requested by an app using
diff --git a/telephony/java/android/telephony/PhysicalChannelConfig.aidl b/telephony/java/android/telephony/PhysicalChannelConfig.aidl
new file mode 100644
index 0000000..651c103
--- /dev/null
+++ b/telephony/java/android/telephony/PhysicalChannelConfig.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 2018, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.telephony;
+
+parcelable PhysicalChannelConfig;
\ No newline at end of file
diff --git a/telephony/java/android/telephony/PhysicalChannelConfig.java b/telephony/java/android/telephony/PhysicalChannelConfig.java
new file mode 100644
index 0000000..651d68d
--- /dev/null
+++ b/telephony/java/android/telephony/PhysicalChannelConfig.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.annotation.IntDef;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * @hide
+ */
+public final class PhysicalChannelConfig implements Parcelable {
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({CONNECTION_PRIMARY_SERVING, CONNECTION_SECONDARY_SERVING})
+    public @interface ConnectionStatus {}
+
+    /**
+     * UE has connection to cell for signalling and possibly data (3GPP 36.331, 25.331).
+     */
+    public static final int CONNECTION_PRIMARY_SERVING = 1;
+
+    /**
+     * UE has connection to cell for data (3GPP 36.331, 25.331).
+     */
+    public static final int CONNECTION_SECONDARY_SERVING = 2;
+
+    /**
+     * Connection status of the cell.
+     *
+     * <p>One of {@link #CONNECTION_PRIMARY_SERVING}, {@link #CONNECTION_SECONDARY_SERVING}.
+     */
+    private int mCellConnectionStatus;
+
+    /**
+     * Cell bandwidth, in kHz.
+     */
+    private int mCellBandwidthDownlinkKhz;
+
+    public PhysicalChannelConfig(int status, int bandwidth) {
+        mCellConnectionStatus = status;
+        mCellBandwidthDownlinkKhz = bandwidth;
+    }
+
+    public PhysicalChannelConfig(Parcel in) {
+        mCellConnectionStatus = in.readInt();
+        mCellBandwidthDownlinkKhz = in.readInt();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mCellConnectionStatus);
+        dest.writeInt(mCellBandwidthDownlinkKhz);
+    }
+
+    /**
+     * @return Cell bandwidth, in kHz
+     */
+    public int getCellBandwidthDownlink() {
+        return mCellBandwidthDownlinkKhz;
+    }
+
+    /**
+     * Gets the connection status of the cell.
+     *
+     * @see #CONNECTION_PRIMARY_SERVING
+     * @see #CONNECTION_SECONDARY_SERVING
+     *
+     * @return Connection status of the cell
+     */
+    @ConnectionStatus
+    public int getConnectionStatus() {
+        return mCellConnectionStatus;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (!(o instanceof PhysicalChannelConfig)) {
+            return false;
+        }
+
+        PhysicalChannelConfig config = (PhysicalChannelConfig) o;
+        return mCellConnectionStatus == config.mCellConnectionStatus
+                && mCellBandwidthDownlinkKhz == config.mCellBandwidthDownlinkKhz;
+    }
+
+    @Override
+    public int hashCode() {
+        return (mCellBandwidthDownlinkKhz * 29) + (mCellConnectionStatus * 31);
+    }
+
+    public static final Parcelable.Creator<PhysicalChannelConfig> CREATOR =
+        new Parcelable.Creator<PhysicalChannelConfig>() {
+            public PhysicalChannelConfig createFromParcel(Parcel in) {
+                return new PhysicalChannelConfig(in);
+            }
+
+            public PhysicalChannelConfig[] newArray(int size) {
+                return new PhysicalChannelConfig[size];
+            }
+        };
+}
diff --git a/tests/FrameworkPerf/AndroidManifest.xml b/tests/FrameworkPerf/AndroidManifest.xml
index 2591aaf..d62ef9e 100644
--- a/tests/FrameworkPerf/AndroidManifest.xml
+++ b/tests/FrameworkPerf/AndroidManifest.xml
@@ -1,5 +1,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.frameworkperf">
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
     <uses-permission android:name="android.permission.WAKE_LOCK" />
     <uses-sdk android:minSdkVersion="5" />
 
diff --git a/tests/OneMedia/AndroidManifest.xml b/tests/OneMedia/AndroidManifest.xml
index c6824ec..8697f1b 100644
--- a/tests/OneMedia/AndroidManifest.xml
+++ b/tests/OneMedia/AndroidManifest.xml
@@ -5,6 +5,7 @@
     android:versionName="1.0" >
 
     <uses-sdk android:minSdkVersion="19"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
 
diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
index 801a396..66e0955 100644
--- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -19,7 +19,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
@@ -229,7 +228,7 @@
                         anyInt(),
                         anyString(),
                         anyString(),
-                        anyLong(),
+                        anyInt(),
                         eq(TEST_SPI),
                         anyInt(),
                         anyInt(),
@@ -264,7 +263,7 @@
                         anyInt(),
                         anyString(),
                         anyString(),
-                        anyLong(),
+                        anyInt(),
                         eq(TEST_SPI),
                         anyInt(),
                         anyInt(),
diff --git a/tests/notification/Android.mk b/tests/notification/Android.mk
index 0669553..255e6e7 100644
--- a/tests/notification/Android.mk
+++ b/tests/notification/Android.mk
@@ -7,7 +7,7 @@
 # Include all test java files.
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs
 LOCAL_PACKAGE_NAME := NotificationTests
 
 LOCAL_SDK_VERSION := 21
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
index e0e6b58..3dbb503 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -317,6 +317,7 @@
     fprintf(out, "\n");
     fprintf(out, "#include <stdint.h>\n");
     fprintf(out, "#include <vector>\n");
+    fprintf(out, "#include <set>\n");
     fprintf(out, "\n");
 
     fprintf(out, "namespace android {\n");
@@ -361,6 +362,36 @@
     fprintf(out, "};\n");
     fprintf(out, "\n");
 
+    fprintf(out, "const static std::set<int> kAtomsWithUidField = {\n");
+    for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+        atom != atoms.decls.end(); atom++) {
+        for (vector<AtomField>::const_iterator field = atom->fields.begin();
+                field != atom->fields.end(); field++) {
+            if (field->name == "uid") {
+                string constant = make_constant_name(atom->name);
+                fprintf(out, " %s,\n", constant.c_str());
+                break;
+            }
+        }
+    }
+    fprintf(out, "};\n");
+    fprintf(out, "\n");
+
+    fprintf(out, "const static std::set<int> kAtomsWithAttributionChain = {\n");
+    for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+        atom != atoms.decls.end(); atom++) {
+        for (vector<AtomField>::const_iterator field = atom->fields.begin();
+                field != atom->fields.end(); field++) {
+            if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+                string constant = make_constant_name(atom->name);
+                fprintf(out, " %s,\n", constant.c_str());
+                break;
+            }
+        }
+    }
+    fprintf(out, "};\n");
+    fprintf(out, "\n");
+
     fprintf(out, "const static int kMaxPushedAtomId = %d;\n\n", maxPushedAtomId);
 
     // Print write methods
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index e9e61a5..101b3e2 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -66,11 +66,11 @@
 
     List<OsuProvider> getMatchingOsuProviders(in ScanResult scanResult);
 
-    int addOrUpdateNetwork(in WifiConfiguration config);
+    int addOrUpdateNetwork(in WifiConfiguration config, String packageName);
 
-    boolean addOrUpdatePasspointConfiguration(in PasspointConfiguration config);
+    boolean addOrUpdatePasspointConfiguration(in PasspointConfiguration config, String packageName);
 
-    boolean removePasspointConfiguration(in String fqdn);
+    boolean removePasspointConfiguration(in String fqdn, String packageName);
 
     List<PasspointConfiguration> getPasspointConfigurations();
 
@@ -80,21 +80,21 @@
 
     void deauthenticateNetwork(long holdoff, boolean ess);
 
-    boolean removeNetwork(int netId);
+    boolean removeNetwork(int netId, String packageName);
 
-    boolean enableNetwork(int netId, boolean disableOthers);
+    boolean enableNetwork(int netId, boolean disableOthers, String packageName);
 
-    boolean disableNetwork(int netId);
+    boolean disableNetwork(int netId, String packageName);
 
-    void startScan(in ScanSettings requested, in WorkSource ws, in String packageName);
+    void startScan(in ScanSettings requested, in WorkSource ws, String packageName);
 
     List<ScanResult> getScanResults(String callingPackage);
 
-    void disconnect();
+    void disconnect(String packageName);
 
-    void reconnect();
+    void reconnect(String packageName);
 
-    void reassociate();
+    void reassociate(String packageName);
 
     WifiInfo getConnectionInfo(String callingPackage);
 
@@ -108,7 +108,7 @@
 
     boolean isDualBandSupported();
 
-    boolean saveConfiguration();
+    boolean saveConfiguration(String packageName);
 
     DhcpInfo getDhcpInfo();
 
@@ -134,7 +134,7 @@
 
     boolean stopSoftAp();
 
-    int startLocalOnlyHotspot(in Messenger messenger, in IBinder binder, in String packageName);
+    int startLocalOnlyHotspot(in Messenger messenger, in IBinder binder, String packageName);
 
     void stopLocalOnlyHotspot();
 
@@ -146,9 +146,9 @@
 
     WifiConfiguration getWifiApConfiguration();
 
-    void setWifiApConfiguration(in WifiConfiguration wifiConfig);
+    void setWifiApConfiguration(in WifiConfiguration wifiConfig, String packageName);
 
-    Messenger getWifiServiceMessenger();
+    Messenger getWifiServiceMessenger(String packageName);
 
     void enableTdls(String remoteIPAddress, boolean enable);
 
@@ -165,9 +165,9 @@
 
     void enableWifiConnectivityManager(boolean enabled);
 
-    void disableEphemeralNetwork(String SSID);
+    void disableEphemeralNetwork(String SSID, String packageName);
 
-    void factoryReset();
+    void factoryReset(String packageName);
 
     Network getCurrentNetwork();
 
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index e4b510d..50ae905 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1132,7 +1132,7 @@
      */
     private int addOrUpdateNetwork(WifiConfiguration config) {
         try {
-            return mService.addOrUpdateNetwork(config);
+            return mService.addOrUpdateNetwork(config, mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1153,7 +1153,7 @@
      */
     public void addOrUpdatePasspointConfiguration(PasspointConfiguration config) {
         try {
-            if (!mService.addOrUpdatePasspointConfiguration(config)) {
+            if (!mService.addOrUpdatePasspointConfiguration(config, mContext.getOpPackageName())) {
                 throw new IllegalArgumentException();
             }
         } catch (RemoteException e) {
@@ -1170,7 +1170,7 @@
      */
     public void removePasspointConfiguration(String fqdn) {
         try {
-            if (!mService.removePasspointConfiguration(fqdn)) {
+            if (!mService.removePasspointConfiguration(fqdn, mContext.getOpPackageName())) {
                 throw new IllegalArgumentException();
             }
         } catch (RemoteException e) {
@@ -1256,7 +1256,7 @@
      */
     public boolean removeNetwork(int netId) {
         try {
-            return mService.removeNetwork(netId);
+            return mService.removeNetwork(netId, mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1302,7 +1302,7 @@
 
         boolean success;
         try {
-            success = mService.enableNetwork(netId, attemptConnect);
+            success = mService.enableNetwork(netId, attemptConnect, mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1328,7 +1328,7 @@
      */
     public boolean disableNetwork(int netId) {
         try {
-            return mService.disableNetwork(netId);
+            return mService.disableNetwork(netId, mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1341,7 +1341,7 @@
      */
     public boolean disconnect() {
         try {
-            mService.disconnect();
+            mService.disconnect(mContext.getOpPackageName());
             return true;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -1356,7 +1356,7 @@
      */
     public boolean reconnect() {
         try {
-            mService.reconnect();
+            mService.reconnect(mContext.getOpPackageName());
             return true;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -1371,7 +1371,7 @@
      */
     public boolean reassociate() {
         try {
-            mService.reassociate();
+            mService.reassociate(mContext.getOpPackageName());
             return true;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -1701,7 +1701,7 @@
     @Deprecated
     public boolean saveConfiguration() {
         try {
-            return mService.saveConfiguration();
+            return mService.saveConfiguration(mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2136,7 +2136,7 @@
     @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE)
     public boolean setWifiApConfiguration(WifiConfiguration wifiConfig) {
         try {
-            mService.setWifiApConfiguration(wifiConfig);
+            mService.setWifiApConfiguration(wifiConfig, mContext.getOpPackageName());
             return true;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -3052,7 +3052,7 @@
     public void disableEphemeralNetwork(String SSID) {
         if (SSID == null) throw new IllegalArgumentException("SSID cannot be null");
         try {
-            mService.disableEphemeralNetwork(SSID);
+            mService.disableEphemeralNetwork(SSID, mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -3093,7 +3093,7 @@
      */
     public Messenger getWifiServiceMessenger() {
         try {
-            return mService.getWifiServiceMessenger();
+            return mService.getWifiServiceMessenger(mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -3619,7 +3619,7 @@
      */
     public void factoryReset() {
         try {
-            mService.factoryReset();
+            mService.factoryReset(mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }