Merge "Create AppComponentFactory.instantiateClassLoader API"
diff --git a/Android.bp b/Android.bp
index abeeb43..3095ca5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -536,6 +536,7 @@
         "telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl",
         "telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl",
         "telephony/java/android/telephony/mbms/vendor/IMbmsGroupCallService.aidl",
+        "telephony/java/android/telephony/ICellInfoCallback.aidl",
         "telephony/java/android/telephony/INetworkService.aidl",
         "telephony/java/android/telephony/INetworkServiceCallback.aidl",
         "telephony/java/com/android/ims/internal/IImsCallSession.aidl",
diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
index 35d3802..f60cbee6 100644
--- a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
@@ -169,6 +169,25 @@
     }
 
     @Test
+    public void testCreate_PrecomputedText_NoStyled_Greedy_NoHyphenation_DirDifferent() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            final PrecomputedText text = makeMeasured(
+                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT,
+                    Layout.BREAK_STRATEGY_SIMPLE, Layout.HYPHENATION_FREQUENCY_NONE);
+            Canvas.freeTextLayoutCaches();
+            state.resumeTiming();
+
+            StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
+                    .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
+                    .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+                    .setTextDirection(TextDirectionHeuristics.RTL)
+                    .build();
+        }
+    }
+
+    @Test
     public void testCreate_PrecomputedText_NoStyled_Greedy_Hyphenation() {
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
diff --git a/api/current.txt b/api/current.txt
index 21ca504..56433f3 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -23253,6 +23253,7 @@
     method public android.media.VolumeShaper createVolumeShaper(android.media.VolumeShaper.Configuration);
     method protected void finalize();
     method public void flush();
+    method public android.media.AudioAttributes getAudioAttributes();
     method public int getAudioFormat();
     method public int getAudioSessionId();
     method public int getBufferCapacityInFrames();
@@ -24467,6 +24468,7 @@
   }
 
   public static final class MediaExtractor.CasInfo {
+    method public byte[] getPrivateData();
     method public android.media.MediaCas.Session getSession();
     method public int getSystemId();
   }
@@ -28983,7 +28985,7 @@
 
   public class WifiManager {
     method public deprecated int addNetwork(android.net.wifi.WifiConfiguration);
-    method public boolean addNetworkSuggestions(java.util.List<android.net.wifi.WifiNetworkSuggestion>, android.app.PendingIntent);
+    method public boolean addNetworkSuggestions(java.util.List<android.net.wifi.WifiNetworkSuggestion>);
     method public void addOrUpdatePasspointConfiguration(android.net.wifi.hotspot2.PasspointConfiguration);
     method public static int calculateSignalLevel(int, int);
     method public deprecated void cancelWps(android.net.wifi.WifiManager.WpsCallback);
@@ -29027,9 +29029,11 @@
     method public deprecated int updateNetwork(android.net.wifi.WifiConfiguration);
     field public static final java.lang.String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK";
     field public static final java.lang.String ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE = "android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE";
+    field public static final java.lang.String ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION = "android.net.wifi.action.WIFI_NETWORK_SUGGESTION_POST_CONNECTION";
     field public static final deprecated int ERROR_AUTHENTICATING = 1; // 0x1
     field public static final deprecated java.lang.String EXTRA_BSSID = "bssid";
     field public static final java.lang.String EXTRA_NETWORK_INFO = "networkInfo";
+    field public static final java.lang.String EXTRA_NETWORK_SUGGESTION = "android.net.wifi.extra.NETWORK_SUGGESTION";
     field public static final java.lang.String EXTRA_NEW_RSSI = "newRssi";
     field public static final deprecated java.lang.String EXTRA_NEW_STATE = "newState";
     field public static final java.lang.String EXTRA_PREVIOUS_WIFI_STATE = "previous_wifi_state";
@@ -43558,6 +43562,7 @@
     method public java.lang.String getCountryIso();
     method public int getDataRoaming();
     method public java.lang.CharSequence getDisplayName();
+    method public java.lang.String getGroupUuid();
     method public java.lang.String getIccId();
     method public int getIconTint();
     method public deprecated int getMcc();
@@ -43565,7 +43570,6 @@
     method public deprecated int getMnc();
     method public java.lang.String getMncString();
     method public java.lang.String getNumber();
-    method public int getParentSubId();
     method public int getSimSlotIndex();
     method public int getSubscriptionId();
     method public boolean isEmbedded();
@@ -43599,6 +43603,7 @@
     method public static boolean isValidSubscriptionId(int);
     method public void removeOnOpportunisticSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener);
     method public void removeOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
+    method public java.lang.String setSubscriptionGroup(int[]);
     method public void setSubscriptionOverrideCongested(int, boolean, long);
     method public void setSubscriptionOverrideUnmetered(int, boolean, long);
     method public void setSubscriptionPlans(int, java.util.List<android.telephony.SubscriptionPlan>);
@@ -43730,6 +43735,7 @@
     method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle);
     method public boolean isWorldPhone();
     method public void listen(android.telephony.PhoneStateListener, int);
+    method public void requestCellInfoUpdate(java.util.concurrent.Executor, android.telephony.TelephonyManager.CellInfoCallback);
     method public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, java.util.concurrent.Executor, android.telephony.TelephonyScanManager.NetworkScanCallback);
     method public void sendDialerSpecialCode(java.lang.String);
     method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
@@ -43832,6 +43838,11 @@
     field public static final java.lang.String VVM_TYPE_OMTP = "vvm_type_omtp";
   }
 
+  public static abstract class TelephonyManager.CellInfoCallback {
+    ctor public TelephonyManager.CellInfoCallback();
+    method public abstract void onCellInfo(java.util.List<android.telephony.CellInfo>);
+  }
+
   public static abstract class TelephonyManager.UssdResponseCallback {
     ctor public TelephonyManager.UssdResponseCallback();
     method public void onReceiveUssdResponse(android.telephony.TelephonyManager, java.lang.String, java.lang.CharSequence);
@@ -43920,6 +43931,7 @@
     method public java.lang.String getApnName();
     method public int getApnTypeBitmask();
     method public int getAuthType();
+    method public int getCarrierId();
     method public java.lang.String getEntryName();
     method public int getId();
     method public deprecated java.net.InetAddress getMmsProxyAddress();
@@ -43970,6 +43982,7 @@
     method public android.telephony.data.ApnSetting.Builder setApnTypeBitmask(int);
     method public android.telephony.data.ApnSetting.Builder setAuthType(int);
     method public android.telephony.data.ApnSetting.Builder setCarrierEnabled(boolean);
+    method public android.telephony.data.ApnSetting.Builder setCarrierId(int);
     method public android.telephony.data.ApnSetting.Builder setEntryName(java.lang.String);
     method public deprecated android.telephony.data.ApnSetting.Builder setMmsProxyAddress(java.net.InetAddress);
     method public android.telephony.data.ApnSetting.Builder setMmsProxyAddress(java.lang.String);
@@ -54361,6 +54374,9 @@
     method public void show(float, float);
     method public void show(float, float, float, float);
     method public void update();
+    field public static final int SOURCE_BOUND_MAX_IN_SURFACE = 0; // 0x0
+    field public static final int SOURCE_BOUND_MAX_IN_VIEW = 1; // 0x1
+    field public static final int SOURCE_BOUND_MAX_VISIBLE = 2; // 0x2
   }
 
   public static class Magnifier.Builder {
@@ -54371,6 +54387,7 @@
     method public android.widget.Magnifier.Builder setElevation(float);
     method public android.widget.Magnifier.Builder setForcePositionWithinWindowSystemInsetsBounds(boolean);
     method public android.widget.Magnifier.Builder setSize(int, int);
+    method public android.widget.Magnifier.Builder setSourceBounds(int, int, int, int);
     method public android.widget.Magnifier.Builder setZoom(float);
   }
 
diff --git a/api/system-current.txt b/api/system-current.txt
index 8eb5507..d84b2ed 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -123,6 +123,7 @@
     field public static final java.lang.String MOUNT_FORMAT_FILESYSTEMS = "android.permission.MOUNT_FORMAT_FILESYSTEMS";
     field public static final java.lang.String MOUNT_UNMOUNT_FILESYSTEMS = "android.permission.MOUNT_UNMOUNT_FILESYSTEMS";
     field public static final java.lang.String MOVE_PACKAGE = "android.permission.MOVE_PACKAGE";
+    field public static final java.lang.String NETWORK_MANAGED_PROVISIONING = "android.permission.NETWORK_MANAGED_PROVISIONING";
     field public static final java.lang.String NETWORK_SETUP_WIZARD = "android.permission.NETWORK_SETUP_WIZARD";
     field public static final java.lang.String NOTIFICATION_DURING_SETUP = "android.permission.NOTIFICATION_DURING_SETUP";
     field public static final java.lang.String NOTIFY_TV_INPUTS = "android.permission.NOTIFY_TV_INPUTS";
@@ -1206,6 +1207,7 @@
   public abstract class PackageManager {
     method public abstract void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
     method public abstract boolean arePermissionsIndividuallyControlled();
+    method public boolean canSuspendPackage(java.lang.String);
     method public abstract java.util.List<android.content.IntentFilter> getAllIntentFilters(java.lang.String);
     method public android.content.pm.dex.ArtManager getArtManager();
     method public abstract java.lang.String getDefaultBrowserPackageNameAsUser(int);
@@ -3640,17 +3642,16 @@
 
   public class WifiManager {
     method public void connect(android.net.wifi.WifiConfiguration, android.net.wifi.WifiManager.ActionListener);
+    method public java.util.List<android.net.wifi.WifiConfiguration> getAllMatchingWifiConfigs(java.util.List<android.net.wifi.ScanResult>);
+    method public java.util.List<android.net.wifi.hotspot2.OsuProvider> getMatchingOsuProviders(java.util.List<android.net.wifi.ScanResult>);
     method public java.util.List<android.net.wifi.WifiConfiguration> getPrivilegedConfiguredNetworks();
     method public android.net.wifi.WifiConfiguration getWifiApConfiguration();
     method public int getWifiApState();
     method public boolean isDeviceToApRttSupported();
     method public boolean isDeviceToDeviceRttSupported();
-    method public boolean isOweSupported();
     method public boolean isPortableHotspotSupported();
     method public boolean isWifiApEnabled();
     method public boolean isWifiScannerSupported();
-    method public boolean isWpa3SaeSupported();
-    method public boolean isWpa3SuiteBSupported();
     method public void registerNetworkRequestMatchCallback(android.net.wifi.WifiManager.NetworkRequestMatchCallback, android.os.Handler);
     method public boolean setWifiApConfiguration(android.net.wifi.WifiConfiguration);
     method public boolean startScan(android.os.WorkSource);
@@ -4110,6 +4111,7 @@
   }
 
   public final class PowerManager {
+    method public void dream(long);
     method public int getPowerSaveMode();
     method public boolean setDynamicPowerSavings(boolean, int);
     method public boolean setPowerSaveMode(boolean);
@@ -4654,6 +4656,11 @@
     field public static final int ID_TYPE_SERIAL = 1; // 0x1
   }
 
+  public class DeviceIdAttestationException extends java.lang.Exception {
+    ctor public DeviceIdAttestationException(java.lang.String);
+    ctor public DeviceIdAttestationException(java.lang.String, java.lang.Throwable);
+  }
+
 }
 
 package android.security.keystore.recovery {
@@ -4784,6 +4791,16 @@
 
 }
 
+package android.service.carrier {
+
+  public abstract class ApnService extends android.app.Service {
+    ctor public ApnService();
+    method public abstract java.util.List<android.content.ContentValues> onRestoreApns(int);
+    method public android.os.IBinder onBind(android.content.Intent);
+  }
+
+}
+
 package android.service.euicc {
 
   public final class EuiccProfileInfo implements android.os.Parcelable {
@@ -5697,6 +5714,7 @@
     method public deprecated boolean isVisualVoicemailEnabled(android.telecom.PhoneAccountHandle);
     method public boolean needsOtaServiceProvisioning();
     method public boolean rebootRadio();
+    method public void requestCellInfoUpdate(android.os.WorkSource, java.util.concurrent.Executor, android.telephony.TelephonyManager.CellInfoCallback);
     method public boolean resetRadioConfig();
     method public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>);
     method public void setCarrierDataEnabled(boolean);
@@ -6687,7 +6705,7 @@
     field public static final int PROCESS_CALL_IMS = 0; // 0x0
   }
 
-  public static class MmTelFeature.MmTelCapabilities {
+  public static class MmTelFeature.MmTelCapabilities extends android.telephony.ims.feature.ImsFeature.Capabilities {
     ctor public MmTelFeature.MmTelCapabilities();
     ctor public deprecated MmTelFeature.MmTelCapabilities(android.telephony.ims.feature.ImsFeature.Capabilities);
     ctor public MmTelFeature.MmTelCapabilities(int);
diff --git a/api/test-current.txt b/api/test-current.txt
index 5531014..1c01cf1 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1037,6 +1037,11 @@
     field public static final int ID_TYPE_SERIAL = 1; // 0x1
   }
 
+  public class DeviceIdAttestationException extends java.lang.Exception {
+    ctor public DeviceIdAttestationException(java.lang.String);
+    ctor public DeviceIdAttestationException(java.lang.String, java.lang.Throwable);
+  }
+
   public static final class KeyGenParameterSpec.Builder {
     method public android.security.keystore.KeyGenParameterSpec.Builder setUniqueIdIncluded(boolean);
   }
diff --git a/cmds/screencap/Android.bp b/cmds/screencap/Android.bp
new file mode 100644
index 0000000..248c675
--- /dev/null
+++ b/cmds/screencap/Android.bp
@@ -0,0 +1,21 @@
+cc_binary {
+    name: "screencap",
+
+    srcs: ["screencap.cpp"],
+
+    shared_libs: [
+        "libcutils",
+        "libutils",
+        "libbinder",
+        "libhwui",
+        "libui",
+        "libgui",
+    ],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wunused",
+        "-Wunreachable-code",
+    ],
+}
diff --git a/cmds/screencap/Android.mk b/cmds/screencap/Android.mk
deleted file mode 100644
index 72e3c56..0000000
--- a/cmds/screencap/Android.mk
+++ /dev/null
@@ -1,21 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES:= \
-    screencap.cpp
-
-LOCAL_SHARED_LIBRARIES := \
-    libcutils \
-    libutils \
-    libbinder \
-    libhwui \
-    libui \
-    libgui
-
-LOCAL_MODULE:= screencap
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
-
-include $(BUILD_EXECUTABLE)
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index e6efc4c..1108844 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -27,6 +27,7 @@
 import "frameworks/base/core/proto/android/bluetooth/enums.proto";
 import "frameworks/base/core/proto/android/os/enums.proto";
 import "frameworks/base/core/proto/android/server/enums.proto";
+import "frameworks/base/core/proto/android/server/location/enums.proto";
 import "frameworks/base/core/proto/android/service/procstats_enum.proto";
 import "frameworks/base/core/proto/android/stats/enums.proto";
 import "frameworks/base/core/proto/android/stats/launcher/launcher.proto";
@@ -118,7 +119,7 @@
         ResourceConfigurationChanged resource_configuration_changed = 66;
         BluetoothEnabledStateChanged bluetooth_enabled_state_changed = 67;
         BluetoothConnectionStateChanged bluetooth_connection_state_changed = 68;
-        // 69 is blank but need not be.
+        GpsSignalQualityChanged gps_signal_quality_changed = 69;
         UsbConnectorStateChanged usb_connector_state_changed = 70;
         SpeakerImpedanceReported speaker_impedance_reported = 71;
         HardwareFailed hardware_failed = 72;
@@ -153,6 +154,8 @@
         // Consider removing this if it becomes a problem
         ServiceStateChanged service_state_changed = 99;
         ServiceLaunchReported service_launch_reported = 100;
+        PhenotypeFlagStateChanged phenotype_flag_state_changed = 101;
+        BinaryPushStateChanged binary_push_state_changed = 102;
     }
 
     // Pulled events will start at field 10000.
@@ -494,7 +497,6 @@
     optional State state = 3;
 }
 
-
 /**
  * Logs when GPS state changes.
  *
@@ -511,6 +513,16 @@
     optional State state = 2;
 }
 
+/**
+ * Logs when GPS signal quality.
+ *
+ * Logged from:
+ *   /frameworks/base/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
+ */
+message GpsSignalQualityChanged {
+    optional android.server.location.GpsSignalQualityEnum level = 1;
+}
+
 
 /**
  * Logs when a sync manager sync state changes.
@@ -1582,6 +1594,8 @@
         FOREGROUND = 2;
     }
     optional ForegroundState foreground_state = 7;
+
+    optional android.server.ErrorSource error_source = 8;
 }
 
 /**
@@ -1600,6 +1614,8 @@
 
     // The pid if available. -1 means not available.
     optional sint32 pid = 4;
+
+    optional android.server.ErrorSource error_source = 5;
 }
 
 /**
@@ -1637,6 +1653,10 @@
         FOREGROUND = 2;
     }
     optional ForegroundState foreground_state = 6;
+
+    optional android.server.ErrorSource error_source = 7;
+
+    optional string package_name = 8;
 }
 
 /**
@@ -2139,6 +2159,43 @@
 }
 
 /*
+ * Logs when a flag flip state changes.
+ * Logged in P/h.
+ */
+message PhenotypeFlagStateChanged {
+    // Mendel configuration name.
+    optional string mendel_config_name = 1;
+    // State
+    enum State {
+        STATE_UNKNOWN = 0;
+        COMMITTED = 1;
+    }
+    optional State state = 2;
+}
+
+/*
+ * Logs when a binary push state changes.
+ * Logged in Play store
+ */
+message BinaryPushStateChanged {
+    // Binary package name.
+    optional string binary_name = 1;
+    // Version code.
+    optional int64 version = 2;
+    // State
+    enum State {
+        STATE_UNKNOWN = 0;
+        DOWNLOAD_START = 1;
+        DOWNLOAD_DONE = 2;
+        INSTALL_START = 3;
+        INSTALL_DONE = 4;
+        REBOOT_START = 5;
+        REBOOT_DONE = 6;
+    }
+    optional State state = 3;
+}
+
+/*
  * Logs when a connection becomes available and lost.
  * Logged in StatsCompanionService.java
  */
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index 60bd4a7..737408d 100644
--- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -608,15 +608,6 @@
             .WillOnce(Invoke([](int tagId, int64_t timeNs,
                                 vector<std::shared_ptr<LogEvent>>* data) {
                 data->clear();
-                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 3);
-                event->write(3);
-                event->init();
-                data->push_back(event);
-                return true;
-            }))
-            .WillOnce(Invoke([](int tagId, int64_t timeNs,
-                                vector<std::shared_ptr<LogEvent>>* data) {
-                data->clear();
                 shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
                 event->write(4);
                 event->init();
@@ -631,7 +622,8 @@
                 event->init();
                 data->push_back(event);
                 return true;
-            }));
+            }))
+            .WillOnce(Return(true));
 
     int triggerId = 5;
     GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
@@ -640,43 +632,28 @@
                                       pullerManager);
 
     vector<shared_ptr<LogEvent>> allData;
-    allData.clear();
 
-    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    EXPECT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size());
     LogEvent trigger(triggerId, bucketStartTimeNs + 10);
     trigger.init();
     gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger);
-    EXPECT_EQ(2UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size());
+    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size());
     trigger.setElapsedTimestampNs(bucketStartTimeNs + 20);
     gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger);
-    EXPECT_EQ(3UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size());
+    EXPECT_EQ(2UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size());
+    trigger.setElapsedTimestampNs(bucket2StartTimeNs + 1);
+    gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger);
 
-    allData.clear();
-    shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    event->write(10);
-    event->init();
-    allData.push_back(event);
-
-    gaugeProducer.onDataPulled(allData);
-    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
-    auto it = gaugeProducer.mCurrentSlicedBucket->begin()->second.front().mFields->begin();
-    EXPECT_EQ(INT, it->mValue.getType());
-    EXPECT_EQ(10, it->mValue.int_value);
     EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size());
-    EXPECT_EQ(3UL, gaugeProducer.mPastBuckets.begin()->second.back().mGaugeAtoms.size());
-    EXPECT_EQ(3, gaugeProducer.mPastBuckets.begin()
+    EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.back().mGaugeAtoms.size());
+    EXPECT_EQ(4, gaugeProducer.mPastBuckets.begin()
                          ->second.back()
                          .mGaugeAtoms[0]
                          .mFields->begin()
                          ->mValue.int_value);
-    EXPECT_EQ(4, gaugeProducer.mPastBuckets.begin()
-                         ->second.back()
-                         .mGaugeAtoms[1]
-                         .mFields->begin()
-                         ->mValue.int_value);
     EXPECT_EQ(5, gaugeProducer.mPastBuckets.begin()
                          ->second.back()
-                         .mGaugeAtoms[2]
+                         .mGaugeAtoms[1]
                          .mFields->begin()
                          ->mValue.int_value);
 }
@@ -731,7 +708,8 @@
                 event->init();
                 data->push_back(event);
                 return true;
-            }));
+            }))
+            .WillOnce(Return(true));
 
     int triggerId = 5;
     GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
@@ -740,30 +718,21 @@
                                       pullerManager);
 
     vector<shared_ptr<LogEvent>> allData;
-    allData.clear();
 
-    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
-    LogEvent trigger(triggerId, bucketStartTimeNs + 10);
+    LogEvent trigger(triggerId, bucketStartTimeNs + 3);
     trigger.init();
     gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger);
+    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    trigger.setElapsedTimestampNs(bucketStartTimeNs + 10);
+    gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger);
     EXPECT_EQ(2UL, gaugeProducer.mCurrentSlicedBucket->size());
     EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size());
     trigger.setElapsedTimestampNs(bucketStartTimeNs + 20);
     gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger);
     EXPECT_EQ(2UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size());
+    trigger.setElapsedTimestampNs(bucket2StartTimeNs + 1);
+    gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger);
 
-    allData.clear();
-    shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    event->write(4);
-    event->write(11);
-    event->init();
-    allData.push_back(event);
-
-    gaugeProducer.onDataPulled(allData);
-    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
-    auto it = gaugeProducer.mCurrentSlicedBucket->begin()->second.front().mFields->begin();
-    EXPECT_EQ(INT, it->mValue.getType());
-    EXPECT_EQ(11, it->mValue.int_value);
     EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.size());
     auto bucketIt = gaugeProducer.mPastBuckets.begin();
     EXPECT_EQ(1UL, bucketIt->second.back().mGaugeAtoms.size());
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 14597ee..550e795 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -6171,6 +6171,7 @@
 libcore.reflect.TypeVariableImpl
 libcore.reflect.Types
 libcore.reflect.WildcardTypeImpl
+libcore.timezone.TimeZoneDataFiles
 libcore.util.BasicLruCache
 libcore.util.CharsetUtils
 libcore.util.CollectionUtils
@@ -6180,7 +6181,6 @@
 libcore.util.NativeAllocationRegistry$CleanerThunk
 libcore.util.Objects
 libcore.util.SneakyThrow
-libcore.util.TimeZoneDataFiles
 libcore.util.ZoneInfo
 libcore.util.ZoneInfo$CheckedArithmeticException
 libcore.util.ZoneInfo$WallTime
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index fcd9a05..8bb704d 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2277,6 +2277,15 @@
     }
 
     @Override
+    public boolean canSuspendPackage(String packageName) {
+        try {
+            return mPM.canSuspendPackageForUser(packageName, mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
     public Bundle getSuspendedPackageAppExtras() {
         final PersistableBundle extras;
         try {
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 4de1dfc..949cdd6 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -42,6 +42,7 @@
 import android.graphics.Point;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.DeadObjectException;
@@ -3243,8 +3244,18 @@
         Objects.requireNonNull(uri);
         Objects.requireNonNull(size);
 
+        // Older apps might be relying on mutable results, so only consider
+        // giving them hardware bitmaps once they target Q or higher. If modern
+        // apps need mutable thumbnails, they can always roll their own logic.
+        final int allocator;
+        if (getTargetSdkVersion() >= Build.VERSION_CODES.Q) {
+            allocator = ImageDecoder.ALLOCATOR_DEFAULT;
+        } else {
+            allocator = ImageDecoder.ALLOCATOR_SOFTWARE;
+        }
+
         try (ContentProviderClient client = acquireContentProviderClient(uri)) {
-            return loadThumbnail(client, uri, size, signal, ImageDecoder.ALLOCATOR_DEFAULT);
+            return loadThumbnail(client, uri, size, signal, allocator);
         }
     }
 
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index a87ee57..d0eff2e 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -276,6 +276,8 @@
             in PersistableBundle appExtras, in PersistableBundle launcherExtras,
             in SuspendDialogInfo dialogInfo, String callingPackage, int userId);
 
+    boolean canSuspendPackageForUser(String packageName, int userId);
+
     boolean isPackageSuspendedForUser(String packageName, int userId);
 
     PersistableBundle getSuspendedPackageAppExtras(String packageName, int userId);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index d3e4045..e14b17e 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5797,6 +5797,30 @@
     }
 
     /**
+     * Returns whether or not a given package can be suspended via a call to {@link
+     * #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle,
+     * SuspendDialogInfo) setPackagesSuspended}. The platform prevents suspending certain critical
+     * packages to keep the device in a functioning state, e.g. the default dialer.
+     * Apps need to hold {@link Manifest.permission#SUSPEND_APPS SUSPEND_APPS} to call this api.
+     *
+     * <p>
+     * Note that this set of critical packages can change with time, so <em>a value of {@code true}
+     * returned by this api does not guarantee that a following call to {@link
+     * #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle,
+     * SuspendDialogInfo) setPackagesSuspended} for the same package will succeed</em>, especially
+     * if considerable time elapsed between the two calls.
+     *
+     * @param packageName The package to check.
+     * @return {@code true} if the given package can be suspended, {@code false} otherwise.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.SUSPEND_APPS)
+    public boolean canSuspendPackage(@NonNull String packageName) {
+        throw new UnsupportedOperationException("canSuspendPackage not implemented");
+    }
+
+    /**
      * @see #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle, String)
      * @param packageName The name of the package to get the suspended status of.
      * @param userId The user id.
diff --git a/core/java/android/database/TranslatingCursor.java b/core/java/android/database/TranslatingCursor.java
new file mode 100644
index 0000000..58e65b2
--- /dev/null
+++ b/core/java/android/database/TranslatingCursor.java
@@ -0,0 +1,229 @@
+/*
+ * 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.database;
+
+import android.annotation.NonNull;
+import android.content.ContentResolver;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.os.CancellationSignal;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Cursor that supports deprecation of {@code _data} like columns which represent raw filepaths,
+ * typically by replacing values with fake paths that the OS then offers to redirect to
+ * {@link ContentResolver#openFileDescriptor(Uri, String)}, which developers
+ * should be using directly.
+ *
+ * @hide
+ */
+public class TranslatingCursor extends CrossProcessCursorWrapper {
+    public static class Config {
+        public final Uri baseUri;
+        public final String idColumn;
+        public final String[] filePathColumns;
+
+        public Config(Uri baseUri, String idColumn, String... filePathColumns) {
+            this.baseUri = baseUri;
+            this.idColumn = idColumn;
+            this.filePathColumns = filePathColumns;
+        }
+    }
+
+    public interface Translator {
+        String translate(String data, long id);
+    }
+
+    private final @NonNull Config mConfig;
+    private final @NonNull Translator mTranslator;
+    private final boolean mDropLast;
+
+    private final int mIdIndex;
+    private final int[] mFilePathColIndices;
+
+    private TranslatingCursor(@NonNull Cursor cursor, @NonNull Config config,
+            @NonNull Translator translator, boolean dropLast) {
+        super(cursor);
+
+        mConfig = Objects.requireNonNull(config);
+        mTranslator = Objects.requireNonNull(translator);
+        mDropLast = dropLast;
+
+        mIdIndex = cursor.getColumnIndexOrThrow(config.idColumn);
+        mFilePathColIndices = new int[config.filePathColumns.length];
+        for (int i = mFilePathColIndices.length - 1; i >= 0; --i) {
+            mFilePathColIndices[i] = cursor.getColumnIndex(config.filePathColumns[i]);
+        }
+    }
+
+    @Override
+    public int getColumnCount() {
+        if (mDropLast) {
+            return super.getColumnCount() - 1;
+        } else {
+            return super.getColumnCount();
+        }
+    }
+
+    @Override
+    public String[] getColumnNames() {
+        if (mDropLast) {
+            return Arrays.copyOfRange(super.getColumnNames(), 0, super.getColumnCount() - 1);
+        } else {
+            return super.getColumnNames();
+        }
+    }
+
+    public static Cursor query(@NonNull Config config, @NonNull Translator translator,
+            SQLiteQueryBuilder qb, SQLiteDatabase db, String[] projectionIn, String selection,
+            String[] selectionArgs, String groupBy, String having, String sortOrder, String limit,
+            CancellationSignal signal) {
+        final boolean requestedId = ArrayUtils.isEmpty(projectionIn)
+                || ArrayUtils.contains(projectionIn, config.idColumn);
+        final boolean requestedData = ArrayUtils.isEmpty(projectionIn)
+                || ArrayUtils.containsAny(projectionIn, config.filePathColumns);
+
+        // If caller didn't request data, we have nothing to redirect
+        if (!requestedData || !ContentResolver.DEPRECATE_DATA_COLUMNS) {
+            return qb.query(db, projectionIn, selection, selectionArgs,
+                    groupBy, having, sortOrder, limit, signal);
+        }
+
+        // If caller didn't request id, we need to splice it in
+        if (!requestedId) {
+            projectionIn = ArrayUtils.appendElement(String.class, projectionIn,
+                    config.idColumn);
+        }
+
+        final Cursor c = qb.query(db, projectionIn, selection, selectionArgs,
+                groupBy, having, sortOrder);
+        return new TranslatingCursor(c, config, translator, !requestedId);
+    }
+
+    @Override
+    public void fillWindow(int position, CursorWindow window) {
+        // Fill window directly to ensure data is rewritten
+        DatabaseUtils.cursorFillWindow(this, position, window);
+    }
+
+    @Override
+    public CursorWindow getWindow() {
+        // Returning underlying window risks leaking data
+        return null;
+    }
+
+    @Override
+    public Cursor getWrappedCursor() {
+        throw new UnsupportedOperationException(
+                "Returning underlying cursor risks leaking data");
+    }
+
+    @Override
+    public double getDouble(int columnIndex) {
+        if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+            throw new IllegalArgumentException();
+        } else {
+            return super.getDouble(columnIndex);
+        }
+    }
+
+    @Override
+    public float getFloat(int columnIndex) {
+        if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+            throw new IllegalArgumentException();
+        } else {
+            return super.getFloat(columnIndex);
+        }
+    }
+
+    @Override
+    public int getInt(int columnIndex) {
+        if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+            throw new IllegalArgumentException();
+        } else {
+            return super.getInt(columnIndex);
+        }
+    }
+
+    @Override
+    public long getLong(int columnIndex) {
+        if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+            throw new IllegalArgumentException();
+        } else {
+            return super.getLong(columnIndex);
+        }
+    }
+
+    @Override
+    public short getShort(int columnIndex) {
+        if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+            throw new IllegalArgumentException();
+        } else {
+            return super.getShort(columnIndex);
+        }
+    }
+
+    @Override
+    public String getString(int columnIndex) {
+        if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+            return mTranslator.translate(super.getString(columnIndex), super.getLong(mIdIndex));
+        } else {
+            return super.getString(columnIndex);
+        }
+    }
+
+    @Override
+    public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
+        if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+            throw new IllegalArgumentException();
+        } else {
+            super.copyStringToBuffer(columnIndex, buffer);
+        }
+    }
+
+    @Override
+    public byte[] getBlob(int columnIndex) {
+        if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+            throw new IllegalArgumentException();
+        } else {
+            return super.getBlob(columnIndex);
+        }
+    }
+
+    @Override
+    public int getType(int columnIndex) {
+        if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+            return Cursor.FIELD_TYPE_STRING;
+        } else {
+            return super.getType(columnIndex);
+        }
+    }
+
+    @Override
+    public boolean isNull(int columnIndex) {
+        if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+            return getString(columnIndex) == null;
+        } else {
+            return super.isNull(columnIndex);
+        }
+    }
+}
diff --git a/core/java/android/hardware/location/ContextHubClient.java b/core/java/android/hardware/location/ContextHubClient.java
index 2717c4e..a83a33e 100644
--- a/core/java/android/hardware/location/ContextHubClient.java
+++ b/core/java/android/hardware/location/ContextHubClient.java
@@ -19,7 +19,6 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.app.PendingIntent;
-import android.content.Intent;
 import android.os.RemoteException;
 
 import com.android.internal.util.Preconditions;
@@ -49,13 +48,25 @@
      */
     private final ContextHubInfo mAttachedHub;
 
-    private final CloseGuard mCloseGuard = CloseGuard.get();
+    private final CloseGuard mCloseGuard;
 
     private final AtomicBoolean mIsClosed = new AtomicBoolean(false);
 
-    /* package */ ContextHubClient(ContextHubInfo hubInfo) {
+    /*
+     * True if this is a persistent client (i.e. does not have to close the connection when the
+     * resource is freed from the system).
+     */
+    private final boolean mPersistent;
+
+    /* package */ ContextHubClient(ContextHubInfo hubInfo, boolean persistent) {
         mAttachedHub = hubInfo;
-        mCloseGuard.open("close");
+        mPersistent = persistent;
+        if (mPersistent) {
+            mCloseGuard = null;
+        } else {
+            mCloseGuard = CloseGuard.get();
+            mCloseGuard.open("close");
+        }
     }
 
     /**
@@ -88,11 +99,18 @@
      * Closes the connection for this client and the Context Hub Service.
      *
      * When this function is invoked, the messaging associated with this client is invalidated.
-     * All futures messages targeted for this client are dropped at the service.
+     * All futures messages targeted for this client are dropped at the service, and the
+     * ContextHubClient is unregistered from the service.
+     *
+     * If this object has a PendingIntent, i.e. the object was generated via
+     * {@link ContextHubManager.createClient(PendingIntent, ContextHubInfo, long)}, then the
+     * Intent events corresponding to the PendingIntent will no longer be triggered.
      */
     public void close() {
         if (!mIsClosed.getAndSet(true)) {
-            mCloseGuard.close();
+            if (mCloseGuard != null) {
+                mCloseGuard.close();
+            }
             try {
                 mClientProxy.close();
             } catch (RemoteException e) {
@@ -102,72 +120,6 @@
     }
 
     /**
-     * Registers to receive persistent intents for a given nanoapp.
-     *
-     * This method should be used if the caller wants to receive notifications even after the
-     * process exits. The client must have an open connection with the Context Hub Service (i.e. it
-     * cannot have been closed through the {@link #close()} method). Only one PendingIntent can be
-     * registered at a time for a single ContextHubClient, and the PendingIntent cannot be
-     * registered if already registered by a ContextHubClient. If registered successfully, intents
-     * will be delivered regarding events for the specified nanoapp from the attached Context Hub.
-     * Any unicast messages for this client will also be delivered. The intent will have an extra
-     * {@link ContextHubManager.EXTRA_CONTEXT_HUB_INFO} of type {@link ContextHubInfo}, which
-     * describes the Context Hub the intent event was for. The intent will also have an extra
-     * {@link ContextHubManager.EXTRA_EVENT_TYPE} of type {@link ContextHubManager.Event}, which
-     * will contain the type of the event. See {@link ContextHubManager.Event} for description of
-     * each event type, along with event-specific extra fields. A client can use
-     * {@link ContextHubIntentEvent.fromIntent(Intent)} to parse the Intent generated by the event.
-     *
-     * When the intent is received, this client can be recreated through
-     * {@link ContextHubManager.createClient(PendingIntent, ContextHubInfo,
-     * ContextHubClientCallback, Exectutor)}. When recreated, the client can be treated as the
-     * same endpoint entity from a nanoapp's perspective, and can be continued to be used to send
-     * messages even if the original process has exited.
-     *
-     * Intents will be delivered until it is unregistered through
-     * {@link #unregisterIntent(PendingIntent)}. Note that the registration of this client will
-     * continued to be maintained at the Context Hub Service until
-     * {@link #unregisterIntent(PendingIntent)} is called for registered intents.
-     *
-     * @param pendingIntent the PendingIntent to register for this client
-     * @param nanoAppId     the unique ID of the nanoapp to receive events for
-     * @return true on success, false otherwise
-     *
-     * @hide
-     */
-    @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
-    public boolean registerIntent(@NonNull PendingIntent pendingIntent, long nanoAppId) {
-        Preconditions.checkNotNull(pendingIntent, "PendingIntent cannot be null");
-
-        try {
-            return mClientProxy.registerIntent(pendingIntent, nanoAppId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Unregisters an intent previously registered via {@link #registerIntent(PendingIntent, long)}.
-     * If this intent has not been registered for this client, this method returns false.
-     *
-     * @param pendingIntent the PendingIntent to unregister
-     *
-     * @return true on success, false otherwise
-     *
-     * @hide
-     */
-    @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
-    public boolean unregisterIntent(@NonNull PendingIntent pendingIntent) {
-        Preconditions.checkNotNull(pendingIntent, "PendingIntent cannot be null");
-
-        try {
-            return mClientProxy.unregisterIntent(pendingIntent);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Sends a message to a nanoapp through the Context Hub Service.
      *
      * This function returns RESULT_SUCCESS if the message has reached the HAL, but
@@ -200,7 +152,9 @@
             if (mCloseGuard != null) {
                 mCloseGuard.warnIfOpen();
             }
-            close();
+            if (!mPersistent) {
+                close();
+            }
         } finally {
             super.finalize();
         }
diff --git a/core/java/android/hardware/location/ContextHubInfo.java b/core/java/android/hardware/location/ContextHubInfo.java
index 36123e3..51daa92 100644
--- a/core/java/android/hardware/location/ContextHubInfo.java
+++ b/core/java/android/hardware/location/ContextHubInfo.java
@@ -15,6 +15,7 @@
  */
 package android.hardware.location;
 
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.hardware.contexthub.V1_0.ContextHub;
 import android.os.Parcel;
@@ -267,6 +268,34 @@
         return retVal;
     }
 
+    @Override
+    public boolean equals(@Nullable Object object) {
+        if (object == this) {
+            return true;
+        }
+
+        boolean isEqual = false;
+        if (object instanceof ContextHubInfo) {
+            ContextHubInfo other = (ContextHubInfo) object;
+            isEqual = (other.getId() == mId)
+                    && other.getName().equals(mName)
+                    && other.getVendor().equals(mVendor)
+                    && other.getToolchain().equals(mToolchain)
+                    && (other.getToolchainVersion() == mToolchainVersion)
+                    && (other.getStaticSwVersion() == getStaticSwVersion())
+                    && (other.getChrePlatformId() == mChrePlatformId)
+                    && (other.getPeakMips() == mPeakMips)
+                    && (other.getStoppedPowerDrawMw() == mStoppedPowerDrawMw)
+                    && (other.getSleepPowerDrawMw() == mSleepPowerDrawMw)
+                    && (other.getPeakPowerDrawMw() == mPeakPowerDrawMw)
+                    && (other.getMaxPacketLengthBytes() == mMaxPacketLengthBytes)
+                    && Arrays.equals(other.getSupportedSensors(), mSupportedSensors)
+                    && Arrays.equals(other.getMemoryRegions(), mMemoryRegions);
+        }
+
+        return isEqual;
+    }
+
     private ContextHubInfo(Parcel in) {
         mId = in.readInt();
         mName = in.readString();
diff --git a/core/java/android/hardware/location/ContextHubIntentEvent.java b/core/java/android/hardware/location/ContextHubIntentEvent.java
index 96e7496..539c494 100644
--- a/core/java/android/hardware/location/ContextHubIntentEvent.java
+++ b/core/java/android/hardware/location/ContextHubIntentEvent.java
@@ -16,6 +16,7 @@
 package android.hardware.location;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.content.Intent;
 
@@ -23,8 +24,9 @@
 
 /**
  * A helper class to retrieve information about a Intent event received for a PendingIntent
- * registered through {@link ContextHubClient.registerIntent(PendingIntent, long)}. This object
- * can only be created through the factory method {@link ContextHubIntentEvent.fromIntent(Intent)}.
+ * registered with {@link ContextHubManager.createClient(ContextHubInfo, PendingIntent, long)}.
+ * This object can only be created through the factory method
+ * {@link ContextHubIntentEvent.fromIntent(Intent)}.
  *
  * @hide
  */
@@ -76,7 +78,7 @@
 
     /**
      * Creates a ContextHubIntentEvent object from an Intent received through a PendingIntent
-     * registered through {@link ContextHubClient.registerIntent(PendingIntent, long)}.
+     * registered with {@link ContextHubManager.createClient(ContextHubInfo, PendingIntent, long)}.
      *
      * @param intent the Intent object from an Intent event
      * @return the ContextHubIntentEvent object describing the event
@@ -206,6 +208,37 @@
         return out + "]";
     }
 
+    @Override
+    public boolean equals(@Nullable Object object) {
+        if (object == this) {
+            return true;
+        }
+
+        boolean isEqual = false;
+        if (object instanceof ContextHubIntentEvent) {
+            ContextHubIntentEvent other = (ContextHubIntentEvent) object;
+            if (other.getEventType() == mEventType
+                    && other.getContextHubInfo().equals(mContextHubInfo)) {
+                isEqual = true;
+                try {
+                    if (mEventType != ContextHubManager.EVENT_HUB_RESET) {
+                        isEqual &= (other.getNanoAppId() == mNanoAppId);
+                    }
+                    if (mEventType == ContextHubManager.EVENT_NANOAPP_ABORTED) {
+                        isEqual &= (other.getNanoAppAbortCode() == mNanoAppAbortCode);
+                    }
+                    if (mEventType == ContextHubManager.EVENT_NANOAPP_MESSAGE) {
+                        isEqual &= other.getNanoAppMessage().equals(mNanoAppMessage);
+                    }
+                } catch (UnsupportedOperationException e) {
+                    isEqual = false;
+                }
+            }
+        }
+
+        return isEqual;
+    }
+
     private static void hasExtraOrThrow(Intent intent, String extra) {
         if (!intent.hasExtra(extra)) {
             throw new IllegalArgumentException("Intent did not have extra: " + extra);
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 9acefa5..843db66 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -25,6 +25,7 @@
 import android.annotation.SystemService;
 import android.app.PendingIntent;
 import android.content.Context;
+import android.content.Intent;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.Looper;
@@ -757,7 +758,7 @@
         Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null");
         Preconditions.checkNotNull(executor, "Executor cannot be null");
 
-        ContextHubClient client = new ContextHubClient(hubInfo);
+        ContextHubClient client = new ContextHubClient(hubInfo, false /* persistent */);
         IContextHubClientCallback clientInterface = createClientCallback(
                 client, callback, executor);
 
@@ -793,43 +794,55 @@
     }
 
     /**
-     * Creates a ContextHubClient based on an Intent received by the Context Hub Service.
+     * Creates a ContextHubClient that will receive notifications based on Intent events.
      *
-     * This method is intended to be used after receiving an Intent received as a result of
-     * {@link ContextHubClient.registerIntent(PendingIntent, long)}, and must have been created
-     * through {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)} or
-     * equivalent at an earlier time.
+     * This method should be used instead of {@link #createClient(ContextHubInfo,
+     * ContextHubClientCallback)} and the equivalent API if the caller wants to preserve the
+     * messaging endpoint of a ContextHubClient, even after a process exits. If the PendingIntent
+     * with the provided nanoapp has already been registered at the service previously, then the
+     * same ContextHubClient will be regenerated without creating a new client connection at the
+     * service. Note that the PendingIntent, nanoapp, and Context Hub must all match in identifying
+     * a previously registered ContextHubClient. If a client is regenerated, it can be treated as
+     * the same endpoint entity from a nanoapp's perspective, and can be continued to be
+     * used to send messages even if the original process has exited.
      *
-     * @param pendingIntent the PendingIntent that has been registered with a client
+     * If registered successfully, intents will be delivered regarding events or messages from the
+     * specified nanoapp from the attached Context Hub. The intent will have an extra
+     * {@link ContextHubManager.EXTRA_CONTEXT_HUB_INFO} of type {@link ContextHubInfo}, which
+     * describes the Context Hub the intent event was for. The intent will also have an extra
+     * {@link ContextHubManager.EXTRA_EVENT_TYPE} of type {@link ContextHubManager.Event}, which
+     * will contain the type of the event. See {@link ContextHubManager.Event} for description of
+     * each event type, along with event-specific extra fields. The client can also use
+     * {@link ContextHubIntentEvent.fromIntent(Intent)} to parse the Intent generated by the event.
+     *
+     * Intent events will be delivered until it is unregistered through
+     * {@link ContextHubClient.close()}. Note that the registration of this
+     * ContextHubClient at the Context Hub Service will continued to be maintained until
+     * {@link ContextHubClient.close()} is called.
+     *
      * @param hubInfo       the hub to attach this client to
-     * @param callback      the notification callback to register
-     * @param executor      the executor to invoke the callback
+     * @param pendingIntent the PendingIntent to register to the client
+     * @param nanoAppId     the ID of the nanoapp that Intent events will be generated for
      * @return the registered client object
      *
-     * @throws IllegalArgumentException if hubInfo does not represent a valid hub, or pendingIntent
-     *                                  was not associated with a client
-     * @throws IllegalStateException    if the client is already registered to a valid callback
-     * @throws NullPointerException     if pendingIntent, hubInfo, callback, or executor is null
+     * @throws IllegalArgumentException if hubInfo does not represent a valid hub
+     * @throws IllegalStateException    if there were too many registered clients at the service
+     * @throws NullPointerException     if pendingIntent or hubInfo is null
      *
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
     @NonNull public ContextHubClient createClient(
-            @NonNull PendingIntent pendingIntent, @NonNull ContextHubInfo hubInfo,
-            @NonNull ContextHubClientCallback callback,
-            @NonNull @CallbackExecutor Executor executor) {
-        Preconditions.checkNotNull(pendingIntent, "PendingIntent cannot be null");
-        Preconditions.checkNotNull(callback, "Callback cannot be null");
-        Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null");
-        Preconditions.checkNotNull(executor, "Executor cannot be null");
+            @NonNull ContextHubInfo hubInfo, @NonNull PendingIntent pendingIntent, long nanoAppId) {
+        Preconditions.checkNotNull(pendingIntent);
+        Preconditions.checkNotNull(hubInfo);
 
-        ContextHubClient client = new ContextHubClient(hubInfo);
-        IContextHubClientCallback clientInterface = createClientCallback(
-                client, callback, executor);
+        ContextHubClient client = new ContextHubClient(hubInfo, true /* persistent */);
 
         IContextHubClient clientProxy;
         try {
-            clientProxy = mService.bindClient(pendingIntent, clientInterface, hubInfo.getId());
+            clientProxy = mService.createPendingIntentClient(
+                    hubInfo.getId(), pendingIntent, nanoAppId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -839,30 +852,6 @@
     }
 
     /**
-     * Equivalent to {@link #createClient(PendingIntent, ContextHubInfo, ContextHubClientCallback,
-     * Executor)} with the executor using the main thread's Looper.
-     *
-     * @param pendingIntent the PendingIntent that has been registered with a client
-     * @param hubInfo       the hub to attach this client to
-     * @param callback      the notification callback to register
-     * @return the registered client object
-     *
-     * @throws IllegalArgumentException if hubInfo does not represent a valid hub, or pendingIntent
-     *                                  was not associated with a client
-     * @throws IllegalStateException    if the client is already registered to a valid callback
-     * @throws NullPointerException     if pendingIntent, hubInfo, or callback is null
-     *
-     * @hide
-     */
-    @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
-    @NonNull public ContextHubClient createClient(
-            @NonNull PendingIntent pendingIntent, @NonNull ContextHubInfo hubInfo,
-            @NonNull ContextHubClientCallback callback) {
-        return createClient(
-                pendingIntent, hubInfo, callback, new HandlerExecutor(Handler.getMain()));
-    }
-
-    /**
      * Unregister a callback for receive messages from the context hub.
      *
      * @see Callback
diff --git a/core/java/android/hardware/location/IContextHubClient.aidl b/core/java/android/hardware/location/IContextHubClient.aidl
index b539414..e33545c 100644
--- a/core/java/android/hardware/location/IContextHubClient.aidl
+++ b/core/java/android/hardware/location/IContextHubClient.aidl
@@ -29,10 +29,4 @@
 
     // Closes the connection with the Context Hub
     void close();
-
-    // Registers a PendingIntent with the client
-    boolean registerIntent(in PendingIntent pendingIntent, long nanoAppId);
-
-    // Unregisters a PendingIntent from the client
-    boolean unregisterIntent(in PendingIntent pendingIntent);
 }
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index 9b0acdf..19ed694 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -61,10 +61,9 @@
     // Creates a client to send and receive messages
     IContextHubClient createClient(in IContextHubClientCallback client, int contextHubId);
 
-    // Binds an existing client to a new callback interface, provided a previously registered
-    // PendingIntent
-    IContextHubClient bindClient(
-            in PendingIntent pendingIntent, in IContextHubClientCallback client, int contextHubId);
+    // Creates a PendingIntent-based client to send and receive messages
+    IContextHubClient createPendingIntentClient(
+            int contextHubId, in PendingIntent pendingIntent, long nanoAppId);
 
     // Returns a list of ContextHub objects of available hubs
     List<ContextHubInfo> getContextHubs();
diff --git a/core/java/android/hardware/location/MemoryRegion.java b/core/java/android/hardware/location/MemoryRegion.java
index 857434e..3d9e859 100644
--- a/core/java/android/hardware/location/MemoryRegion.java
+++ b/core/java/android/hardware/location/MemoryRegion.java
@@ -16,6 +16,7 @@
 
 package android.hardware.location;
 
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -106,6 +107,25 @@
     }
 
     @Override
+    public boolean equals(@Nullable Object object) {
+        if (object == this) {
+            return true;
+        }
+
+        boolean isEqual = false;
+        if (object instanceof MemoryRegion) {
+            MemoryRegion other = (MemoryRegion) object;
+            isEqual = (other.getCapacityBytes() == mSizeBytes)
+                    && (other.getFreeCapacityBytes() == mSizeBytesFree)
+                    && (other.isReadable() == mIsReadable)
+                    && (other.isWritable() == mIsWritable)
+                    && (other.isExecutable() == mIsExecutable);
+        }
+
+        return isEqual;
+    }
+
+    @Override
     public int describeContents() {
         return 0;
     }
diff --git a/core/java/android/hardware/location/NanoAppMessage.java b/core/java/android/hardware/location/NanoAppMessage.java
index 6635258..9f90d59 100644
--- a/core/java/android/hardware/location/NanoAppMessage.java
+++ b/core/java/android/hardware/location/NanoAppMessage.java
@@ -15,10 +15,13 @@
  */
 package android.hardware.location;
 
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.Arrays;
+
 /**
  * A class describing messages send to or from nanoapps through the Context Hub Service.
  *
@@ -168,4 +171,22 @@
 
         return ret;
     }
+
+    @Override
+    public boolean equals(@Nullable Object object) {
+        if (object == this) {
+            return true;
+        }
+
+        boolean isEqual = false;
+        if (object instanceof NanoAppMessage) {
+            NanoAppMessage other = (NanoAppMessage) object;
+            isEqual = (other.getNanoAppId() == mNanoAppId)
+                    && (other.getMessageType() == mMessageType)
+                    && (other.isBroadcastMessage() == mIsBroadcasted)
+                    && Arrays.equals(other.getMessageBody(), mMessageBody);
+        }
+
+        return isEqual;
+    }
 }
diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java
index 4cd0001..4e551756 100644
--- a/core/java/android/net/MacAddress.java
+++ b/core/java/android/net/MacAddress.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -27,6 +28,8 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.net.Inet6Address;
+import java.net.UnknownHostException;
 import java.security.SecureRandom;
 import java.util.Arrays;
 import java.util.Random;
@@ -408,4 +411,34 @@
         Preconditions.checkNotNull(mask);
         return (mAddr & mask.mAddr) == (baseAddress.mAddr & mask.mAddr);
     }
+
+    /**
+     * Create a link-local Inet6Address from the MAC address. The EUI-48 MAC address is converted
+     * to an EUI-64 MAC address per RFC 4291. The resulting EUI-64 is used to construct a link-local
+     * IPv6 address per RFC 4862.
+     *
+     * @return A link-local Inet6Address constructed from the MAC address.
+     * @hide
+     */
+    public @Nullable Inet6Address getLinkLocalIpv6FromEui48Mac() {
+        byte[] macEui48Bytes = toByteArray();
+        byte[] addr = new byte[16];
+
+        addr[0] = (byte) 0xfe;
+        addr[1] = (byte) 0x80;
+        addr[8] = (byte) (macEui48Bytes[0] ^ (byte) 0x02); // flip the link-local bit
+        addr[9] = macEui48Bytes[1];
+        addr[10] = macEui48Bytes[2];
+        addr[11] = (byte) 0xff;
+        addr[12] = (byte) 0xfe;
+        addr[13] = macEui48Bytes[3];
+        addr[14] = macEui48Bytes[4];
+        addr[15] = macEui48Bytes[5];
+
+        try {
+            return Inet6Address.getByAddress(null, addr, 0);
+        } catch (UnknownHostException e) {
+            return null;
+        }
+    }
 }
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index da4b823..c7184c0 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -382,7 +382,9 @@
     /**
      * Sets the work source for this thread.
      *
-     * <p>All the following binder calls on this thread will use the provided work source.
+     * <p>All the following binder calls on this thread will use the provided work source. If this
+     * is called during an on-going binder transaction, all the following binder calls will use the
+     * work source until the end of the transaction.
      *
      * <p>The concept of worksource is similar to {@link WorkSource}. However, for performance
      * reasons, we only support one UID. This UID represents the original user responsible for the
@@ -390,20 +392,20 @@
      *
      * <p>A typical use case would be
      * <pre>
-     * Binder.setThreadWorkSource(uid);
+     * long token = Binder.setCallingWorkSourceUid(uid);
      * try {
      *   // Call an API.
      * } finally {
-     *   Binder.clearThreadWorkSource();
+     *   Binder.restoreCallingWorkSource(token);
      * }
      * </pre>
      *
      * @param workSource The original UID responsible for the binder call.
-     * @return The previously set work source.
+     * @return token to restore original work source.
      * @hide
      **/
     @CriticalNative
-    public static final native int setThreadWorkSource(int workSource);
+    public static final native long setCallingWorkSourceUid(int workSource);
 
     /**
      * Returns the work source set by the caller.
@@ -416,16 +418,34 @@
      * @hide
      */
     @CriticalNative
-    public static final native int getThreadWorkSource();
+    public static final native int getCallingWorkSourceUid();
 
     /**
      * Clears the work source on this thread.
      *
-     * @return The previously set work source.
+     * @return token to restore original work source.
      * @hide
      **/
     @CriticalNative
-    public static final native int clearThreadWorkSource();
+    public static final native long clearCallingWorkSource();
+
+    /**
+     * Restores the work source on this thread using a token returned by
+     * {@link #setCallingWorkSourceUid(int) or {@link clearCallingWorkSource()}.
+     *
+     * <p>A typical use case would be
+     * <pre>
+     * long token = Binder.setCallingWorkSourceUid(uid);
+     * try {
+     *   // Call an API.
+     * } finally {
+     *   Binder.restoreCallingWorkSource(token);
+     * }
+     * </pre>
+     * @hide
+     **/
+    @CriticalNative
+    public static final native void restoreCallingWorkSource(long token);
 
     /**
      * Flush any Binder commands pending in the current thread to the kernel
@@ -586,7 +606,7 @@
      *
      * <li>By default, this listener will propagate the worksource if the outgoing call happens on
      * the same thread as the incoming binder call.
-     * <li>Custom attribution can be done by calling {@link ThreadLocalWorkSourceUid#set(int)}.
+     * <li>Custom attribution can be done by calling {@link ThreadLocalWorkSource#setUid(int)}.
      * @hide
      */
     public static class PropagateWorkSourceTransactListener implements ProxyTransactListener {
@@ -595,12 +615,11 @@
            // Note that {@link Binder#getCallingUid()} is already set to the UID of the current
            // process when this method is called.
            //
-           // We use ThreadLocalWorkSourceUid instead. It also allows feature owners to set
-           // {@link ThreadLocalWorkSourceUid#set(int) manually to attribute resources to a UID.
-            int uid = ThreadLocalWorkSourceUid.get();
-            if (uid >= 0) {
-                int originalUid = Binder.setThreadWorkSource(uid);
-                return Integer.valueOf(originalUid);
+           // We use ThreadLocalWorkSource instead. It also allows feature owners to set
+           // {@link ThreadLocalWorkSource#set(int) manually to attribute resources to a UID.
+            int uid = ThreadLocalWorkSource.getUid();
+            if (uid != ThreadLocalWorkSource.UID_NONE) {
+                return Binder.setCallingWorkSourceUid(uid);
             }
             return null;
         }
@@ -608,8 +627,8 @@
         @Override
         public void onTransactEnded(Object session) {
             if (session != null) {
-                int uid = (int) session;
-                Binder.setThreadWorkSource(uid);
+                long token = (long) session;
+                Binder.restoreCallingWorkSource(token);
             }
         }
     }
@@ -897,11 +916,11 @@
         // Log any exceptions as warnings, don't silently suppress them.
         // If the call was FLAG_ONEWAY then these exceptions disappear into the ether.
         final boolean tracingEnabled = Binder.isTracingEnabled();
+        final long origWorkSource = ThreadLocalWorkSource.setUid(Binder.getCallingUid());
         try {
             if (tracingEnabled) {
                 Trace.traceBegin(Trace.TRACE_TAG_ALWAYS, getClass().getName() + ":" + code);
             }
-            ThreadLocalWorkSourceUid.set(Binder.getCallingUid());
             res = onTransact(code, data, reply, flags);
         } catch (RemoteException|RuntimeException e) {
             if (observer != null) {
@@ -922,7 +941,7 @@
             }
             res = true;
         } finally {
-            ThreadLocalWorkSourceUid.clear();
+            ThreadLocalWorkSource.restore(origWorkSource);
             if (tracingEnabled) {
                 Trace.traceEnd(Trace.TRACE_TAG_ALWAYS);
             }
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index f3a9a50..e8704af 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -739,7 +739,7 @@
 
     private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
         msg.target = this;
-        msg.workSourceUid = ThreadLocalWorkSourceUid.get();
+        msg.workSourceUid = ThreadLocalWorkSource.getUid();
 
         if (mAsynchronous) {
             msg.setAsynchronous(true);
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index 5b8abab..a8d1215 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -204,8 +204,8 @@
             if (observer != null) {
                 token = observer.messageDispatchStarting();
             }
+            long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
             try {
-                ThreadLocalWorkSourceUid.set(msg.workSourceUid);
                 msg.target.dispatchMessage(msg);
                 if (observer != null) {
                     observer.messageDispatched(token, msg);
@@ -217,7 +217,7 @@
                 }
                 throw exception;
             } finally {
-                ThreadLocalWorkSourceUid.clear();
+                ThreadLocalWorkSource.restore(origWorkSource);
                 if (traceTag != 0) {
                     Trace.traceEnd(traceTag);
                 }
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index a307cd8..1c1db68 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -24,6 +24,7 @@
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.content.Context;
+import android.service.dreams.Sandman;
 import android.util.Log;
 import android.util.proto.ProtoOutputStream;
 
@@ -1001,6 +1002,29 @@
     }
 
     /**
+     * Requests the device to start dreaming.
+     * <p>
+     * If dream can not be started, for example if another {@link PowerManager} transition is in
+     * progress, does nothing. Unlike {@link #nap(long)}, this does not put device to sleep when
+     * dream ends.
+     * </p><p>
+     * Requires the {@link android.Manifest.permission#WRITE_DREAM_STATE} permission.
+     * </p>
+     *
+     * @param time The time when the request to nap was issued, in the
+     * {@link SystemClock#uptimeMillis()} time base.  This timestamp may be used to correctly
+     * order the dream request with other power management functions.  It should be set
+     * to the timestamp of the input event that caused the request to dream.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE)
+    public void dream(long time) {
+        Sandman.startDreamByUserRequest(mContext);
+    }
+
+    /**
      * Boosts the brightness of the screen to maximum for a predetermined
      * period of time.  This is used to make the screen more readable in bright
      * daylight for a short duration.
diff --git a/core/java/android/os/ThreadLocalWorkSourceUid.java b/core/java/android/os/ThreadLocalWorkSource.java
similarity index 64%
rename from core/java/android/os/ThreadLocalWorkSourceUid.java
rename to core/java/android/os/ThreadLocalWorkSource.java
index df1d275..53dd460 100644
--- a/core/java/android/os/ThreadLocalWorkSourceUid.java
+++ b/core/java/android/os/ThreadLocalWorkSource.java
@@ -19,26 +19,41 @@
 /**
  * @hide Only for use within system server.
  */
-public final class ThreadLocalWorkSourceUid {
+public final class ThreadLocalWorkSource {
     public static final int UID_NONE = Message.UID_NONE;
     private static final ThreadLocal<Integer> sWorkSourceUid =
             ThreadLocal.withInitial(() -> UID_NONE);
 
     /** Returns the original work source uid. */
-    public static int get() {
+    public static int getUid() {
         return sWorkSourceUid.get();
     }
 
     /** Sets the original work source uid. */
-    public static void set(int uid) {
+    public static long setUid(int uid) {
+        final long token = getToken();
         sWorkSourceUid.set(uid);
+        return token;
+    }
+
+    /** Restores the state using the provided token. */
+    public static void restore(long token) {
+        sWorkSourceUid.set(parseUidFromToken(token));
     }
 
     /** Clears the stored work source uid. */
-    public static void clear() {
-        sWorkSourceUid.set(UID_NONE);
+    public static long clear() {
+        return setUid(UID_NONE);
     }
 
-    private ThreadLocalWorkSourceUid() {
+    private static int parseUidFromToken(long token) {
+        return (int) token;
+    }
+
+    private static long getToken() {
+        return sWorkSourceUid.get();
+    }
+
+    private ThreadLocalWorkSource() {
     }
 }
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index f4dcce1..15ded8d 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -154,7 +154,7 @@
 
     // User authenticators.
     public static final int HW_AUTH_PASSWORD = 1 << 0;
-    public static final int HW_AUTH_FINGERPRINT = 1 << 1;
+    public static final int HW_AUTH_BIOMETRIC = 1 << 1;
 
     // Error codes.
     public static final int KM_ERROR_OK = 0;
diff --git a/core/java/android/service/carrier/ApnService.java b/core/java/android/service/carrier/ApnService.java
new file mode 100644
index 0000000..d53eb37
--- /dev/null
+++ b/core/java/android/service/carrier/ApnService.java
@@ -0,0 +1,77 @@
+/*
+ * 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.service.carrier;
+
+import android.annotation.SystemApi;
+import android.annotation.WorkerThread;
+import android.app.Service;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.internal.telephony.IApnSourceService;
+
+import java.util.List;
+
+/**
+ * A service that the system can call to restore default APNs.
+ * <p>
+ * To extend this class, specify the full name of your implementation in the resource file
+ * {@code packages/providers/TelephonyProvider/res/values/config.xml} as the
+ * {@code apn_source_service}.
+ * </p>
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class ApnService extends Service {
+
+    private static final String LOG_TAG = "ApnService";
+
+    private final IApnSourceService.Stub mBinder = new IApnSourceService.Stub() {
+        /**
+         * Retreive APNs for the default slot index.
+         */
+        @Override
+        public ContentValues[] getApns(int subId) {
+            try {
+                List<ContentValues> apns = ApnService.this.onRestoreApns(subId);
+                return apns.toArray(new ContentValues[apns.size()]);
+            } catch (Exception e) {
+                Log.e(LOG_TAG, "Error in getApns for subId=" + subId + ": " + e.getMessage(), e);
+                return null;
+            }
+        }
+    };
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    /**
+     * Override this method to restore default user APNs with a carrier service instead of the
+     * built in platform xml APNs list.
+     * <p>
+     * This method is called by the TelephonyProvider when the user requests restoring the default
+     * APNs. It should return a list of ContentValues representing the default APNs for the given
+     * subId.
+     */
+    @WorkerThread
+    public abstract List<ContentValues> onRestoreApns(int subId);
+}
diff --git a/core/java/android/view/DisplayListCanvas.java b/core/java/android/view/DisplayListCanvas.java
index 043b31d..03d9955 100644
--- a/core/java/android/view/DisplayListCanvas.java
+++ b/core/java/android/view/DisplayListCanvas.java
@@ -20,6 +20,7 @@
 import android.graphics.BaseRecordingCanvas;
 import android.graphics.CanvasProperty;
 import android.graphics.Paint;
+import android.os.Build;
 
 /**
  * This class exists temporarily to workaround broken apps
@@ -36,13 +37,13 @@
     }
 
     /** @hide */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public abstract void drawRoundRect(CanvasProperty<Float> left, CanvasProperty<Float> top,
             CanvasProperty<Float> right, CanvasProperty<Float> bottom, CanvasProperty<Float> rx,
             CanvasProperty<Float> ry, CanvasProperty<Paint> paint);
 
     /** @hide */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public abstract void drawCircle(CanvasProperty<Float> cx, CanvasProperty<Float> cy,
             CanvasProperty<Float> radius, CanvasProperty<Paint> paint);
 }
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index fa30221..4a5ccdf 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -218,11 +218,6 @@
                 layoutRight = end - paddingEnd;
                 end = layoutLeft = layoutRight - child.getMeasuredWidth();
             }
-            if (child == mAudiblyAlertedIcon) {
-                int paddingEnd = mContentEndMargin;
-                layoutRight = end - paddingEnd;
-                end = layoutLeft = layoutRight - child.getMeasuredWidth();
-            }
             if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
                 int ltrLeft = layoutLeft;
                 layoutLeft = getWidth() - layoutRight;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 7a621a3..484c6f3 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1449,6 +1449,9 @@
             }
 
             if (mStopped) {
+                if (mSurfaceHolder != null) {
+                    notifySurfaceDestroyed();
+                }
                 destroySurface();
             }
         }
@@ -2393,13 +2396,7 @@
                     }
                     mIsCreating = false;
                 } else if (hadSurface) {
-                    mSurfaceHolder.ungetCallbacks();
-                    SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
-                    if (callbacks != null) {
-                        for (SurfaceHolder.Callback c : callbacks) {
-                            c.surfaceDestroyed(mSurfaceHolder);
-                        }
-                    }
+                    notifySurfaceDestroyed();
                     mSurfaceHolder.mSurfaceLock.lock();
                     try {
                         mSurfaceHolder.mSurface = new Surface();
@@ -2667,6 +2664,16 @@
         mIsInTraversal = false;
     }
 
+    private void notifySurfaceDestroyed() {
+        mSurfaceHolder.ungetCallbacks();
+        SurfaceHolder.Callback[] callbacks = mSurfaceHolder.getCallbacks();
+        if (callbacks != null) {
+            for (SurfaceHolder.Callback c : callbacks) {
+                c.surfaceDestroyed(mSurfaceHolder);
+            }
+        }
+    }
+
     private void maybeHandleWindowMove(Rect frame) {
 
         // TODO: Well, we are checking whether the frame has changed similarly
diff --git a/core/java/android/view/intelligence/IntelligenceManager.java b/core/java/android/view/intelligence/IntelligenceManager.java
index b74600b..242bf6a 100644
--- a/core/java/android/view/intelligence/IntelligenceManager.java
+++ b/core/java/android/view/intelligence/IntelligenceManager.java
@@ -196,6 +196,7 @@
 
     private void handleSendEvent(@NonNull ContentCaptureEvent event) {
 
+        //TODO(b/111276913): make a copy and don't use lock
         synchronized (mLock) {
             mEvents.add(event);
             final int numberEvents = mEvents.size();
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index 9f509b1..7756a19 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -17,6 +17,7 @@
 package android.widget;
 
 import android.annotation.FloatRange;
+import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -40,6 +41,7 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Message;
+import android.util.Log;
 import android.view.ContextThemeWrapper;
 import android.view.Display;
 import android.view.PixelCopy;
@@ -55,11 +57,15 @@
 import com.android.internal.R;
 import com.android.internal.util.Preconditions;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Android magnifier widget. Can be used by any view which is attached to a window.
  */
 @UiThread
 public final class Magnifier {
+    private static final String TAG = "Magnifier";
     // Use this to specify that a previous configuration value does not exist.
     private static final int NONEXISTENT_PREVIOUS_CONFIG_VALUE = -1;
     // The callbacks of the pixel copy requests will be invoked on
@@ -83,8 +89,8 @@
     private int mSourceWidth;
     // The height of the content that will be copied to the magnifier.
     private int mSourceHeight;
-    // Whether the zoom of the magnifier has changed since last content copy.
-    private boolean mDirtyZoom;
+    // Whether the zoom of the magnifier or the view position have changed since last content copy.
+    private boolean mDirtyState;
     // The elevation of the window containing the magnifier.
     private final float mWindowElevation;
     // The corner radius of the window containing the magnifier.
@@ -95,6 +101,14 @@
     private final int mDefaultVerticalSourceToMagnifierOffset;
     // Whether the magnifier will be clamped inside the main surface and not overlap system insets.
     private final boolean mForcePositionWithinWindowSystemInsetsBounds;
+    // The behavior of the left bound of the rectangle where the content can be copied from.
+    private @SourceBound int mLeftContentBound;
+    // The behavior of the top bound of the rectangle where the content can be copied from.
+    private @SourceBound int mTopContentBound;
+    // The behavior of the right bound of the rectangle where the content can be copied from.
+    private @SourceBound int mRightContentBound;
+    // The behavior of the bottom bound of the rectangle where the content can be copied from.
+    private @SourceBound int mBottomContentBound;
     // The parent surface for the magnifier surface.
     private SurfaceInfo mParentSurface;
     // The surface where the content will be copied from.
@@ -145,6 +159,10 @@
                 params.mVerticalDefaultSourceToMagnifierOffset;
         mForcePositionWithinWindowSystemInsetsBounds =
                 params.mForcePositionWithinWindowSystemInsetsBounds;
+        mLeftContentBound = params.mLeftContentBound;
+        mTopContentBound = params.mTopContentBound;
+        mRightContentBound = params.mRightContentBound;
+        mBottomContentBound = params.mBottomContentBound;
         // The view's surface coordinates will not be updated until the magnifier is first shown.
         mViewCoordinatesInSurface = new int[2];
     }
@@ -195,8 +213,6 @@
     public void show(@FloatRange(from = 0) float sourceCenterX,
             @FloatRange(from = 0) float sourceCenterY,
             float magnifierCenterX, float magnifierCenterY) {
-        sourceCenterX = Math.max(0, Math.min(sourceCenterX, mView.getWidth()));
-        sourceCenterY = Math.max(0, Math.min(sourceCenterY, mView.getHeight()));
 
         obtainSurfaces();
         obtainContentCoordinates(sourceCenterX, sourceCenterY);
@@ -205,7 +221,7 @@
         final int startX = mClampedCenterZoomCoords.x - mSourceWidth / 2;
         final int startY = mClampedCenterZoomCoords.y - mSourceHeight / 2;
         if (sourceCenterX != mPrevShowSourceCoords.x || sourceCenterY != mPrevShowSourceCoords.y
-                || mDirtyZoom) {
+                || mDirtyState) {
             if (mWindow == null) {
                 synchronized (mLock) {
                     mWindow = new InternalPopupWindow(mView.getContext(), mView.getDisplay(),
@@ -262,13 +278,13 @@
     public void update() {
         if (mWindow != null) {
             obtainSurfaces();
-            if (!mDirtyZoom) {
+            if (!mDirtyState) {
                 // Update the content shown in the magnifier.
                 performPixelCopy(mPrevStartCoordsInSurface.x, mPrevStartCoordsInSurface.y,
                         false /* update window position */);
             } else {
-                // If the zoom has changed, we cannot use the same top left coordinates
-                // as before, so just #show again to have them recomputed.
+                // If for example the zoom has changed, we cannot use the same top left
+                // coordinates as before, so just #show again to have them recomputed.
                 show(mPrevShowSourceCoords.x, mPrevShowSourceCoords.y,
                         mPrevShowWindowCoords.x, mPrevShowWindowCoords.y);
             }
@@ -315,6 +331,7 @@
 
     /**
      * Sets the zoom to be applied to the chosen content before being copied to the magnifier popup.
+     * The change will become effective at the next #show or #update call.
      * @param zoom the zoom to be set
      */
     public void setZoom(@FloatRange(from = 0f) float zoom) {
@@ -322,7 +339,7 @@
         mZoom = zoom;
         mSourceWidth = Math.round(mWindowWidth / mZoom);
         mSourceHeight = Math.round(mWindowHeight / mZoom);
-        mDirtyZoom = true;
+        mDirtyState = true;
     }
 
     /**
@@ -480,7 +497,14 @@
      * magnifier. These are relative to the surface the content is copied from.
      */
     private void obtainContentCoordinates(final float xPosInView, final float yPosInView) {
+        final int prevViewXInSurface = mViewCoordinatesInSurface[0];
+        final int prevViewYInSurface = mViewCoordinatesInSurface[1];
         mView.getLocationInSurface(mViewCoordinatesInSurface);
+        if (mViewCoordinatesInSurface[0] != prevViewXInSurface
+                || mViewCoordinatesInSurface[1] != prevViewYInSurface) {
+            mDirtyState = true;
+        }
+
         final int zoomCenterX;
         final int zoomCenterY;
         if (mView instanceof SurfaceView) {
@@ -492,8 +516,25 @@
             zoomCenterY = Math.round(yPosInView + mViewCoordinatesInSurface[1]);
         }
 
-        // Clamp the x location to avoid magnifying content which does not belong
-        // to the magnified view. This will not take into account overlapping views.
+        final Rect[] bounds = new Rect[3]; // [MAX_IN_SURFACE, MAX_IN_VIEW, MAX_VISIBLE]
+        // Obtain the surface bounds rectangle.
+        final Rect surfaceBounds = new Rect(0, 0,
+                mContentCopySurface.mWidth, mContentCopySurface.mHeight);
+        bounds[0] = surfaceBounds;
+        // Obtain the view bounds rectangle.
+        final Rect viewBounds;
+        if (mView instanceof SurfaceView) {
+            viewBounds = new Rect(0, 0, mContentCopySurface.mWidth, mContentCopySurface.mHeight);
+        } else {
+            viewBounds = new Rect(
+                    mViewCoordinatesInSurface[0],
+                    mViewCoordinatesInSurface[1],
+                    mViewCoordinatesInSurface[0] + mView.getWidth(),
+                    mViewCoordinatesInSurface[1] + mView.getHeight()
+            );
+        }
+        bounds[1] = viewBounds;
+        // Obtain the visible view region rectangle.
         final Rect viewVisibleRegion = new Rect();
         mView.getGlobalVisibleRect(viewVisibleRegion);
         if (mView.getViewRootImpl() != null) {
@@ -505,9 +546,40 @@
             // If we copy content from a SurfaceView, clamp coordinates relative to it.
             viewVisibleRegion.offset(-mViewCoordinatesInSurface[0], -mViewCoordinatesInSurface[1]);
         }
-        mClampedCenterZoomCoords.x = Math.max(viewVisibleRegion.left + mSourceWidth / 2, Math.min(
-                zoomCenterX, viewVisibleRegion.right - mSourceWidth / 2));
-        mClampedCenterZoomCoords.y = zoomCenterY;
+        bounds[2] = viewVisibleRegion;
+
+        // Aggregate the above to obtain the bounds where the content copy will be restricted.
+        int resolvedLeft = Integer.MIN_VALUE;
+        for (int i = mLeftContentBound; i >= 0; --i) {
+            resolvedLeft = Math.max(resolvedLeft, bounds[i].left);
+        }
+        int resolvedTop = Integer.MIN_VALUE;
+        for (int i = mTopContentBound; i >= 0; --i) {
+            resolvedTop = Math.max(resolvedTop, bounds[i].top);
+        }
+        int resolvedRight = Integer.MAX_VALUE;
+        for (int i = mRightContentBound; i >= 0; --i) {
+            resolvedRight = Math.min(resolvedRight, bounds[i].right);
+        }
+        int resolvedBottom = Integer.MAX_VALUE;
+        for (int i = mBottomContentBound; i >= 0; --i) {
+            resolvedBottom = Math.min(resolvedBottom, bounds[i].bottom);
+        }
+        // Adjust <left-right> and <top-bottom> pairs of bounds to make sense.
+        resolvedLeft = Math.min(resolvedLeft, mContentCopySurface.mWidth - mSourceWidth);
+        resolvedTop = Math.min(resolvedTop, mContentCopySurface.mHeight - mSourceHeight);
+        if (resolvedLeft < 0 || resolvedTop < 0) {
+            Log.e(TAG, "Magnifier's content is copied from a surface smaller than"
+                    + "the content requested size. This will probably lead to distorted content.");
+        }
+        resolvedRight = Math.max(resolvedRight, resolvedLeft + mSourceWidth);
+        resolvedBottom = Math.max(resolvedBottom, resolvedTop + mSourceHeight);
+
+        // Finally compute the coordinates of the source center.
+        mClampedCenterZoomCoords.x = Math.max(resolvedLeft + mSourceWidth / 2, Math.min(
+                zoomCenterX, resolvedRight - mSourceWidth / 2));
+        mClampedCenterZoomCoords.y = Math.max(resolvedTop + mSourceHeight / 2, Math.min(
+                zoomCenterY, resolvedBottom - mSourceHeight / 2));
     }
 
     /**
@@ -539,20 +611,16 @@
         if (mContentCopySurface.mSurface == null || !mContentCopySurface.mSurface.isValid()) {
             return;
         }
-        // Clamp copy coordinates inside the surface to avoid displaying distorted content.
-        final int clampedStartXInSurface = Math.max(0,
-                Math.min(startXInSurface, mContentCopySurface.mWidth - mSourceWidth));
-        final int clampedStartYInSurface = Math.max(0,
-                Math.min(startYInSurface, mContentCopySurface.mHeight - mSourceHeight));
+
         // Clamp window coordinates inside the parent surface, to avoid displaying
         // the magnifier out of screen or overlapping with system insets.
         final Point windowCoords = getCurrentClampedWindowCoordinates();
 
         // Perform the pixel copy.
-        mPixelCopyRequestRect.set(clampedStartXInSurface,
-                clampedStartYInSurface,
-                clampedStartXInSurface + mSourceWidth,
-                clampedStartYInSurface + mSourceHeight);
+        mPixelCopyRequestRect.set(startXInSurface,
+                startYInSurface,
+                startXInSurface + mSourceWidth,
+                startYInSurface + mSourceHeight);
         final InternalPopupWindow currentWindowInstance = mWindow;
         final Bitmap bitmap =
                 Bitmap.createBitmap(mSourceWidth, mSourceHeight, Bitmap.Config.ARGB_8888);
@@ -573,7 +641,7 @@
                 sPixelCopyHandlerThread.getThreadHandler());
         mPrevStartCoordsInSurface.x = startXInSurface;
         mPrevStartCoordsInSurface.y = startYInSurface;
-        mDirtyZoom = false;
+        mDirtyState = false;
     }
 
     /**
@@ -912,6 +980,10 @@
         private int mHorizontalDefaultSourceToMagnifierOffset;
         private int mVerticalDefaultSourceToMagnifierOffset;
         private boolean mForcePositionWithinWindowSystemInsetsBounds;
+        private @SourceBound int mLeftContentBound;
+        private @SourceBound int mTopContentBound;
+        private @SourceBound int mRightContentBound;
+        private @SourceBound int  mBottomContentBound;
 
         /**
          * Construct a new builder for {@link Magnifier} objects.
@@ -937,6 +1009,10 @@
                     a.getDimensionPixelSize(R.styleable.Magnifier_magnifierVerticalOffset, 0);
             a.recycle();
             mForcePositionWithinWindowSystemInsetsBounds = true;
+            mLeftContentBound = SOURCE_BOUND_MAX_VISIBLE;
+            mTopContentBound = SOURCE_BOUND_MAX_IN_SURFACE;
+            mRightContentBound = SOURCE_BOUND_MAX_VISIBLE;
+            mBottomContentBound = SOURCE_BOUND_MAX_IN_SURFACE;
         }
 
         /**
@@ -1044,6 +1120,52 @@
         }
 
         /**
+         * Defines the bounds of the rectangle where the magnifier will be able to copy its content
+         * from. The content will always be copied from the {@link Surface} of the main application
+         * window unless the magnified view is a {@link SurfaceView}, in which case its backing
+         * surface will be used. Each bound can have a different behavior, with the options being:
+         * <ul>
+         *   <li>{@link #SOURCE_BOUND_MAX_VISIBLE}, which extends the bound as much as possible
+         *   while remaining in the visible region of the magnified view, as given by
+         *   {@link android.view.View#getGlobalVisibleRect(Rect)}. For example, this will take into
+         *   account the case when the view is contained in a scrollable container, and the
+         *   magnifier will refuse to copy content outside of the visible view region</li>
+         *   <li>{@link #SOURCE_BOUND_MAX_IN_VIEW}, which extends the bound as much as possible
+         *   while remaining in the bounds of the view. Note that, although this option is
+         *   used, the magnifier will always only display content visible on the screen: if the
+         *   view lays outside the screen or is covered by a different view either partially or
+         *   totally, the magnifier will not show any view region not visible on the screen.</li>
+         *   <li>{@link #SOURCE_BOUND_MAX_IN_SURFACE}, which extends the bound as much
+         *   as possible while remaining inside the surface the content is copied from.</li>
+         * </ul>
+         * Note that if either of the first three options is used, the bound will be compared to
+         * the bound of the surface (i.e. as if {@link #SOURCE_BOUND_MAX_IN_SURFACE} was used),
+         * and the more restrictive one will be chosen. In other words, no attempt to copy content
+         * from outside the surface will be permitted. If two opposite bounds are not well-behaved
+         * (i.e. left + sourceWidth > right or top + sourceHeight > bottom), the left and top
+         * bounds will have priority and the others will be extended accordingly. If the pairs
+         * obtained this way still remain out of bounds, the smallest possible offset will be added
+         * to the pairs to bring them inside the surface bounds. If this is impossible
+         * (i.e. the surface is too small for the size of the content we try to copy on either
+         * dimension), an error will be logged and the magnifier content will look distorted.
+         * The default values assumed by the builder for the source bounds are
+         * left: {@link #SOURCE_BOUND_MAX_VISIBLE}, top: {@link #SOURCE_BOUND_MAX_IN_SURFACE},
+         * right: {@link #SOURCE_BOUND_MAX_VISIBLE}, bottom: {@link #SOURCE_BOUND_MAX_IN_SURFACE}.
+         * @param left the left bound for content copy
+         * @param top the top bound for content copy
+         * @param right the right bound for content copy
+         * @param bottom the bottom bound for content copy
+         */
+        public Builder setSourceBounds(@SourceBound int left, @SourceBound int top,
+                @SourceBound int right, @SourceBound int bottom) {
+            mLeftContentBound = left;
+            mTopContentBound = top;
+            mRightContentBound = right;
+            mBottomContentBound = bottom;
+            return this;
+        }
+
+        /**
          * Builds a {@link Magnifier} instance based on the configuration of this {@link Builder}.
          */
         public @NonNull Magnifier build() {
@@ -1051,6 +1173,38 @@
         }
     }
 
+    /**
+     * A source bound that will extend as much as possible, while remaining within the surface
+     * the content is copied from.
+     */
+
+    public static final int SOURCE_BOUND_MAX_IN_SURFACE = 0;
+    /**
+     * A source bound that will extend as much as possible, while remaining within the
+     * magnified view.
+     */
+
+    public static final int SOURCE_BOUND_MAX_IN_VIEW = 1;
+
+    /**
+     * A source bound that will extend as much as possible, while remaining within the
+     * visible region of the magnified view, as determined by
+     * {@link View#getGlobalVisibleRect(Rect)}.
+     */
+    public static final int SOURCE_BOUND_MAX_VISIBLE = 2;
+
+
+    /**
+     * Used to describe the {@link Surface} rectangle where the magnifier's content is allowed
+     * to be copied from. For more details, see method
+     * {@link Magnifier.Builder#setSourceBounds(int, int, int, int)}
+     *
+     * @hide
+     */
+    @IntDef({SOURCE_BOUND_MAX_IN_SURFACE, SOURCE_BOUND_MAX_IN_VIEW, SOURCE_BOUND_MAX_VISIBLE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SourceBound {}
+
     // The rest of the file consists of test APIs.
 
     /**
diff --git a/core/java/android/widget/MediaControlView2.java b/core/java/android/widget/MediaControlView2.java
deleted file mode 100644
index f52854a..0000000
--- a/core/java/android/widget/MediaControlView2.java
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.widget;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.media.SessionToken2;
-import android.media.session.MediaController;
-import android.media.update.ApiLoader;
-import android.media.update.MediaControlView2Provider;
-import android.media.update.ViewGroupHelper;
-import android.util.AttributeSet;
-import android.view.View;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-// TODO: Use link annotation to refer VideoView2 once VideoView2 became unhidden.
-/**
- * @hide
- * A View that contains the controls for MediaPlayer2.
- * It provides a wide range of UI including buttons such as "Play/Pause", "Rewind", "Fast Forward",
- * "Subtitle", "Full Screen", and it is also possible to add multiple custom buttons.
- *
- * <p>
- * <em> MediaControlView2 can be initialized in two different ways: </em>
- * 1) When VideoView2 is initialized, it automatically initializes a MediaControlView2 instance and
- * adds it to the view.
- * 2) Initialize MediaControlView2 programmatically and add it to a ViewGroup instance.
- *
- * In the first option, VideoView2 automatically connects MediaControlView2 to MediaController,
- * which is necessary to communicate with MediaSession2. In the second option, however, the
- * developer needs to manually retrieve a MediaController instance and set it to MediaControlView2
- * by calling setController(MediaController controller).
- *
- * <p>
- * There is no separate method that handles the show/hide behavior for MediaControlView2. Instead,
- * one can directly change the visibility of this view by calling View.setVisibility(int). The
- * values supported are View.VISIBLE and View.GONE.
- * In addition, the following customization is supported:
- * Set focus to the play/pause button by calling requestPlayButtonFocus().
- *
- * <p>
- * It is also possible to add custom buttons with custom icons and actions inside MediaControlView2.
- * Those buttons will be shown when the overflow button is clicked.
- * See VideoView2#setCustomActions for more details on how to add.
- */
-public class MediaControlView2 extends ViewGroupHelper<MediaControlView2Provider> {
-    /** @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 {}
-
-    /**
-     * MediaControlView2 button value for playing and pausing media.
-     * @hide
-     */
-    public static final int BUTTON_PLAY_PAUSE = 1;
-    /**
-     * MediaControlView2 button value for jumping 30 seconds forward.
-     * @hide
-     */
-    public static final int BUTTON_FFWD = 2;
-    /**
-     * MediaControlView2 button value for jumping 10 seconds backward.
-     * @hide
-     */
-    public static final int BUTTON_REW = 3;
-    /**
-     * MediaControlView2 button value for jumping to next media.
-     * @hide
-     */
-    public static final int BUTTON_NEXT = 4;
-    /**
-     * MediaControlView2 button value for jumping to previous media.
-     * @hide
-     */
-    public static final int BUTTON_PREV = 5;
-    /**
-     * MediaControlView2 button value for showing/hiding subtitle track.
-     * @hide
-     */
-    public static final int BUTTON_SUBTITLE = 6;
-    /**
-     * MediaControlView2 button value for toggling full screen.
-     * @hide
-     */
-    public static final int BUTTON_FULL_SCREEN = 7;
-    /**
-     * MediaControlView2 button value for showing/hiding overflow buttons.
-     * @hide
-     */
-    public static final int BUTTON_OVERFLOW = 8;
-    /**
-     * MediaControlView2 button value for muting audio.
-     * @hide
-     */
-    public static final int BUTTON_MUTE = 9;
-    /**
-     * MediaControlView2 button value for adjusting aspect ratio of view.
-     * @hide
-     */
-    public static final int BUTTON_ASPECT_RATIO = 10;
-    /**
-     * MediaControlView2 button value for showing/hiding settings page.
-     * @hide
-     */
-    public static final int BUTTON_SETTINGS = 11;
-
-    public MediaControlView2(@NonNull Context context) {
-        this(context, null);
-    }
-
-    public MediaControlView2(@NonNull Context context, @Nullable AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public MediaControlView2(@NonNull Context context, @Nullable AttributeSet attrs,
-            int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public MediaControlView2(@NonNull Context context, @Nullable AttributeSet attrs,
-            int defStyleAttr, int defStyleRes) {
-        super((instance, superProvider, privateProvider) ->
-                ApiLoader.getProvider().createMediaControlView2(
-                        (MediaControlView2) instance, superProvider, privateProvider,
-                        attrs, defStyleAttr, defStyleRes),
-                context, attrs, defStyleAttr, defStyleRes);
-        mProvider.initialize(attrs, defStyleAttr, defStyleRes);
-    }
-
-    /**
-     * Sets MediaSession2 token to control corresponding MediaSession2.
-     */
-    public void setMediaSessionToken(SessionToken2 token) {
-        mProvider.setMediaSessionToken_impl(token);
-    }
-
-    /**
-     * Registers a callback to be invoked when the fullscreen mode should be changed.
-     * @param l The callback that will be run
-     */
-    public void setOnFullScreenListener(OnFullScreenListener l) {
-        mProvider.setOnFullScreenListener_impl(l);
-    }
-
-    /**
-     * @hide TODO: remove once the implementation is revised
-     */
-    public void setController(MediaController controller) {
-        mProvider.setController_impl(controller);
-    }
-
-    /**
-     * Changes the visibility state of an individual button. Default value is View.Visible.
-     *
-     * @param button the {@code Button} assigned to individual buttons
-     * <ul>
-     * <li>{@link #BUTTON_PLAY_PAUSE}
-     * <li>{@link #BUTTON_FFWD}
-     * <li>{@link #BUTTON_REW}
-     * <li>{@link #BUTTON_NEXT}
-     * <li>{@link #BUTTON_PREV}
-     * <li>{@link #BUTTON_SUBTITLE}
-     * <li>{@link #BUTTON_FULL_SCREEN}
-     * <li>{@link #BUTTON_MUTE}
-     * <li>{@link #BUTTON_OVERFLOW}
-     * <li>{@link #BUTTON_ASPECT_RATIO}
-     * <li>{@link #BUTTON_SETTINGS}
-     * </ul>
-     * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
-     * @hide
-     */
-    public void setButtonVisibility(@Button int button, @Visibility int visibility) {
-        mProvider.setButtonVisibility_impl(button, visibility);
-    }
-
-    /**
-     *  Requests focus for the play/pause button.
-     */
-    public void requestPlayButtonFocus() {
-        mProvider.requestPlayButtonFocus_impl();
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        mProvider.onLayout_impl(changed, l, t, r, b);
-    }
-
-    /**
-     * Interface definition of a callback to be invoked to inform the fullscreen mode is changed.
-     * Application should handle the fullscreen mode accordingly.
-     */
-    public interface OnFullScreenListener {
-        /**
-         * Called to indicate a fullscreen mode change.
-         */
-        void onFullScreen(View view, boolean fullScreen);
-    }
-}
diff --git a/core/java/android/widget/VideoView2.java b/core/java/android/widget/VideoView2.java
deleted file mode 100644
index 0724294a..0000000
--- a/core/java/android/widget/VideoView2.java
+++ /dev/null
@@ -1,452 +0,0 @@
-/*
- * 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.widget;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
-import android.content.Context;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.media.DataSourceDesc;
-import android.media.MediaItem2;
-import android.media.MediaMetadata2;
-import android.media.MediaPlayer2;
-import android.media.SessionToken2;
-import android.media.session.MediaController;
-import android.media.session.PlaybackState;
-import android.media.update.ApiLoader;
-import android.media.update.VideoView2Provider;
-import android.media.update.ViewGroupHelper;
-import android.net.Uri;
-import android.os.Bundle;
-import android.util.AttributeSet;
-import android.view.View;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Executor;
-
-// TODO: Replace MediaSession wtih MediaSession2 once MediaSession2 is submitted.
-/**
- * @hide
- * Displays a video file.  VideoView2 class is a View class which is wrapping {@link MediaPlayer2}
- * so that developers can easily implement a video rendering application.
- *
- * <p>
- * <em> Data sources that VideoView2 supports : </em>
- * VideoView2 can play video files and audio-only files as
- * well. It can load from various sources such as resources or content providers. The supported
- * media file formats are the same as {@link MediaPlayer2}.
- *
- * <p>
- * <em> View type can be selected : </em>
- * VideoView2 can render videos on top of TextureView as well as
- * SurfaceView selectively. The default is SurfaceView and it can be changed using
- * {@link #setViewType(int)} method. Using SurfaceView is recommended in most cases for saving
- * battery. TextureView might be preferred for supporting various UIs such as animation and
- * translucency.
- *
- * <p>
- * <em> Differences between {@link VideoView} class : </em>
- * VideoView2 covers and inherits the most of
- * VideoView's functionalities. The main differences are
- * <ul>
- * <li> VideoView2 inherits FrameLayout and renders videos using SurfaceView and TextureView
- * selectively while VideoView inherits SurfaceView class.
- * <li> VideoView2 is integrated with MediaControlView2 and a default MediaControlView2 instance is
- * attached to VideoView2 by default. If a developer does not want to use the default
- * MediaControlView2, needs to set enableControlView attribute to false. For instance,
- * <pre>
- * &lt;VideoView2
- *     android:id="@+id/video_view"
- *     xmlns:widget="http://schemas.android.com/apk/com.android.media.update"
- *     widget:enableControlView="false" /&gt;
- * </pre>
- * If a developer wants to attach a customed MediaControlView2, then set enableControlView attribute
- * to false and assign the customed media control widget using {@link #setMediaControlView2}.
- * <li> VideoView2 is integrated with MediaPlayer2 while VideoView is integrated with MediaPlayer.
- * <li> VideoView2 is integrated with MediaSession and so it responses with media key events.
- * A VideoView2 keeps a MediaSession instance internally and connects it to a corresponding
- * MediaControlView2 instance.
- * </p>
- * </ul>
- *
- * <p>
- * <em> Audio focus and audio attributes : </em>
- * By default, VideoView2 requests audio focus with
- * {@link AudioManager#AUDIOFOCUS_GAIN}. Use {@link #setAudioFocusRequest(int)} to change this
- * behavior. The default {@link AudioAttributes} used during playback have a usage of
- * {@link AudioAttributes#USAGE_MEDIA} and a content type of
- * {@link AudioAttributes#CONTENT_TYPE_MOVIE}, use {@link #setAudioAttributes(AudioAttributes)} to
- * modify them.
- *
- * <p>
- * Note: VideoView2 does not retain its full state when going into the background. In particular, it
- * does not restore the current play state, play position, selected tracks. Applications should save
- * and restore these on their own in {@link android.app.Activity#onSaveInstanceState} and
- * {@link android.app.Activity#onRestoreInstanceState}.
- */
-public class VideoView2 extends ViewGroupHelper<VideoView2Provider> {
-    /** @hide */
-    @IntDef({
-            VIEW_TYPE_TEXTUREVIEW,
-            VIEW_TYPE_SURFACEVIEW
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ViewType {}
-
-    /**
-     * Indicates video is rendering on SurfaceView.
-     *
-     * @see #setViewType
-     */
-    public static final int VIEW_TYPE_SURFACEVIEW = 1;
-
-    /**
-     * Indicates video is rendering on TextureView.
-     *
-     * @see #setViewType
-     */
-    public static final int VIEW_TYPE_TEXTUREVIEW = 2;
-
-    public VideoView2(@NonNull Context context) {
-        this(context, null);
-    }
-
-    public VideoView2(@NonNull Context context, @Nullable AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public VideoView2(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public VideoView2(
-            @NonNull Context context, @Nullable AttributeSet attrs,
-            int defStyleAttr, int defStyleRes) {
-        super((instance, superProvider, privateProvider) ->
-                ApiLoader.getProvider().createVideoView2(
-                        (VideoView2) instance, superProvider, privateProvider,
-                        attrs, defStyleAttr, defStyleRes),
-                context, attrs, defStyleAttr, defStyleRes);
-        mProvider.initialize(attrs, defStyleAttr, defStyleRes);
-    }
-
-    /**
-     * Sets MediaControlView2 instance. It will replace the previously assigned MediaControlView2
-     * instance if any.
-     *
-     * @param mediaControlView a media control view2 instance.
-     * @param intervalMs a time interval in milliseconds until VideoView2 hides MediaControlView2.
-     */
-    public void setMediaControlView2(MediaControlView2 mediaControlView, long intervalMs) {
-        mProvider.setMediaControlView2_impl(mediaControlView, intervalMs);
-    }
-
-    /**
-     * Returns MediaControlView2 instance which is currently attached to VideoView2 by default or by
-     * {@link #setMediaControlView2} method.
-     */
-    public MediaControlView2 getMediaControlView2() {
-        return mProvider.getMediaControlView2_impl();
-    }
-
-    /**
-     * Sets MediaMetadata2 instance. It will replace the previously assigned MediaMetadata2 instance
-     * if any.
-     *
-     * @param metadata a MediaMetadata2 instance.
-     * @hide
-     */
-    public void setMediaMetadata(MediaMetadata2 metadata) {
-        mProvider.setMediaMetadata_impl(metadata);
-    }
-
-    /**
-     * Returns MediaMetadata2 instance which is retrieved from MediaPlayer2 inside VideoView2 by
-     * default or by {@link #setMediaMetadata} method.
-     * @hide
-     */
-    public MediaMetadata2 getMediaMetadata() {
-        // TODO: add to Javadoc whether this value can be null or not when integrating with
-        // MediaSession2.
-        return mProvider.getMediaMetadata_impl();
-    }
-
-    /**
-     * Returns MediaController instance which is connected with MediaSession that VideoView2 is
-     * using. This method should be called when VideoView2 is attached to window, or it throws
-     * IllegalStateException, since internal MediaSession instance is not available until
-     * this view is attached to window. Please check {@link android.view.View#isAttachedToWindow}
-     * before calling this method.
-     *
-     * @throws IllegalStateException if interal MediaSession is not created yet.
-     * @hide  TODO: remove
-     */
-    @UnsupportedAppUsage
-    public MediaController getMediaController() {
-        return mProvider.getMediaController_impl();
-    }
-
-    /**
-     * Returns {@link android.media.SessionToken2} so that developers create their own
-     * {@link android.media.MediaController2} instance. This method should be called when VideoView2
-     * is attached to window, or it throws IllegalStateException.
-     *
-     * @throws IllegalStateException if interal MediaSession is not created yet.
-     */
-    public SessionToken2 getMediaSessionToken() {
-        return mProvider.getMediaSessionToken_impl();
-    }
-
-    /**
-     * Shows or hides closed caption or subtitles if there is any.
-     * The first subtitle track will be chosen if there multiple subtitle tracks exist.
-     * Default behavior of VideoView2 is not showing subtitle.
-     * @param enable shows closed caption or subtitles if this value is true, or hides.
-     */
-    public void setSubtitleEnabled(boolean enable) {
-        mProvider.setSubtitleEnabled_impl(enable);
-    }
-
-    /**
-     * Returns true if showing subtitle feature is enabled or returns false.
-     * Although there is no subtitle track or closed caption, it can return true, if the feature
-     * has been enabled by {@link #setSubtitleEnabled}.
-     */
-    public boolean isSubtitleEnabled() {
-        return mProvider.isSubtitleEnabled_impl();
-    }
-
-    /**
-     * Sets playback speed.
-     *
-     * It is expressed as a multiplicative factor, where normal speed is 1.0f. If it is less than
-     * or equal to zero, it will be just ignored and nothing will be changed. If it exceeds the
-     * maximum speed that internal engine supports, system will determine best handling or it will
-     * be reset to the normal speed 1.0f.
-     * @param speed the playback speed. It should be positive.
-     */
-    // TODO: Support this via MediaController2.
-    public void setSpeed(float speed) {
-        mProvider.setSpeed_impl(speed);
-    }
-
-    /**
-     * 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 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 video path.
-     *
-     * @param path the path of the video.
-     *
-     * @hide TODO remove
-     */
-    @UnsupportedAppUsage
-    public void setVideoPath(String path) {
-        mProvider.setVideoPath_impl(path);
-    }
-
-    /**
-     * Sets video URI.
-     *
-     * @param uri the URI of the video.
-     *
-     * @hide TODO remove
-     */
-    public void setVideoUri(Uri uri) {
-        mProvider.setVideoUri_impl(uri);
-    }
-
-    /**
-     * Sets video URI using specific headers.
-     *
-     * @param uri     the URI of the video.
-     * @param headers the headers for the URI request.
-     *                Note that the cross domain redirection is allowed by default, but that can be
-     *                changed with key/value pairs through the headers parameter with
-     *                "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value
-     *                to disallow or allow cross domain redirection.
-     *
-     * @hide TODO remove
-     */
-    public void setVideoUri(Uri uri, Map<String, String> headers) {
-        mProvider.setVideoUri_impl(uri, headers);
-    }
-
-    /**
-     * Sets {@link MediaItem2} object to render using VideoView2. Alternative way to set media
-     * object to VideoView2 is {@link #setDataSource}.
-     * @param mediaItem the MediaItem2 to play
-     * @see #setDataSource
-     */
-    public void setMediaItem(@NonNull MediaItem2 mediaItem) {
-        mProvider.setMediaItem_impl(mediaItem);
-    }
-
-    /**
-     * Sets {@link DataSourceDesc} object to render using VideoView2.
-     * @param dataSource the {@link DataSourceDesc} object to play.
-     * @see #setMediaItem
-     */
-    public void setDataSource(@NonNull DataSourceDesc dataSource) {
-        mProvider.setDataSource_impl(dataSource);
-    }
-
-    /**
-     * Selects which view will be used to render video between SurfacView and TextureView.
-     *
-     * @param viewType the view type to render video
-     * <ul>
-     * <li>{@link #VIEW_TYPE_SURFACEVIEW}
-     * <li>{@link #VIEW_TYPE_TEXTUREVIEW}
-     * </ul>
-     */
-    public void setViewType(@ViewType int viewType) {
-        mProvider.setViewType_impl(viewType);
-    }
-
-    /**
-     * Returns view type.
-     *
-     * @return view type. See {@see setViewType}.
-     */
-    @ViewType
-    public int getViewType() {
-        return mProvider.getViewType_impl();
-    }
-
-    /**
-     * Sets custom actions which will be shown as custom buttons in {@link MediaControlView2}.
-     *
-     * @param actionList A list of {@link PlaybackState.CustomAction}. The return value of
-     *                   {@link PlaybackState.CustomAction#getIcon()} will be used to draw buttons
-     *                   in {@link MediaControlView2}.
-     * @param executor executor to run callbacks on.
-     * @param listener A listener to be called when a custom button is clicked.
-     * @hide  TODO remove
-     */
-    public void setCustomActions(List<PlaybackState.CustomAction> actionList,
-            Executor executor, OnCustomActionListener listener) {
-        mProvider.setCustomActions_impl(actionList, executor, listener);
-    }
-
-    /**
-     * Registers a callback to be invoked when a view type change is done.
-     * {@see #setViewType(int)}
-     * @param l The callback that will be run
-     * @hide
-     */
-    @VisibleForTesting
-    @UnsupportedAppUsage
-    public void setOnViewTypeChangedListener(OnViewTypeChangedListener l) {
-        mProvider.setOnViewTypeChangedListener_impl(l);
-    }
-
-    /**
-     * Registers a callback to be invoked when the fullscreen mode should be changed.
-     * @param l The callback that will be run
-     * @hide  TODO remove
-     */
-    public void setFullScreenRequestListener(OnFullScreenRequestListener l) {
-        mProvider.setFullScreenRequestListener_impl(l);
-    }
-
-    /**
-     * Interface definition of a callback to be invoked when the view type has been changed.
-     *
-     * @hide
-     */
-    @VisibleForTesting
-    public interface OnViewTypeChangedListener {
-        /**
-         * Called when the view type has been changed.
-         * @see #setViewType(int)
-         * @param view the View whose view type is changed
-         * @param viewType
-         * <ul>
-         * <li>{@link #VIEW_TYPE_SURFACEVIEW}
-         * <li>{@link #VIEW_TYPE_TEXTUREVIEW}
-         * </ul>
-         */
-        @UnsupportedAppUsage
-        void onViewTypeChanged(View view, @ViewType int viewType);
-    }
-
-    /**
-     * Interface definition of a callback to be invoked to inform the fullscreen mode is changed.
-     * Application should handle the fullscreen mode accordingly.
-     * @hide  TODO remove
-     */
-    public interface OnFullScreenRequestListener {
-        /**
-         * Called to indicate a fullscreen mode change.
-         */
-        void onFullScreenRequest(View view, boolean fullScreen);
-    }
-
-    /**
-     * Interface definition of a callback to be invoked to inform that a custom action is performed.
-     * @hide  TODO remove
-     */
-    public interface OnCustomActionListener {
-        /**
-         * Called to indicate that a custom action is performed.
-         *
-         * @param action The action that was originally sent in the
-         *               {@link PlaybackState.CustomAction}.
-         * @param extras Optional extras.
-         */
-        void onCustomAction(String action, Bundle extras);
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        mProvider.onLayout_impl(changed, l, t, r, b);
-    }
-}
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index 70fc72f..34e8ed4 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -421,7 +421,7 @@
     }
 
     protected int getWorkSourceUid() {
-        return Binder.getThreadWorkSource();
+        return Binder.getCallingWorkSourceUid();
     }
 
     protected long getElapsedRealtimeMicro() {
diff --git a/core/java/com/android/internal/os/KernelCpuThreadReader.java b/core/java/com/android/internal/os/KernelCpuThreadReader.java
index 7c82a7e..ade5d05 100644
--- a/core/java/com/android/internal/os/KernelCpuThreadReader.java
+++ b/core/java/com/android/internal/os/KernelCpuThreadReader.java
@@ -21,6 +21,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
 
 import java.io.IOException;
 import java.nio.file.DirectoryStream;
@@ -33,6 +34,16 @@
  * Given a process, will iterate over the child threads of the process, and return the CPU usage
  * statistics for each child thread. The CPU usage statistics contain the amount of time spent in a
  * frequency band.
+ *
+ * <p>Frequencies are bucketed together to reduce the amount of data created. This means that we
+ * return {@link #NUM_BUCKETS} frequencies instead of the full number. Frequencies are reported as
+ * the lowest frequency in that range. Frequencies are spread as evenly as possible across the
+ * buckets. The buckets do not cross over the little/big frequencies reported.
+ *
+ * <p>N.B.: In order to bucket across little/big frequencies correctly, we assume that the {@code
+ * time_in_state} file contains every little core frequency in ascending order, followed by every
+ * big core frequency in ascending order. This assumption might not hold for devices with different
+ * kernel implementations of the {@code time_in_state} file generation.
  */
 public class KernelCpuThreadReader {
 
@@ -80,6 +91,11 @@
             DEFAULT_PROC_PATH.resolve("self/time_in_state");
 
     /**
+     * Number of frequency buckets
+     */
+    private static final int NUM_BUCKETS = 8;
+
+    /**
      * Where the proc filesystem is mounted
      */
     private final Path mProcPath;
@@ -95,6 +111,11 @@
      */
     private final ProcTimeInStateReader mProcTimeInStateReader;
 
+    /**
+     * Used to sort frequencies and usage times into buckets
+     */
+    private final FrequencyBucketCreator mFrequencyBucketCreator;
+
     private KernelCpuThreadReader() throws IOException {
         this(DEFAULT_PROC_PATH, DEFAULT_INITIAL_TIME_IN_STATE_PATH);
     }
@@ -111,12 +132,10 @@
         mProcPath = procPath;
         mProcTimeInStateReader = new ProcTimeInStateReader(initialTimeInStatePath);
 
-        // Copy mProcTimeInState's frequencies, casting the longs to ints
-        long[] frequenciesKhz = mProcTimeInStateReader.getFrequenciesKhz();
-        mFrequenciesKhz = new int[frequenciesKhz.length];
-        for (int i = 0; i < frequenciesKhz.length; i++) {
-            mFrequenciesKhz[i] = (int) frequenciesKhz[i];
-        }
+        // Copy mProcTimeInState's frequencies and initialize bucketing
+        final long[] frequenciesKhz = mProcTimeInStateReader.getFrequenciesKhz();
+        mFrequencyBucketCreator = new FrequencyBucketCreator(frequenciesKhz, NUM_BUCKETS);
+        mFrequenciesKhz = mFrequencyBucketCreator.getBucketMinFrequencies(frequenciesKhz);
     }
 
     /**
@@ -228,12 +247,7 @@
         if (cpuUsagesLong == null) {
             return null;
         }
-
-        // Convert long[] to int[]
-        final int[] cpuUsages = new int[cpuUsagesLong.length];
-        for (int i = 0; i < cpuUsagesLong.length; i++) {
-            cpuUsages[i] = (int) cpuUsagesLong[i];
-        }
+        int[] cpuUsages = mFrequencyBucketCreator.getBucketedValues(cpuUsagesLong);
 
         return new ThreadCpuUsage(threadId, threadName, cpuUsages);
     }
@@ -266,6 +280,132 @@
     }
 
     /**
+     * Puts frequencies and usage times into buckets
+     */
+    @VisibleForTesting
+    public static class FrequencyBucketCreator {
+        private final int mNumBuckets;
+        private final int mNumFrequencies;
+        private final int mBigFrequenciesStartIndex;
+        private final int mLittleNumBuckets;
+        private final int mBigNumBuckets;
+        private final int mLittleBucketSize;
+        private final int mBigBucketSize;
+
+        /**
+         * Buckets based of a list of frequencies
+         *
+         * @param frequencies the frequencies to base buckets off
+         * @param numBuckets how many buckets to create
+         */
+        @VisibleForTesting
+        public FrequencyBucketCreator(long[] frequencies, int numBuckets) {
+            Preconditions.checkArgument(numBuckets > 0);
+
+            mNumFrequencies = frequencies.length;
+            mBigFrequenciesStartIndex = getBigFrequenciesStartIndex(frequencies);
+
+            final int littleNumBuckets;
+            final int bigNumBuckets;
+            if (mBigFrequenciesStartIndex < frequencies.length) {
+                littleNumBuckets = numBuckets / 2;
+                bigNumBuckets = numBuckets - littleNumBuckets;
+            } else {
+                // If we've got no big frequencies, set all buckets to little frequencies
+                littleNumBuckets = numBuckets;
+                bigNumBuckets = 0;
+            }
+
+            // Ensure that we don't have more buckets than frequencies
+            mLittleNumBuckets = Math.min(littleNumBuckets, mBigFrequenciesStartIndex);
+            mBigNumBuckets = Math.min(
+                    bigNumBuckets, frequencies.length - mBigFrequenciesStartIndex);
+            mNumBuckets = mLittleNumBuckets + mBigNumBuckets;
+
+            // Set the size of each little and big bucket. If they have no buckets, the size is zero
+            mLittleBucketSize = mLittleNumBuckets == 0 ? 0 :
+                    mBigFrequenciesStartIndex / mLittleNumBuckets;
+            mBigBucketSize = mBigNumBuckets == 0 ? 0 :
+                    (frequencies.length - mBigFrequenciesStartIndex) / mBigNumBuckets;
+        }
+
+        /**
+         * Find the index where frequencies change from little core to big core
+         */
+        @VisibleForTesting
+        public static int getBigFrequenciesStartIndex(long[] frequenciesKhz) {
+            for (int i = 0; i < frequenciesKhz.length - 1; i++) {
+                if (frequenciesKhz[i] > frequenciesKhz[i + 1]) {
+                    return i + 1;
+                }
+            }
+
+            return frequenciesKhz.length;
+        }
+
+        /**
+         * Get the minimum frequency in each bucket
+         */
+        @VisibleForTesting
+        public int[] getBucketMinFrequencies(long[] frequenciesKhz) {
+            Preconditions.checkArgument(frequenciesKhz.length == mNumFrequencies);
+            // If there's only one bucket, we bucket everything together so the first bucket is the
+            // min frequency
+            if (mNumBuckets == 1) {
+                return new int[]{(int) frequenciesKhz[0]};
+            }
+
+            final int[] bucketMinFrequencies = new int[mNumBuckets];
+            // Initialize little buckets min frequencies
+            for (int i = 0; i < mLittleNumBuckets; i++) {
+                bucketMinFrequencies[i] = (int) frequenciesKhz[i * mLittleBucketSize];
+            }
+            // Initialize big buckets min frequencies
+            for (int i = 0; i < mBigNumBuckets; i++) {
+                final int frequencyIndex = mBigFrequenciesStartIndex + i * mBigBucketSize;
+                bucketMinFrequencies[mLittleNumBuckets + i] = (int) frequenciesKhz[frequencyIndex];
+            }
+            return bucketMinFrequencies;
+        }
+
+        /**
+         * Put an array of values into buckets. This takes a {@code long[]} and returns {@code
+         * int[]} as everywhere this method is used will have to do the conversion anyway, so we
+         * save time by doing it here instead
+         *
+         * @param values the values to bucket
+         * @return the bucketed usage times
+         */
+        @VisibleForTesting
+        public int[] getBucketedValues(long[] values) {
+            Preconditions.checkArgument(values.length == mNumFrequencies);
+            final int[] bucketed = new int[mNumBuckets];
+
+            // If there's only one bucket, add all frequencies in
+            if (mNumBuckets == 1) {
+                for (int i = 0; i < values.length; i++) {
+                    bucketed[0] += values[i];
+                }
+                return bucketed;
+            }
+
+            // Initialize the little buckets
+            for (int i = 0; i < mBigFrequenciesStartIndex; i++) {
+                final int bucketIndex = Math.min(i / mLittleBucketSize, mLittleNumBuckets - 1);
+                bucketed[bucketIndex] += values[i];
+            }
+            // Initialize the big buckets
+            for (int i = mBigFrequenciesStartIndex; i < values.length; i++) {
+                final int bucketIndex = Math.min(
+                        mLittleNumBuckets + (i - mBigFrequenciesStartIndex) / mBigBucketSize,
+                        mNumBuckets - 1);
+                bucketed[bucketIndex] += values[i];
+            }
+            return bucketed;
+        }
+    }
+
+    /**
      * CPU usage of a process
      */
     public static class ProcessCpuUsage {
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index fd042b3..4f8bbc1 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -904,19 +904,24 @@
     return IPCThreadState::self()->getStrictModePolicy();
 }
 
-static jint android_os_Binder_setThreadWorkSource(jint workSource)
+static jlong android_os_Binder_setCallingWorkSourceUid(jint workSource)
 {
-    return IPCThreadState::self()->setWorkSource(workSource);
+    return IPCThreadState::self()->setCallingWorkSourceUid(workSource);
 }
 
-static jint android_os_Binder_getThreadWorkSource()
+static jlong android_os_Binder_getCallingWorkSourceUid()
 {
-    return IPCThreadState::self()->getWorkSource();
+    return IPCThreadState::self()->getCallingWorkSourceUid();
 }
 
-static jint android_os_Binder_clearThreadWorkSource()
+static jlong android_os_Binder_clearCallingWorkSource()
 {
-    return IPCThreadState::self()->clearWorkSource();
+    return IPCThreadState::self()->clearCallingWorkSource();
+}
+
+static void android_os_Binder_restoreCallingWorkSource(long token)
+{
+    IPCThreadState::self()->restoreCallingWorkSource(token);
 }
 
 static void android_os_Binder_flushPendingCommands(JNIEnv* env, jobject clazz)
@@ -962,11 +967,12 @@
     // @CriticalNative
     { "getThreadStrictModePolicy", "()I", (void*)android_os_Binder_getThreadStrictModePolicy },
     // @CriticalNative
-    { "setThreadWorkSource", "(I)I", (void*)android_os_Binder_setThreadWorkSource },
+    { "setCallingWorkSourceUid", "(I)J", (void*)android_os_Binder_setCallingWorkSourceUid },
     // @CriticalNative
-    { "getThreadWorkSource", "()I", (void*)android_os_Binder_getThreadWorkSource },
+    { "getCallingWorkSourceUid", "()I", (void*)android_os_Binder_getCallingWorkSourceUid },
     // @CriticalNative
-    { "clearThreadWorkSource", "()I", (void*)android_os_Binder_clearThreadWorkSource },
+    { "clearCallingWorkSource", "()J", (void*)android_os_Binder_clearCallingWorkSource },
+    { "restoreCallingWorkSource", "(J)V", (void*)android_os_Binder_restoreCallingWorkSource },
     { "flushPendingCommands", "()V", (void*)android_os_Binder_flushPendingCommands },
     { "getNativeBBinderHolder", "()J", (void*)android_os_Binder_getNativeBBinderHolder },
     { "getNativeFinalizer", "()J", (void*)android_os_Binder_getNativeFinalizer },
diff --git a/core/proto/android/server/enums.proto b/core/proto/android/server/enums.proto
index ef02438..89f7010 100644
--- a/core/proto/android/server/enums.proto
+++ b/core/proto/android/server/enums.proto
@@ -28,3 +28,13 @@
     // Device idle mode - active in full mode.
     DEVICE_IDLE_MODE_DEEP = 2;
 }
+
+enum ErrorSource {
+    ERROR_SOURCE_UNKNOWN = 0;
+    // Data app
+    DATA_APP = 1;
+    // System app
+    SYSTEM_APP = 2;
+    // System server.
+    SYSTEM_SERVER = 3;
+}
diff --git a/core/proto/android/server/location/enums.proto b/core/proto/android/server/location/enums.proto
new file mode 100644
index 0000000..b6dc589
--- /dev/null
+++ b/core/proto/android/server/location/enums.proto
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package android.server.location;
+
+option java_outer_classname = "ServerLocationProtoEnums";
+option java_multiple_files = true;
+
+// GPS Signal Quality levels,
+// primarily used by location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
+enum GpsSignalQualityEnum {
+    GPS_SIGNAL_QUALITY_UNKNOWN = -1;
+    GPS_SIGNAL_QUALITY_POOR = 0;
+    GPS_SIGNAL_QUALITY_GOOD = 1;
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 62896be..20fec3c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -359,6 +359,7 @@
     <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST" />
     <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_SUBSCRIPTION_REMEDIATION" />
     <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_LAUNCH_OSU_VIEW" />
+    <protected-broadcast android:name="android.net.wifi.action.WIFI_NETWORK_SUGGESTION_POST_CONNECTION" />
     <protected-broadcast android:name="android.net.wifi.supplicant.CONNECTION_CHANGE" />
     <protected-broadcast android:name="android.net.wifi.supplicant.STATE_CHANGE" />
     <protected-broadcast android:name="android.net.wifi.p2p.STATE_CHANGED" />
@@ -642,7 +643,6 @@
         <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.READ_CONTACTS"
-        android:permissionGroup="android.permission-group.CONTACTS"
         android:label="@string/permlab_readContacts"
         android:description="@string/permdesc_readContacts"
         android:protectionLevel="dangerous" />
@@ -651,7 +651,6 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.WRITE_CONTACTS"
-        android:permissionGroup="android.permission-group.CONTACTS"
         android:label="@string/permlab_writeContacts"
         android:description="@string/permdesc_writeContacts"
         android:protectionLevel="dangerous" />
@@ -673,7 +672,6 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.READ_CALENDAR"
-        android:permissionGroup="android.permission-group.CALENDAR"
         android:label="@string/permlab_readCalendar"
         android:description="@string/permdesc_readCalendar"
         android:protectionLevel="dangerous" />
@@ -682,7 +680,6 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.WRITE_CALENDAR"
-        android:permissionGroup="android.permission-group.CALENDAR"
         android:label="@string/permlab_writeCalendar"
         android:description="@string/permdesc_writeCalendar"
         android:protectionLevel="dangerous" />
@@ -704,7 +701,6 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.SEND_SMS"
-        android:permissionGroup="android.permission-group.SMS"
         android:label="@string/permlab_sendSms"
         android:description="@string/permdesc_sendSms"
         android:permissionFlags="costsMoney"
@@ -714,7 +710,6 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.RECEIVE_SMS"
-        android:permissionGroup="android.permission-group.SMS"
         android:label="@string/permlab_receiveSms"
         android:description="@string/permdesc_receiveSms"
         android:protectionLevel="dangerous"/>
@@ -723,7 +718,6 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.READ_SMS"
-        android:permissionGroup="android.permission-group.SMS"
         android:label="@string/permlab_readSms"
         android:description="@string/permdesc_readSms"
         android:protectionLevel="dangerous" />
@@ -732,7 +726,6 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.RECEIVE_WAP_PUSH"
-        android:permissionGroup="android.permission-group.SMS"
         android:label="@string/permlab_receiveWapPush"
         android:description="@string/permdesc_receiveWapPush"
         android:protectionLevel="dangerous" />
@@ -741,7 +734,6 @@
         <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.RECEIVE_MMS"
-        android:permissionGroup="android.permission-group.SMS"
         android:label="@string/permlab_receiveMms"
         android:description="@string/permdesc_receiveMms"
         android:protectionLevel="dangerous" />
@@ -759,7 +751,6 @@
          <p>Protection level: dangerous
          @hide Pending API council approval -->
     <permission android:name="android.permission.READ_CELL_BROADCASTS"
-        android:permissionGroup="android.permission-group.SMS"
         android:label="@string/permlab_readCellBroadcasts"
         android:description="@string/permdesc_readCellBroadcasts"
         android:protectionLevel="dangerous" />
@@ -801,7 +792,6 @@
      @deprecated replaced by new strongly-typed permission groups in Q.
      -->
     <permission android:name="android.permission.READ_EXTERNAL_STORAGE"
-        android:permissionGroup="android.permission-group.STORAGE"
         android:label="@string/permlab_sdcardRead"
         android:description="@string/permdesc_sdcardRead"
         android:protectionLevel="normal" />
@@ -822,7 +812,6 @@
          @deprecated replaced by new strongly-typed permission groups in Q.
     -->
     <permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
-        android:permissionGroup="android.permission-group.STORAGE"
         android:label="@string/permlab_sdcardWrite"
         android:description="@string/permdesc_sdcardWrite"
         android:protectionLevel="normal" />
@@ -838,14 +827,12 @@
 
     <!-- Allows an application to read the user's shared audio collection. -->
     <permission android:name="android.permission.READ_MEDIA_AUDIO"
-        android:permissionGroup="android.permission-group.MEDIA_AURAL"
         android:label="@string/permlab_audioRead"
         android:description="@string/permdesc_audioRead"
         android:protectionLevel="dangerous" />
 
     <!-- Allows an application to modify the user's shared audio collection. -->
     <permission android:name="android.permission.WRITE_MEDIA_AUDIO"
-        android:permissionGroup="android.permission-group.MEDIA_AURAL"
         android:label="@string/permlab_audioWrite"
         android:description="@string/permdesc_audioWrite"
         android:protectionLevel="dangerous" />
@@ -861,28 +848,24 @@
 
     <!-- Allows an application to read the user's shared images collection. -->
     <permission android:name="android.permission.READ_MEDIA_IMAGES"
-        android:permissionGroup="android.permission-group.MEDIA_VISUAL"
         android:label="@string/permlab_imagesRead"
         android:description="@string/permdesc_imagesRead"
         android:protectionLevel="dangerous" />
 
     <!-- Allows an application to modify the user's shared images collection. -->
     <permission android:name="android.permission.WRITE_MEDIA_IMAGES"
-        android:permissionGroup="android.permission-group.MEDIA_VISUAL"
         android:label="@string/permlab_imagesWrite"
         android:description="@string/permdesc_imagesWrite"
         android:protectionLevel="dangerous" />
 
     <!-- Allows an application to read the user's shared video collection. -->
     <permission android:name="android.permission.READ_MEDIA_VIDEO"
-        android:permissionGroup="android.permission-group.MEDIA_VISUAL"
         android:label="@string/permlab_videoRead"
         android:description="@string/permdesc_videoRead"
         android:protectionLevel="dangerous" />
 
     <!-- Allows an application to modify the user's shared video collection. -->
     <permission android:name="android.permission.WRITE_MEDIA_VIDEO"
-        android:permissionGroup="android.permission-group.MEDIA_VISUAL"
         android:label="@string/permlab_videoWrite"
         android:description="@string/permdesc_videoWrite"
         android:protectionLevel="dangerous" />
@@ -890,7 +873,6 @@
     <!-- Allows an application to access any geographic locations persisted in the
          user's shared collection. -->
     <permission android:name="android.permission.ACCESS_MEDIA_LOCATION"
-        android:permissionGroup="android.permission-group.MEDIA_VISUAL"
         android:label="@string/permlab_mediaLocation"
         android:description="@string/permdesc_mediaLocation"
         android:protectionLevel="dangerous" />
@@ -921,7 +903,6 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.ACCESS_FINE_LOCATION"
-        android:permissionGroup="android.permission-group.LOCATION"
         android:label="@string/permlab_accessFineLocation"
         android:description="@string/permdesc_accessFineLocation"
         android:backgroundPermission="android.permission.ACCESS_BACKGROUND_LOCATION"
@@ -932,7 +913,6 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.ACCESS_COARSE_LOCATION"
-        android:permissionGroup="android.permission-group.LOCATION"
         android:label="@string/permlab_accessCoarseLocation"
         android:description="@string/permdesc_accessCoarseLocation"
         android:backgroundPermission="android.permission.ACCESS_BACKGROUND_LOCATION"
@@ -945,7 +925,6 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"
-        android:permissionGroup="android.permission-group.LOCATION"
         android:label="@string/permlab_accessBackgroundLocation"
         android:description="@string/permdesc_accessBackgroundLocation"
         android:protectionLevel="dangerous|instant" />
@@ -986,7 +965,6 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.READ_CALL_LOG"
-        android:permissionGroup="android.permission-group.CALL_LOG"
         android:label="@string/permlab_readCallLog"
         android:description="@string/permdesc_readCallLog"
         android:protectionLevel="dangerous" />
@@ -1005,7 +983,6 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.WRITE_CALL_LOG"
-        android:permissionGroup="android.permission-group.CALL_LOG"
         android:label="@string/permlab_writeCallLog"
         android:description="@string/permdesc_writeCallLog"
         android:protectionLevel="dangerous" />
@@ -1016,7 +993,6 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.PROCESS_OUTGOING_CALLS"
-        android:permissionGroup="android.permission-group.CALL_LOG"
         android:label="@string/permlab_processOutgoingCalls"
         android:description="@string/permdesc_processOutgoingCalls"
         android:protectionLevel="dangerous" />
@@ -1048,7 +1024,6 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.READ_PHONE_STATE"
-        android:permissionGroup="android.permission-group.PHONE"
         android:label="@string/permlab_readPhoneState"
         android:description="@string/permdesc_readPhoneState"
         android:protectionLevel="dangerous" />
@@ -1057,7 +1032,6 @@
          granted by {@link #READ_PHONE_STATE} but is exposed to instant applications.
          <p>Protection level: dangerous-->
     <permission android:name="android.permission.READ_PHONE_NUMBERS"
-        android:permissionGroup="android.permission-group.PHONE"
         android:label="@string/permlab_readPhoneNumbers"
         android:description="@string/permdesc_readPhoneNumbers"
         android:protectionLevel="dangerous|instant" />
@@ -1067,7 +1041,6 @@
         <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.CALL_PHONE"
-        android:permissionGroup="android.permission-group.PHONE"
         android:permissionFlags="costsMoney"
         android:label="@string/permlab_callPhone"
         android:description="@string/permdesc_callPhone"
@@ -1077,7 +1050,6 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL"
-        android:permissionGroup="android.permission-group.PHONE"
         android:label="@string/permlab_addVoicemail"
         android:description="@string/permdesc_addVoicemail"
         android:protectionLevel="dangerous" />
@@ -1086,7 +1058,6 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.USE_SIP"
-        android:permissionGroup="android.permission-group.PHONE"
         android:description="@string/permdesc_use_sip"
         android:label="@string/permlab_use_sip"
         android:protectionLevel="dangerous"/>
@@ -1095,7 +1066,6 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.ANSWER_PHONE_CALLS"
-        android:permissionGroup="android.permission-group.PHONE"
         android:label="@string/permlab_answerPhoneCalls"
         android:description="@string/permdesc_answerPhoneCalls"
         android:protectionLevel="dangerous|runtime" />
@@ -1123,7 +1093,6 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.ACCEPT_HANDOVER"
-                android:permissionGroup="android.permission-group.PHONE"
                 android.label="@string/permlab_acceptHandover"
                 android:description="@string/permdesc_acceptHandovers"
                 android:protectionLevel="dangerous" />
@@ -1147,7 +1116,6 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.RECORD_AUDIO"
-        android:permissionGroup="android.permission-group.MICROPHONE"
         android:label="@string/permlab_recordAudio"
         android:description="@string/permdesc_recordAudio"
         android:protectionLevel="dangerous|instant"/>
@@ -1169,7 +1137,6 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.ACTIVITY_RECOGNITION"
-        android:permissionGroup="android.permission-group.ACTIVITY_RECOGNITION"
         android:label="@string/permlab_activityRecognition"
         android:description="@string/permdesc_activityRecognition"
         android:protectionLevel="dangerous|instant" />
@@ -1218,7 +1185,6 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.CAMERA"
-        android:permissionGroup="android.permission-group.CAMERA"
         android:label="@string/permlab_camera"
         android:description="@string/permdesc_camera"
         android:protectionLevel="dangerous|instant" />
@@ -1242,7 +1208,6 @@
          measure what is happening inside his/her body, such as heart rate.
          <p>Protection level: dangerous -->
     <permission android:name="android.permission.BODY_SENSORS"
-        android:permissionGroup="android.permission-group.SENSORS"
         android:label="@string/permlab_bodySensors"
         android:description="@string/permdesc_bodySensors"
         android:protectionLevel="dangerous" />
@@ -1576,6 +1541,14 @@
     <permission android:name="android.permission.NETWORK_SETUP_WIZARD"
         android:protectionLevel="signature|setup" />
 
+    <!-- Allows Managed Provisioning to call methods in Networking services
+         <p>Not for use by any other third-party or privileged applications.
+         @SystemApi
+         @hide This should only be used by ManagedProvisioning app.
+    -->
+    <permission android:name="android.permission.NETWORK_MANAGED_PROVISIONING"
+        android:protectionLevel="signature|privileged" />
+
     <!-- #SystemApi @hide Allows applications to access information about LoWPAN interfaces.
          <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.ACCESS_LOWPAN_STATE"
@@ -1721,7 +1694,6 @@
     <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.GET_ACCOUNTS"
-        android:permissionGroup="android.permission-group.CONTACTS"
         android:protectionLevel="dangerous"
         android:description="@string/permdesc_getAccounts"
         android:label="@string/permlab_getAccounts" />
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index 4ee9731..433ae39 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -116,17 +116,6 @@
         android:contentDescription="@string/expand_button_content_description_collapsed"
         />
     <ImageView
-        android:id="@+id/profile_badge"
-        android:layout_width="@dimen/notification_badge_size"
-        android:layout_height="@dimen/notification_badge_size"
-        android:layout_gravity="center"
-        android:layout_marginStart="4dp"
-        android:paddingTop="1dp"
-        android:scaleType="fitCenter"
-        android:visibility="gone"
-        android:contentDescription="@string/notification_work_profile_content_description"
-        />
-    <ImageView
         android:id="@+id/alerted_icon"
         android:layout_width="@dimen/notification_alerted_size"
         android:layout_height="@dimen/notification_alerted_size"
@@ -138,6 +127,17 @@
         android:contentDescription="@string/notification_alerted_content_description"
         android:src="@drawable/ic_notifications_alerted"
         android:tint="@color/notification_secondary_text_color_light"
+    />
+    <ImageView
+        android:id="@+id/profile_badge"
+        android:layout_width="@dimen/notification_badge_size"
+        android:layout_height="@dimen/notification_badge_size"
+        android:layout_gravity="center"
+        android:layout_marginStart="4dp"
+        android:paddingTop="1dp"
+        android:scaleType="fitCenter"
+        android:visibility="gone"
+        android:contentDescription="@string/notification_work_profile_content_description"
         />
     <LinearLayout
         android:id="@+id/app_ops"
diff --git a/core/res/res/values-night/themes_device_defaults.xml b/core/res/res/values-night/themes_device_defaults.xml
index 931674a..84c6446 100644
--- a/core/res/res/values-night/themes_device_defaults.xml
+++ b/core/res/res/values-night/themes_device_defaults.xml
@@ -52,6 +52,9 @@
     <!-- DeviceDefault theme for a window that should look like the Settings app.  -->
     <style name="Theme.DeviceDefault.Settings" parent="Theme.DeviceDefault">
         <item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item>
+        <item name="colorBackground">@color/primary_dark_device_default_settings</item>
+
+        <item name="listDivider">@color/list_divider_color_dark</item>
     </style>
 
     <!-- Theme for the dialog shown when an app crashes or ANRs. -->
diff --git a/core/res/res/values/colors_device_defaults.xml b/core/res/res/values/colors_device_defaults.xml
index 0fe80a1..ded916f 100644
--- a/core/res/res/values/colors_device_defaults.xml
+++ b/core/res/res/values/colors_device_defaults.xml
@@ -42,4 +42,7 @@
     <!-- Error color -->
     <color name="error_color_device_default_dark">@color/error_color_material_dark</color>
     <color name="error_color_device_default_light">@color/error_color_material_light</color>
+
+    <color name="list_divider_color_light">#64000000</color>
+    <color name="list_divider_color_dark">#85ffffff</color>
 </resources>
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index 3385527..fa009bd 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -1475,6 +1475,8 @@
 
         <!-- Toolbar attributes -->
         <item name="toolbarStyle">@style/Widget.DeviceDefault.Toolbar</item>
+
+        <item name="listDivider">@color/list_divider_color_light</item>
     </style>
 
     <!-- @hide DeviceDefault theme for a window that should use Settings theme colors
diff --git a/core/tests/coretests/src/android/os/BinderTest.java b/core/tests/coretests/src/android/os/BinderTest.java
index 1beb598..534c5cd 100644
--- a/core/tests/coretests/src/android/os/BinderTest.java
+++ b/core/tests/coretests/src/android/os/BinderTest.java
@@ -21,17 +21,26 @@
 import junit.framework.TestCase;
 
 public class BinderTest extends TestCase {
+    private static final int UID = 100;
 
     @SmallTest
     public void testSetWorkSource() throws Exception {
-        Binder.setThreadWorkSource(100);
-        assertEquals(100, Binder.getThreadWorkSource());
+        Binder.setCallingWorkSourceUid(UID);
+        assertEquals(UID, Binder.getCallingWorkSourceUid());
     }
 
     @SmallTest
     public void testClearWorkSource() throws Exception {
-        Binder.setThreadWorkSource(100);
-        Binder.clearThreadWorkSource();
-        assertEquals(-1, Binder.getThreadWorkSource());
+        Binder.setCallingWorkSourceUid(UID);
+        Binder.clearCallingWorkSource();
+        assertEquals(-1, Binder.getCallingWorkSourceUid());
+    }
+
+    @SmallTest
+    public void testRestoreWorkSource() throws Exception {
+        Binder.setCallingWorkSourceUid(UID);
+        long token = Binder.clearCallingWorkSource();
+        Binder.restoreCallingWorkSource(token);
+        assertEquals(UID, Binder.getCallingWorkSourceUid());
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java
index b9ef434..c866bc4 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.os;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -139,4 +140,133 @@
 
         assertEquals(threadCount, THREAD_IDS.length);
     }
+
+    @Test
+    public void testBucketSetup_simple() {
+        long[] frequencies = {1, 2, 3, 4, 1, 2, 3, 4};
+        KernelCpuThreadReader.FrequencyBucketCreator
+                frequencyBucketCreator = new KernelCpuThreadReader.FrequencyBucketCreator(
+                frequencies, 4);
+        assertArrayEquals(
+                new int[]{1, 3, 1, 3},
+                frequencyBucketCreator.getBucketMinFrequencies(frequencies));
+        assertArrayEquals(
+                new int[]{2, 2, 2, 2},
+                frequencyBucketCreator.getBucketedValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
+    }
+
+    @Test
+    public void testBucketSetup_noBig() {
+        long[] frequencies = {1, 2, 3, 4, 5, 6, 7, 8};
+        KernelCpuThreadReader.FrequencyBucketCreator
+                frequencyBucketCreator = new KernelCpuThreadReader.FrequencyBucketCreator(
+                frequencies, 4);
+        assertArrayEquals(
+                new int[]{1, 3, 5, 7},
+                frequencyBucketCreator.getBucketMinFrequencies(frequencies));
+        assertArrayEquals(
+                new int[]{2, 2, 2, 2},
+                frequencyBucketCreator.getBucketedValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
+    }
+
+    @Test
+    public void testBucketSetup_moreLittle() {
+        long[] frequencies = {1, 2, 3, 4, 5, 1, 2, 3};
+        KernelCpuThreadReader.FrequencyBucketCreator
+                frequencyBucketCreator = new KernelCpuThreadReader.FrequencyBucketCreator(
+                frequencies, 4);
+        assertArrayEquals(
+                new int[]{1, 3, 1, 2},
+                frequencyBucketCreator.getBucketMinFrequencies(frequencies));
+        assertArrayEquals(
+                new int[]{2, 3, 1, 2},
+                frequencyBucketCreator.getBucketedValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
+    }
+
+    @Test
+    public void testBucketSetup_moreBig() {
+        long[] frequencies = {1, 2, 3, 1, 2, 3, 4, 5};
+        KernelCpuThreadReader.FrequencyBucketCreator
+                frequencyBucketCreator = new KernelCpuThreadReader.FrequencyBucketCreator(
+                frequencies, 4);
+        assertArrayEquals(
+                new int[]{1, 2, 1, 3},
+                frequencyBucketCreator.getBucketMinFrequencies(frequencies));
+        assertArrayEquals(
+                new int[]{1, 2, 2, 3},
+                frequencyBucketCreator.getBucketedValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
+    }
+
+    @Test
+    public void testBucketSetup_equalBuckets() {
+        long[] frequencies = {1, 2, 3, 4, 1, 2, 3, 4};
+        KernelCpuThreadReader.FrequencyBucketCreator
+                frequencyBucketCreator = new KernelCpuThreadReader.FrequencyBucketCreator(
+                frequencies, 8);
+        assertArrayEquals(
+                new int[]{1, 2, 3, 4, 1, 2, 3, 4},
+                frequencyBucketCreator.getBucketMinFrequencies(frequencies));
+        assertArrayEquals(
+                new int[]{1, 1, 1, 1, 1, 1, 1, 1},
+                frequencyBucketCreator.getBucketedValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
+    }
+
+    @Test
+    public void testBucketSetup_moreBigBucketsThanFrequencies() {
+        long[] frequencies = {1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3};
+        KernelCpuThreadReader.FrequencyBucketCreator
+                frequencyBucketCreator = new KernelCpuThreadReader.FrequencyBucketCreator(
+                frequencies, 8);
+        assertArrayEquals(
+                new int[]{1, 3, 5, 7, 1, 2, 3},
+                frequencyBucketCreator.getBucketMinFrequencies(frequencies));
+        assertArrayEquals(
+                new int[]{2, 2, 2, 3, 1, 1, 1},
+                frequencyBucketCreator.getBucketedValues(
+                        new long[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}));
+    }
+
+    @Test
+    public void testBucketSetup_oneBucket() {
+        long[] frequencies = {1, 2, 3, 4, 2, 3, 4, 5};
+        KernelCpuThreadReader.FrequencyBucketCreator
+                frequencyBucketCreator = new KernelCpuThreadReader.FrequencyBucketCreator(
+                frequencies, 1);
+        assertArrayEquals(
+                new int[]{1},
+                frequencyBucketCreator.getBucketMinFrequencies(frequencies));
+        assertArrayEquals(
+                new int[]{8},
+                frequencyBucketCreator.getBucketedValues(
+                        new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
+    }
+
+
+    @Test
+    public void testGetBigFrequenciesStartIndex_simple() {
+        assertEquals(
+                3, KernelCpuThreadReader.FrequencyBucketCreator.getBigFrequenciesStartIndex(
+                        new long[]{1, 2, 3, 1, 2, 3}));
+    }
+
+    @Test
+    public void testGetBigFrequenciesStartIndex_moreLittle() {
+        assertEquals(
+                4, KernelCpuThreadReader.FrequencyBucketCreator.getBigFrequenciesStartIndex(
+                        new long[]{1, 2, 3, 4, 1, 2}));
+    }
+
+    @Test
+    public void testGetBigFrequenciesStartIndex_moreBig() {
+        assertEquals(
+                2, KernelCpuThreadReader.FrequencyBucketCreator.getBigFrequenciesStartIndex(
+                        new long[]{1, 2, 1, 2, 3, 4}));
+    }
+
+    @Test
+    public void testGetBigFrequenciesStartIndex_noBig() {
+        assertEquals(
+                4, KernelCpuThreadReader.FrequencyBucketCreator.getBigFrequenciesStartIndex(
+                        new long[]{1, 2, 3, 4}));
+    }
 }
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 84cb5f8..c5bd039 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -96,6 +96,7 @@
         <permission name="android.permission.MANAGE_DEVICE_ADMINS"/>
         <permission name="android.permission.MANAGE_USERS"/>
         <permission name="android.permission.MASTER_CLEAR"/>
+        <permission name="android.permission.NETWORK_MANAGED_PROVISIONING"/>
         <permission name="android.permission.PERFORM_CDMA_PROVISIONING"/>
         <permission name="android.permission.SET_TIME"/>
         <permission name="android.permission.SET_TIME_ZONE"/>
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 835b735..6d58d95 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -23,6 +23,7 @@
 import android.app.KeyguardManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.Binder;
 import android.os.IBinder;
@@ -55,6 +56,7 @@
 import java.io.IOException;
 import java.security.InvalidKeyException;
 import java.util.Arrays;
+import java.util.Date;
 import java.util.List;
 import java.util.Locale;
 import java.util.concurrent.CompletableFuture;
@@ -699,11 +701,9 @@
         args.addEnum(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_SHA_2_384);
         args.addEnum(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_SHA_2_512);
         args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
-        args.addUnsignedLong(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
-                             KeymasterArguments.UINT64_MAX_VALUE);
-        args.addUnsignedLong(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
-                             KeymasterArguments.UINT64_MAX_VALUE);
-        args.addUnsignedLong(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, BigInteger.ZERO);
+        args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, new Date(Long.MAX_VALUE));
+        args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, new Date(Long.MAX_VALUE));
+        args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, new Date(0));
         return args;
     }
 
@@ -1254,7 +1254,7 @@
                     return new UserNotAuthenticatedException();
                 }
 
-                long fingerprintOnlySid = getFingerprintOnlySid();
+                final long fingerprintOnlySid = getFingerprintOnlySid();
                 if ((fingerprintOnlySid != 0)
                         && (keySids.contains(KeymasterArguments.toUint64(fingerprintOnlySid)))) {
                     // One of the key's SIDs is the current fingerprint SID -- user can be
@@ -1262,6 +1262,14 @@
                     return new UserNotAuthenticatedException();
                 }
 
+                final long faceOnlySid = getFaceOnlySid();
+                if ((faceOnlySid != 0)
+                        && (keySids.contains(KeymasterArguments.toUint64(faceOnlySid)))) {
+                    // One of the key's SIDs is the current face SID -- user can be
+                    // authenticated against that SID.
+                    return new UserNotAuthenticatedException();
+                }
+
                 // None of the key's SIDs can ever be authenticated
                 return new KeyPermanentlyInvalidatedException();
             }
@@ -1272,6 +1280,21 @@
         }
     }
 
+    private long getFaceOnlySid() {
+        final PackageManager packageManager = mContext.getPackageManager();
+        if (!packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)) {
+            return 0;
+        }
+        FaceManager faceManager = mContext.getSystemService(FaceManager.class);
+        if (faceManager == null) {
+            return 0;
+        }
+
+        // TODO: Restore USE_BIOMETRIC or USE_BIOMETRIC_INTERNAL permission check in
+        // FaceManager.getAuthenticatorId once the ID is no longer needed here.
+        return faceManager.getAuthenticatorId();
+    }
+
     private long getFingerprintOnlySid() {
         final PackageManager packageManager = mContext.getPackageManager();
         if (!packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
index 7bbc099..a2d2355 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
@@ -182,8 +182,8 @@
                     KeymasterDefs.KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED);
 
         boolean invalidatedByBiometricEnrollment = false;
-        if (keymasterSwEnforcedUserAuthenticators == KeymasterDefs.HW_AUTH_FINGERPRINT
-            || keymasterHwEnforcedUserAuthenticators == KeymasterDefs.HW_AUTH_FINGERPRINT) {
+        if (keymasterSwEnforcedUserAuthenticators == KeymasterDefs.HW_AUTH_BIOMETRIC
+            || keymasterHwEnforcedUserAuthenticators == KeymasterDefs.HW_AUTH_BIOMETRIC) {
             // Fingerprint-only key; will be invalidated if the root SID isn't in the SID list.
             invalidatedByBiometricEnrollment = keymasterSecureUserIds != null
                     && !keymasterSecureUserIds.isEmpty()
diff --git a/keystore/java/android/security/keystore/DeviceIdAttestationException.java b/keystore/java/android/security/keystore/DeviceIdAttestationException.java
index e18d193..13f50b1 100644
--- a/keystore/java/android/security/keystore/DeviceIdAttestationException.java
+++ b/keystore/java/android/security/keystore/DeviceIdAttestationException.java
@@ -16,11 +16,16 @@
 
 package android.security.keystore;
 
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+
 /**
  * Thrown when {@link AttestationUtils} is unable to attest the given device ids.
  *
  * @hide
  */
+@SystemApi
+@TestApi
 public class DeviceIdAttestationException extends Exception {
     /**
      * Constructs a new {@code DeviceIdAttestationException} with the current stack trace and the
diff --git a/keystore/java/android/security/keystore/KeymasterUtils.java b/keystore/java/android/security/keystore/KeymasterUtils.java
index f829bb7..52896b5 100644
--- a/keystore/java/android/security/keystore/KeymasterUtils.java
+++ b/keystore/java/android/security/keystore/KeymasterUtils.java
@@ -16,7 +16,7 @@
 
 package android.security.keystore;
 
-import android.app.ActivityManager;
+import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.security.GateKeeper;
 import android.security.KeyStore;
@@ -24,6 +24,8 @@
 import android.security.keymaster.KeymasterDefs;
 
 import java.security.ProviderException;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * @hide
@@ -121,35 +123,44 @@
 
         if (spec.getUserAuthenticationValidityDurationSeconds() == -1) {
             // Every use of this key needs to be authorized by the user. This currently means
-            // fingerprint-only auth.
+            // fingerprint or face auth.
             FingerprintManager fingerprintManager =
                     KeyStore.getApplicationContext().getSystemService(FingerprintManager.class);
+            FaceManager faceManager =
+                    KeyStore.getApplicationContext().getSystemService(FaceManager.class);
             // TODO: Restore USE_FINGERPRINT permission check in
             // FingerprintManager.getAuthenticatorId once the ID is no longer needed here.
-            long fingerprintOnlySid =
+            final long fingerprintOnlySid =
                     (fingerprintManager != null) ? fingerprintManager.getAuthenticatorId() : 0;
-            if (fingerprintOnlySid == 0) {
+            final long faceOnlySid =
+                    (faceManager != null) ? faceManager.getAuthenticatorId() : 0;
+
+            if (fingerprintOnlySid == 0 && faceOnlySid == 0) {
                 throw new IllegalStateException(
-                        "At least one fingerprint must be enrolled to create keys requiring user"
+                        "At least one biometric must be enrolled to create keys requiring user"
                         + " authentication for every use");
             }
 
-            long sid;
+            List<Long> sids = new ArrayList<>();
             if (spec.getBoundToSpecificSecureUserId() != GateKeeper.INVALID_SECURE_USER_ID) {
-                sid = spec.getBoundToSpecificSecureUserId();
+                sids.add(spec.getBoundToSpecificSecureUserId());
             } else if (spec.isInvalidatedByBiometricEnrollment()) {
-                // The fingerprint-only SID will change on fingerprint enrollment or removal of all,
-                // enrolled fingerprints, invalidating the key.
-                sid = fingerprintOnlySid;
+                // The biometric-only SIDs will change on biometric enrollment or removal of all
+                // enrolled templates, invalidating the key.
+                sids.add(fingerprintOnlySid);
+                sids.add(faceOnlySid);
             } else {
                 // The root SID will *not* change on fingerprint enrollment, or removal of all
                 // enrolled fingerprints, allowing the key to remain valid.
-                sid = getRootSid();
+                sids.add(getRootSid());
             }
 
-            args.addUnsignedLong(
-                    KeymasterDefs.KM_TAG_USER_SECURE_ID, KeymasterArguments.toUint64(sid));
-            args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, KeymasterDefs.HW_AUTH_FINGERPRINT);
+            for (int i = 0; i < sids.size(); i++) {
+                args.addUnsignedLong(KeymasterDefs.KM_TAG_USER_SECURE_ID,
+                        KeymasterArguments.toUint64(sids.get(i)));
+            }
+            args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, KeymasterDefs.HW_AUTH_BIOMETRIC);
+
             if (spec.isUserAuthenticationValidWhileOnBody()) {
                 throw new ProviderException("Key validity extension while device is on-body is not "
                         + "supported for keys requiring fingerprint authentication");
@@ -166,7 +177,7 @@
             args.addUnsignedLong(KeymasterDefs.KM_TAG_USER_SECURE_ID,
                     KeymasterArguments.toUint64(sid));
             args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE,
-                    KeymasterDefs.HW_AUTH_PASSWORD | KeymasterDefs.HW_AUTH_FINGERPRINT);
+                    KeymasterDefs.HW_AUTH_PASSWORD | KeymasterDefs.HW_AUTH_BIOMETRIC);
             args.addUnsignedInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT,
                     spec.getUserAuthenticationValidityDurationSeconds());
             if (spec.isUserAuthenticationValidWhileOnBody()) {
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index d401b38..6ae5999 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -164,7 +164,11 @@
 
     if (surface) {
         mRenderThread.requireGlContext();
-        mEglSurface = mEglManager.createSurface(surface, colorMode);
+        auto newSurface = mEglManager.createSurface(surface, colorMode);
+        if (!newSurface) {
+            return false;
+        }
+        mEglSurface = newSurface.unwrap();
     }
 
     if (colorMode == ColorMode::SRGB) {
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index d4ffddd..65ced6a 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -261,7 +261,7 @@
     }
 }
 
-EGLSurface EglManager::createSurface(EGLNativeWindowType window, ColorMode colorMode) {
+Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window, ColorMode colorMode) {
     LOG_ALWAYS_FATAL_IF(!hasEglContext(), "Not initialized");
 
     bool wideColorGamut = colorMode == ColorMode::WideColorGamut && EglExtensions.glColorSpace &&
@@ -311,9 +311,9 @@
 
     EGLSurface surface = eglCreateWindowSurface(
             mEglDisplay, wideColorGamut ? mEglConfigWideGamut : mEglConfig, window, attribs);
-    LOG_ALWAYS_FATAL_IF(surface == EGL_NO_SURFACE,
-                        "Failed to create EGLSurface for window %p, eglErr = %s", (void*)window,
-                        eglErrorString());
+    if (surface == EGL_NO_SURFACE) {
+        return Error<EGLint> { eglGetError() };
+    }
 
     if (mSwapBehavior != SwapBehavior::Preserved) {
         LOG_ALWAYS_FATAL_IF(eglSurfaceAttrib(mEglDisplay, surface, EGL_SWAP_BEHAVIOR,
diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h
index 55c81d4..2a44f7e 100644
--- a/libs/hwui/renderthread/EglManager.h
+++ b/libs/hwui/renderthread/EglManager.h
@@ -25,6 +25,7 @@
 #include <ui/GraphicBuffer.h>
 #include <utils/StrongPointer.h>
 #include "IRenderPipeline.h"
+#include "utils/Result.h"
 
 namespace android {
 namespace uirenderer {
@@ -47,7 +48,7 @@
 
     bool hasEglContext();
 
-    EGLSurface createSurface(EGLNativeWindowType window, ColorMode colorMode);
+    Result<EGLSurface, EGLint> createSurface(EGLNativeWindowType window, ColorMode colorMode);
     void destroySurface(EGLSurface surface);
 
     void destroy();
diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp
index 0795208..a6073eb 100644
--- a/libs/hwui/tests/unit/RenderNodeTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeTests.cpp
@@ -321,7 +321,7 @@
     // Check that the VD is in the dislay list, and the layer update queue contains the correct
     // damage rect.
     EXPECT_TRUE(rootNode->getDisplayList()->hasVectorDrawables());
-    EXPECT_FALSE(info.layerUpdateQueue->entries().empty());
+    ASSERT_FALSE(info.layerUpdateQueue->entries().empty());
     EXPECT_EQ(rootNode.get(), info.layerUpdateQueue->entries().at(0).renderNode.get());
     EXPECT_EQ(uirenderer::Rect(0, 0, 200, 400), info.layerUpdateQueue->entries().at(0).damage);
     canvasContext->destroy();
diff --git a/libs/hwui/utils/Result.h b/libs/hwui/utils/Result.h
new file mode 100644
index 0000000..7f33f2e
--- /dev/null
+++ b/libs/hwui/utils/Result.h
@@ -0,0 +1,54 @@
+/*
+ * 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 <variant>
+#include <log/log.h>
+
+namespace android::uirenderer {
+
+template <typename E>
+struct Error {
+    E error;
+};
+
+template <typename R, typename E>
+class Result {
+public:
+    Result(const R& r) : result(std::forward<R>(r)) {}
+    Result(R&& r) : result(std::forward<R>(r)) {}
+    Result(Error<E>&& error) : result(std::forward<Error<E>>(error)) {}
+
+    operator bool() const {
+        return result.index() == 0;
+    }
+
+    R unwrap() const {
+        LOG_ALWAYS_FATAL_IF(result.index() == 1, "unwrap called on error value!");
+        return std::get<R>(result);
+    }
+
+    E error() const {
+        LOG_ALWAYS_FATAL_IF(result.index() == 0, "No error to get from Result");
+        return std::get<Error<E>>(result).error;
+    }
+
+private:
+    std::variant<R, Error<E>> result;
+};
+
+}; // namespace android::uirenderer
diff --git a/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java b/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
index 8a02a82..057a4ae 100644
--- a/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
+++ b/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
@@ -20,9 +20,12 @@
 import android.os.connectivity.GpsBatteryStats;
 import android.os.SystemProperties;
 
+import android.server.location.ServerLocationProtoEnums;
+
 import android.text.format.DateUtils;
 import android.util.Base64;
 import android.util.Log;
+import android.util.StatsLog;
 import android.util.TimeUtils;
 
 import java.util.Arrays;
@@ -39,11 +42,17 @@
 
   private static final String TAG = GnssMetrics.class.getSimpleName();
 
+  /* Constant which indicates GPS signal quality is as yet unknown */
+  public static final int GPS_SIGNAL_QUALITY_UNKNOWN =
+          ServerLocationProtoEnums.GPS_SIGNAL_QUALITY_UNKNOWN; // -1
+
   /* Constant which indicates GPS signal quality is poor */
-  public static final int GPS_SIGNAL_QUALITY_POOR = 0;
+  public static final int GPS_SIGNAL_QUALITY_POOR =
+      ServerLocationProtoEnums.GPS_SIGNAL_QUALITY_POOR; // 0
 
   /* Constant which indicates GPS signal quality is good */
-  public static final int GPS_SIGNAL_QUALITY_GOOD = 1;
+  public static final int GPS_SIGNAL_QUALITY_GOOD =
+      ServerLocationProtoEnums.GPS_SIGNAL_QUALITY_GOOD; // 1
 
   /* Number of GPS signal quality levels */
   public static final int NUM_GPS_SIGNAL_QUALITY_LEVELS = GPS_SIGNAL_QUALITY_GOOD + 1;
@@ -329,11 +338,15 @@
     /* Last reported Top Four Average CN0 */
     private double mLastAverageCn0;
 
+    /* Last reported signal quality bin (based on Top Four Average CN0) */
+    private int mLastSignalLevel;
+
     public GnssPowerMetrics(IBatteryStats stats) {
       mBatteryStats = stats;
       // Used to initialize the variable to a very small value (unachievable in practice) so that
       // the first CNO report will trigger an update to BatteryStats
       mLastAverageCn0 = -100.0;
+      mLastSignalLevel = GPS_SIGNAL_QUALITY_UNKNOWN;
     }
 
     /**
@@ -384,8 +397,13 @@
       if (Math.abs(avgCn0 - mLastAverageCn0) < REPORTING_THRESHOLD_DB_HZ) {
         return;
       }
+      int signalLevel = getSignalLevel(avgCn0);
+      if (signalLevel != mLastSignalLevel) {
+        StatsLog.write(StatsLog.GPS_SIGNAL_QUALITY_CHANGED, signalLevel);
+        mLastSignalLevel = signalLevel;
+      }
       try {
-        mBatteryStats.noteGpsSignalQuality(getSignalLevel(avgCn0));
+        mBatteryStats.noteGpsSignalQuality(signalLevel);
         mLastAverageCn0 = avgCn0;
       } catch (Exception e) {
         Log.w(TAG, "Exception", e);
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 3ec595d..d37f8ab 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -16,23 +16,13 @@
 
 package android.media;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
-import java.lang.Math;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.NioUtils;
-import java.util.LinkedList;
-import java.util.concurrent.Executor;
-
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
-import android.os.Build;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
@@ -43,6 +33,15 @@
 
 import com.android.internal.annotations.GuardedBy;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.NioUtils;
+import java.util.LinkedList;
+import java.util.concurrent.Executor;
+
 /**
  * The AudioTrack class manages and plays a single audio resource for Java applications.
  * It allows streaming of PCM audio buffers to the audio sink for playback. This is
@@ -372,6 +371,10 @@
      */
     private int mAudioFormat;   // initialized by all constructors via audioParamCheck()
     /**
+     * The AudioAttributes used in configuration.
+     */
+    private AudioAttributes mConfiguredAudioAttributes;
+    /**
      * Audio session ID
      */
     private int mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE;
@@ -571,6 +574,8 @@
         super(attributes, AudioPlaybackConfiguration.PLAYER_TYPE_JAM_AUDIOTRACK);
         // mState already == STATE_UNINITIALIZED
 
+        mConfiguredAudioAttributes = attributes; // object copy not needed, immutable.
+
         if (format == null) {
             throw new IllegalArgumentException("Illegal null AudioFormat");
         }
@@ -1302,6 +1307,23 @@
     }
 
     /**
+     * Returns the {@link AudioAttributes} used in configuration.
+     * If a {@code streamType} is used instead of an {@code AudioAttributes}
+     * to configure the AudioTrack
+     * (the use of {@code streamType} for configuration is deprecated),
+     * then the {@code AudioAttributes}
+     * equivalent to the {@code streamType} is returned.
+     * @return The {@code AudioAttributes} used to configure the AudioTrack.
+     * @throws IllegalStateException If the track is not initialized.
+     */
+    public @NonNull AudioAttributes getAudioAttributes() {
+        if (mState == STATE_UNINITIALIZED || mConfiguredAudioAttributes == null) {
+            throw new IllegalStateException("track not initialized");
+        }
+        return mConfiguredAudioAttributes;
+    }
+
+    /**
      * Returns the configured audio data encoding. See {@link AudioFormat#ENCODING_PCM_8BIT},
      * {@link AudioFormat#ENCODING_PCM_16BIT}, and {@link AudioFormat#ENCODING_PCM_FLOAT}.
      */
diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java
index 4919eeb..c203fa9 100644
--- a/media/java/android/media/MediaExtractor.java
+++ b/media/java/android/media/MediaExtractor.java
@@ -272,10 +272,12 @@
     public static final class CasInfo {
         private final int mSystemId;
         private final MediaCas.Session mSession;
+        private final byte[] mPrivateData;
 
-        CasInfo(int systemId, @Nullable MediaCas.Session session) {
+        CasInfo(int systemId, @Nullable MediaCas.Session session, @Nullable byte[] privateData) {
             mSystemId = systemId;
             mSession = session;
+            mPrivateData = privateData;
         }
 
         /**
@@ -288,10 +290,30 @@
         }
 
         /**
+         * Retrieves the private data in the CA_Descriptor associated with a track.
+         * Some CAS systems may need this to initialize the CAS plugin object. This
+         * private data can only be retrieved before a valid {@link MediaCas} object
+         * is set on the extractor.
+         * <p>
+         * @see MediaExtractor#setMediaCas
+         * <p>
+         * @return a byte array containing the private data. A null return value
+         *         indicates that the private data is unavailable. An empty array,
+         *         on the other hand, indicates that the private data is empty
+         *         (zero in length).
+         */
+        @Nullable
+        public byte[] getPrivateData() {
+            return mPrivateData;
+        }
+
+        /**
          * Retrieves the {@link MediaCas.Session} associated with a track. The
          * session is needed to initialize a descrambler in order to decode the
-         * scrambled track.
+         * scrambled track. The session object can only be retrieved after a valid
+         * {@link MediaCas} object is set on the extractor.
          * <p>
+         * @see MediaExtractor#setMediaCas
          * @see MediaDescrambler#setMediaCasSession
          * <p>
          * @return a {@link MediaCas.Session} object associated with a track.
@@ -321,6 +343,13 @@
         if (formatMap.containsKey(MediaFormat.KEY_CA_SYSTEM_ID)) {
             int systemId = ((Integer)formatMap.get(MediaFormat.KEY_CA_SYSTEM_ID)).intValue();
             MediaCas.Session session = null;
+            byte[] privateData = null;
+            if (formatMap.containsKey(MediaFormat.KEY_CA_PRIVATE_DATA)) {
+                ByteBuffer buf = (ByteBuffer) formatMap.get(MediaFormat.KEY_CA_PRIVATE_DATA);
+                buf.rewind();
+                privateData = new byte[buf.remaining()];
+                buf.get(privateData);
+            }
             if (mMediaCas != null && formatMap.containsKey(MediaFormat.KEY_CA_SESSION_ID)) {
                 ByteBuffer buf = (ByteBuffer) formatMap.get(MediaFormat.KEY_CA_SESSION_ID);
                 buf.rewind();
@@ -328,7 +357,7 @@
                 buf.get(sessionId);
                 session = mMediaCas.createFromSessionId(toByteArray(sessionId));
             }
-            return new CasInfo(systemId, session);
+            return new CasInfo(systemId, session, privateData);
         }
         return null;
     }
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index d10cbbc..5dee16e 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -919,7 +919,7 @@
      * a media track.
      * <p>
      * This key is set by {@link MediaExtractor} if the track is scrambled with a conditional
-     * access system.
+     * access system, regardless of the presence of a valid {@link MediaCas} object.
      * <p>
      * The associated value is an integer.
      * @hide
@@ -930,13 +930,25 @@
      * A key describing the {@link MediaCas.Session} object associated with a media track.
      * <p>
      * This key is set by {@link MediaExtractor} if the track is scrambled with a conditional
-     * access system.
+     * access system, after it receives a valid {@link MediaCas} object.
      * <p>
      * The associated value is a ByteBuffer.
      * @hide
      */
     public static final String KEY_CA_SESSION_ID = "ca-session-id";
 
+
+    /**
+     * A key describing the private data in the CA_descriptor associated with a media track.
+     * <p>
+     * This key is set by {@link MediaExtractor} if the track is scrambled with a conditional
+     * access system, before it receives a valid {@link MediaCas} object.
+     * <p>
+     * The associated value is a ByteBuffer.
+     * @hide
+     */
+    public static final String KEY_CA_PRIVATE_DATA = "ca-private-data";
+
     /* package private */ MediaFormat(Map<String, Object> map) {
         mMap = map;
     }
diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java
index e8b2f65..a80511a 100644
--- a/media/java/android/media/MediaPlayer2.java
+++ b/media/java/android/media/MediaPlayer2.java
@@ -21,22 +21,59 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
+import android.content.ContentResolver;
 import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.graphics.Rect;
 import android.graphics.SurfaceTexture;
+import android.media.MediaPlayer2Proto.PlayerMessage;
+import android.media.MediaPlayer2Proto.Value;
+import android.net.Uri;
 import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
 import android.os.PersistableBundle;
+import android.os.PowerManager;
+import android.util.Log;
+import android.util.Pair;
 import android.view.Surface;
 import android.view.SurfaceHolder;
 
+import com.android.framework.protobuf.InvalidProtocolBufferException;
+import com.android.internal.annotations.GuardedBy;
+
 import dalvik.system.CloseGuard;
 
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.net.HttpCookie;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Queue;
 import java.util.UUID;
+import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.Executor;
-
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
 
 /**
  * @hide
@@ -225,26 +262,108 @@
  * successful transition. Any other value will be an error. Call {@link #getState()} to
  * determine the current state. </p>
  */
-public abstract class MediaPlayer2 implements AutoCloseable
+public class MediaPlayer2 implements AutoCloseable
                                             , AudioRouting {
+    static {
+        System.loadLibrary("media2_jni");
+        native_init();
+    }
+
+    private static native void native_init();
+
+    private static final int NEXT_SOURCE_STATE_ERROR = -1;
+    private static final int NEXT_SOURCE_STATE_INIT = 0;
+    private static final int NEXT_SOURCE_STATE_PREPARING = 1;
+    private static final int NEXT_SOURCE_STATE_PREPARED = 2;
+
+    private static final String TAG = "MediaPlayer2";
+
+    private Context mContext;
+
+    private long mNativeContext;  // accessed by native methods
+    private long mNativeSurfaceTexture;  // accessed by native methods
+    private int mListenerContext;  // accessed by native methods
+    private SurfaceHolder mSurfaceHolder;
+    private PowerManager.WakeLock mWakeLock = null;
+    private boolean mScreenOnWhilePlaying;
+    private boolean mStayAwake;
+
+    private final Object mSrcLock = new Object();
+    //--- guarded by |mSrcLock| start
+    private SourceInfo mCurrentSourceInfo;
+    private final Queue<SourceInfo> mNextSourceInfos = new ConcurrentLinkedQueue<>();
+    //--- guarded by |mSrcLock| end
+    private final AtomicLong mSrcIdGenerator = new AtomicLong(0);
+
+    private volatile float mVolume = 1.0f;
+    private VideoSize mVideoSize = new VideoSize(0, 0);
+
+    // TODO: create per-source drm fields in SourceInfo
+    // Modular DRM
+    private final Object mDrmLock = new Object();
+    //--- guarded by |mDrmLock| start
+    private UUID mDrmUUID;
+    private DrmInfo mDrmInfo;
+    private MediaDrm mDrmObj;
+    private byte[] mDrmSessionId;
+    private boolean mDrmInfoResolved;
+    private boolean mActiveDrmScheme;
+    private boolean mDrmConfigAllowed;
+    private boolean mDrmProvisioningInProgress;
+    private boolean mPrepareDrmInProgress;
+    private ProvisioningThread mDrmProvisioningThread;
+    //--- guarded by |mDrmLock| end
+
+    // Creating a dummy audio track, used for keeping session id alive
+    private final Object mSessionIdLock = new Object();
+    @GuardedBy("mSessionIdLock")
+    private AudioTrack mDummyAudioTrack;
+
+    private HandlerThread mHandlerThread;
+    private final TaskHandler mTaskHandler;
+    private final Object mTaskLock = new Object();
+    @GuardedBy("mTaskLock")
+    private final List<Task> mPendingTasks = new LinkedList<>();
+    @GuardedBy("mTaskLock")
+    private Task mCurrentTask;
+
+    @GuardedBy("mTaskLock")
+    boolean mIsPreviousCommandSeekTo = false;
+    // |mPreviousSeekPos| and |mPreviousSeekMode| are valid only when |mIsPreviousCommandSeekTo|
+    // is true, and they are accessed on |mHandlerThread| only.
+    long mPreviousSeekPos = -1;
+    int mPreviousSeekMode = SEEK_PREVIOUS_SYNC;
+
+    @GuardedBy("this")
+    private boolean mReleased;
+
     private final CloseGuard mGuard = CloseGuard.get();
 
     /**
-     * Create a MediaPlayer2 object.
-     *
-     * @return A MediaPlayer2 object created
+     * Default constructor.
+     * <p>When done with the MediaPlayer2, you should call {@link #close()},
+     * to free the resources. If not released, too many MediaPlayer2 instances may
+     * result in an exception.</p>
      */
-    public static final MediaPlayer2 create(Context context) {
-        return new MediaPlayer2Impl(context);
+    public MediaPlayer2(Context context) {
+        mGuard.open("close");
+
+        mContext = context;
+        mHandlerThread = new HandlerThread("MediaPlayer2TaskThread");
+        mHandlerThread.start();
+        Looper looper = mHandlerThread.getLooper();
+        mTaskHandler = new TaskHandler(this, looper);
+        AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        int sessionId = am.generateAudioSessionId();
+        keepAudioSessionIdAlive(sessionId);
+
+        /* Native setup requires a weak reference to our object.
+         * It's easier to create it here than in C++.
+         */
+        native_setup(sessionId, new WeakReference<MediaPlayer2>(this));
     }
 
-    /**
-     * @hide
-     */
-    // add hidden empty constructor so it doesn't show in SDK
-    public MediaPlayer2() {
-        mGuard.open("close");
-    }
+    private native void native_setup(int sessionId, Object mediaplayer2This);
 
     /**
      * Releases the resources held by this {@code MediaPlayer2} object.
@@ -275,8 +394,41 @@
         synchronized (mGuard) {
             mGuard.close();
         }
+        release();
     }
 
+    private synchronized void release() {
+        if (mReleased) {
+            return;
+        }
+        stayAwake(false);
+        updateSurfaceScreenOn();
+        synchronized (mEventCbLock) {
+            mEventCallbackRecords.clear();
+        }
+        if (mHandlerThread != null) {
+            mHandlerThread.quitSafely();
+            mHandlerThread = null;
+        }
+
+        // Modular DRM clean up
+        mOnDrmConfigHelper = null;
+        synchronized (mDrmEventCbLock) {
+            mDrmEventCallbackRecords.clear();
+        }
+        resetDrmState();
+
+        native_release();
+
+        synchronized (mSessionIdLock) {
+            mDummyAudioTrack.release();
+        }
+
+        mReleased = true;
+    }
+
+    private native void native_release();
+
     // Have to declare protected for finalize() since it is protected
     // in the base class Object.
     @Override
@@ -286,8 +438,51 @@
         }
 
         close();
+        native_finalize();
     }
 
+    private native void native_finalize();
+
+    /**
+     * Resets the MediaPlayer2 to its uninitialized state. After calling
+     * this method, you will have to initialize it again by setting the
+     * data source and calling prepare().
+     */
+    // This is a synchronous call.
+    public void reset() {
+        synchronized (mEventCbLock) {
+            mEventCallbackRecords.clear();
+        }
+        synchronized (mDrmEventCbLock) {
+            mDrmEventCallbackRecords.clear();
+        }
+        synchronized (mSrcLock) {
+            mCurrentSourceInfo = null;
+            mNextSourceInfos.clear();
+        }
+
+        synchronized (mTaskLock) {
+            mPendingTasks.clear();
+            mIsPreviousCommandSeekTo = false;
+        }
+
+        stayAwake(false);
+        native_reset();
+
+        AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        int sessionId = am.generateAudioSessionId();
+        keepAudioSessionIdAlive(sessionId);
+
+        // make sure none of the listeners get called anymore
+        if (mTaskHandler != null) {
+            mTaskHandler.removeCallbacksAndMessages(null);
+        }
+
+        resetDrmState();
+    }
+
+    private native void native_reset();
+
     /**
      * Starts or resumes playback. If playback had previously been paused,
      * playback will continue from where it was paused. If playback had
@@ -297,39 +492,78 @@
      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
      */
     // This is an asynchronous call.
-    public abstract Object play();
+    public Object play() {
+        return addTask(new Task(CALL_COMPLETED_PLAY, false) {
+            @Override
+            void process() {
+                stayAwake(true);
+                native_start();
+            }
+        });
+    }
+
+    private native void native_start() throws IllegalStateException;
 
     /**
      * Prepares the player for playback, asynchronously.
      *
-     * After setting the datasource and the display surface, you need to
-     * call prepare().
+     * After setting the datasource and the display surface, you need to call prepare().
      *
      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
      */
     // This is an asynchronous call.
-    public abstract Object prepare();
+    public Object prepare() {
+        return addTask(new Task(CALL_COMPLETED_PREPARE, true) {
+            @Override
+            void process() {
+                native_prepare();
+            }
+        });
+    }
+
+    private native void native_prepare();
 
     /**
      * Pauses playback. Call play() to resume.
      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
      */
     // This is an asynchronous call.
-    public abstract Object pause();
+    public Object pause() {
+        return addTask(new Task(CALL_COMPLETED_PAUSE, false) {
+            @Override
+            void process() {
+                stayAwake(false);
+
+                native_pause();
+            }
+        });
+    }
+
+    private native void native_pause() throws IllegalStateException;
 
     /**
      * Tries to play next data source if applicable.
      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
      */
     // This is an asynchronous call.
-    public abstract Object skipToNext();
+    public Object skipToNext() {
+        return addTask(new Task(CALL_COMPLETED_SKIP_TO_NEXT, false) {
+            @Override
+            void process() {
+                if (getState() == PLAYER_STATE_PLAYING) {
+                    pause();
+                }
+                playNextDataSource();
+            }
+        });
+    }
 
     /**
      * Gets the current playback position.
      *
      * @return the current position in milliseconds
      */
-    public abstract long getCurrentPosition();
+    public native long getCurrentPosition();
 
     /**
      * Gets the duration of the file.
@@ -337,7 +571,7 @@
      * @return the duration in milliseconds, if no duration is available
      *         (for example, if streaming live content), -1 is returned.
      */
-    public abstract long getDuration();
+    public native long getDuration();
 
     /**
      * Gets the current buffered media source position received through progressive downloading.
@@ -347,7 +581,18 @@
      *
      * @return the current buffered media source position in milliseconds
      */
-    public abstract long getBufferedPosition();
+    public long getBufferedPosition() {
+        // Use cached buffered percent for now.
+        int bufferedPercentage;
+        synchronized (mSrcLock) {
+            if (mCurrentSourceInfo == null) {
+                bufferedPercentage = 0;
+            } else {
+                bufferedPercentage = mCurrentSourceInfo.mBufferedPercentage.get();
+            }
+        }
+        return getDuration() * bufferedPercentage / 100;
+    }
 
     /**
      * MediaPlayer2 has not been prepared or just has been reset.
@@ -396,7 +641,11 @@
      *
      * @return the current player state.
      */
-    public abstract @MediaPlayer2State int getState();
+    public @MediaPlayer2State int getState() {
+        return native_getState();
+    }
+
+    private native int native_getState();
 
     /**
      * Sets the audio attributes for this MediaPlayer2.
@@ -407,13 +656,31 @@
      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
      */
     // This is an asynchronous call.
-    public abstract Object setAudioAttributes(@NonNull AudioAttributes attributes);
+    public Object setAudioAttributes(@NonNull AudioAttributes attributes) {
+        return addTask(new Task(CALL_COMPLETED_SET_AUDIO_ATTRIBUTES, false) {
+            @Override
+            void process() {
+                if (attributes == null) {
+                    final String msg = "Cannot set AudioAttributes to null";
+                    throw new IllegalArgumentException(msg);
+                }
+                native_setAudioAttributes(attributes);
+            }
+        });
+    }
+
+    // return true if the parameter is set successfully, false otherwise
+    private native boolean native_setAudioAttributes(AudioAttributes audioAttributes);
 
     /**
      * Gets the audio attributes for this MediaPlayer2.
      * @return attributes a set of audio attributes
      */
-    public abstract @Nullable AudioAttributes getAudioAttributes();
+    public @NonNull AudioAttributes getAudioAttributes() {
+        return native_getAudioAttributes();
+    }
+
+    private native AudioAttributes native_getAudioAttributes();
 
     /**
      * Sets the data source as described by a DataSourceDesc.
@@ -422,7 +689,23 @@
      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
      */
     // This is an asynchronous call.
-    public abstract Object setDataSource(@NonNull DataSourceDesc dsd);
+    public Object setDataSource(@NonNull DataSourceDesc dsd) {
+        return addTask(new Task(CALL_COMPLETED_SET_DATA_SOURCE, false) {
+            @Override
+            void process() throws IOException {
+                checkArgument(dsd != null, "the DataSourceDesc cannot be null");
+                int state = getState();
+                if (state != PLAYER_STATE_ERROR && state != PLAYER_STATE_IDLE) {
+                    throw new IllegalStateException("called in wrong state " + state);
+                }
+
+                synchronized (mSrcLock) {
+                    mCurrentSourceInfo = new SourceInfo(dsd);
+                    handleDataSource(true /* isCurrent */, dsd, mCurrentSourceInfo.mId);
+                }
+            }
+        });
+    }
 
     /**
      * Sets a single data source as described by a DataSourceDesc which will be played
@@ -432,7 +715,19 @@
      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
      */
     // This is an asynchronous call.
-    public abstract Object setNextDataSource(@NonNull DataSourceDesc dsd);
+    public Object setNextDataSource(@NonNull DataSourceDesc dsd) {
+        return addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCE, false) {
+            @Override
+            void process() {
+                checkArgument(dsd != null, "the DataSourceDesc cannot be null");
+                synchronized (mSrcLock) {
+                    mNextSourceInfos.clear();
+                    mNextSourceInfos.add(new SourceInfo(dsd));
+                }
+                prepareNextDataSource();
+            }
+        });
+    }
 
     /**
      * Sets a list of data sources to be played sequentially after current data source is done.
@@ -441,21 +736,367 @@
      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
      */
     // This is an asynchronous call.
-    public abstract Object setNextDataSources(@NonNull List<DataSourceDesc> dsds);
+    public Object setNextDataSources(@NonNull List<DataSourceDesc> dsds) {
+        return addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCES, false) {
+            @Override
+            void process() {
+                if (dsds == null || dsds.size() == 0) {
+                    throw new IllegalArgumentException("data source list cannot be null or empty.");
+                }
+                for (DataSourceDesc dsd : dsds) {
+                    if (dsd == null) {
+                        throw new IllegalArgumentException(
+                                "DataSourceDesc in the source list cannot be null.");
+                    }
+                }
+
+                synchronized (mSrcLock) {
+                    mNextSourceInfos.clear();
+                    for (DataSourceDesc dsd : dsds) {
+                        mNextSourceInfos.add(new SourceInfo(dsd));
+                    }
+                }
+                prepareNextDataSource();
+            }
+        });
+    }
 
     /**
      * Removes all data sources pending to be played.
      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
      */
     // This is an asynchronous call.
-    public abstract Object clearNextDataSources();
+    public Object clearNextDataSources() {
+        return addTask(new Task(CALL_COMPLETED_CLEAR_NEXT_DATA_SOURCES, false) {
+            @Override
+            void process() {
+                mNextSourceInfos.clear();
+            }
+        });
+    }
 
     /**
      * Gets the current data source as described by a DataSourceDesc.
      *
      * @return the current DataSourceDesc
      */
-    public abstract @NonNull DataSourceDesc getCurrentDataSource();
+    public DataSourceDesc getCurrentDataSource() {
+        synchronized (mSrcLock) {
+            return mCurrentSourceInfo == null ? null : mCurrentSourceInfo.mDSD;
+        }
+    }
+
+    private void handleDataSource(boolean isCurrent, @NonNull DataSourceDesc dsd, long srcId)
+            throws IOException {
+        checkArgument(dsd != null, "the DataSourceDesc cannot be null");
+
+        switch (dsd.getType()) {
+            case DataSourceDesc.TYPE_CALLBACK:
+                handleDataSource(isCurrent,
+                                 srcId,
+                                 dsd.getMedia2DataSource(),
+                                 dsd.getStartPosition(),
+                                 dsd.getEndPosition());
+                break;
+
+            case DataSourceDesc.TYPE_FD:
+                handleDataSource(isCurrent,
+                                 srcId,
+                                 dsd.getFileDescriptor(),
+                                 dsd.getFileDescriptorOffset(),
+                                 dsd.getFileDescriptorLength(),
+                                 dsd.getStartPosition(),
+                                 dsd.getEndPosition());
+                break;
+
+            case DataSourceDesc.TYPE_URI:
+                handleDataSource(isCurrent,
+                                 srcId,
+                                 dsd.getUriContext(),
+                                 dsd.getUri(),
+                                 dsd.getUriHeaders(),
+                                 dsd.getUriCookies(),
+                                 dsd.getStartPosition(),
+                                 dsd.getEndPosition());
+                break;
+
+            default:
+                break;
+        }
+    }
+
+    /**
+     * To provide cookies for the subsequent HTTP requests, you can install your own default cookie
+     * handler and use other variants of setDataSource APIs instead. Alternatively, you can use
+     * this API to pass the cookies as a list of HttpCookie. If the app has not installed
+     * a CookieHandler already, this API creates a CookieManager and populates its CookieStore with
+     * the provided cookies. If the app has installed its own handler already, this API requires the
+     * handler to be of CookieManager type such that the API can update the manager’s CookieStore.
+     *
+     * <p><strong>Note</strong> that the cross domain redirection is allowed by default,
+     * but that can be changed with key/value pairs through the headers parameter with
+     * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to
+     * disallow or allow cross domain redirection.
+     *
+     * @throws IllegalArgumentException if cookies are provided and the installed handler is not
+     *                                  a CookieManager
+     * @throws IllegalStateException    if it is called in an invalid state
+     * @throws NullPointerException     if context or uri is null
+     * @throws IOException              if uri has a file scheme and an I/O error occurs
+     */
+    private void handleDataSource(
+            boolean isCurrent, long srcId,
+            @NonNull Context context, @NonNull Uri uri,
+            @Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies,
+            long startPos, long endPos)
+            throws IOException {
+        // The context and URI usually belong to the calling user. Get a resolver for that user.
+        final ContentResolver resolver = context.getContentResolver();
+        final String scheme = uri.getScheme();
+        if (ContentResolver.SCHEME_FILE.equals(scheme)) {
+            handleDataSource(isCurrent, srcId, uri.getPath(), null, null, startPos, endPos);
+            return;
+        }
+
+        final int ringToneType = RingtoneManager.getDefaultType(uri);
+        try {
+            AssetFileDescriptor afd;
+            // Try requested Uri locally first
+            if (ContentResolver.SCHEME_CONTENT.equals(scheme) && ringToneType != -1) {
+                afd = RingtoneManager.openDefaultRingtoneUri(context, uri);
+                if (attemptDataSource(isCurrent, srcId, afd, startPos, endPos)) {
+                    return;
+                }
+                final Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri(
+                        context, ringToneType);
+                afd = resolver.openAssetFileDescriptor(actualUri, "r");
+            } else {
+                afd = resolver.openAssetFileDescriptor(uri, "r");
+            }
+            if (attemptDataSource(isCurrent, srcId, afd, startPos, endPos)) {
+                return;
+            }
+        } catch (NullPointerException | SecurityException | IOException ex) {
+            Log.w(TAG, "Couldn't open " + uri + ": " + ex);
+            // Fallback to media server
+        }
+        handleDataSource(isCurrent, srcId, uri.toString(), headers, cookies, startPos, endPos);
+    }
+
+    private boolean attemptDataSource(boolean isCurrent, long srcId, AssetFileDescriptor afd,
+            long startPos, long endPos) throws IOException {
+        try {
+            if (afd.getDeclaredLength() < 0) {
+                handleDataSource(isCurrent,
+                        srcId,
+                        afd.getFileDescriptor(),
+                        0,
+                        DataSourceDesc.LONG_MAX,
+                        startPos,
+                        endPos);
+            } else {
+                handleDataSource(isCurrent,
+                        srcId,
+                        afd.getFileDescriptor(),
+                        afd.getStartOffset(),
+                        afd.getDeclaredLength(),
+                        startPos,
+                        endPos);
+            }
+            return true;
+        } catch (NullPointerException | SecurityException | IOException ex) {
+            Log.w(TAG, "Couldn't open srcId:" + srcId + ": " + ex);
+            return false;
+        } finally {
+            if (afd != null) {
+                afd.close();
+            }
+        }
+    }
+
+    private void handleDataSource(
+            boolean isCurrent, long srcId,
+            String path, Map<String, String> headers, List<HttpCookie> cookies,
+            long startPos, long endPos)
+            throws IOException {
+        String[] keys = null;
+        String[] values = null;
+
+        if (headers != null) {
+            keys = new String[headers.size()];
+            values = new String[headers.size()];
+
+            int i = 0;
+            for (Map.Entry<String, String> entry: headers.entrySet()) {
+                keys[i] = entry.getKey();
+                values[i] = entry.getValue();
+                ++i;
+            }
+        }
+        handleDataSource(isCurrent, srcId, path, keys, values, cookies, startPos, endPos);
+    }
+
+    private void handleDataSource(boolean isCurrent, long srcId,
+            String path, String[] keys, String[] values, List<HttpCookie> cookies,
+            long startPos, long endPos)
+            throws IOException {
+        final Uri uri = Uri.parse(path);
+        final String scheme = uri.getScheme();
+        if ("file".equals(scheme)) {
+            path = uri.getPath();
+        } else if (scheme != null) {
+            // handle non-file sources
+            Media2Utils.storeCookies(cookies);
+            nativeHandleDataSourceUrl(
+                    isCurrent,
+                    srcId,
+                    Media2HTTPService.createHTTPService(path),
+                    path,
+                    keys,
+                    values,
+                    startPos,
+                    endPos);
+            return;
+        }
+
+        final File file = new File(path);
+        if (file.exists()) {
+            FileInputStream is = new FileInputStream(file);
+            FileDescriptor fd = is.getFD();
+            handleDataSource(isCurrent, srcId, fd, 0, DataSourceDesc.LONG_MAX, startPos, endPos);
+            is.close();
+        } else {
+            throw new IOException("handleDataSource failed.");
+        }
+    }
+
+    private native void nativeHandleDataSourceUrl(
+            boolean isCurrent, long srcId,
+            Media2HTTPService httpService, String path, String[] keys, String[] values,
+            long startPos, long endPos)
+            throws IOException;
+
+    /**
+     * Sets the data source (FileDescriptor) to use. The FileDescriptor must be
+     * seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility
+     * to close the file descriptor. It is safe to do so as soon as this call returns.
+     *
+     * @throws IllegalStateException if it is called in an invalid state
+     * @throws IllegalArgumentException if fd is not a valid FileDescriptor
+     * @throws IOException if fd can not be read
+     */
+    private void handleDataSource(
+            boolean isCurrent, long srcId,
+            FileDescriptor fd, long offset, long length,
+            long startPos, long endPos) throws IOException {
+        nativeHandleDataSourceFD(isCurrent, srcId, fd, offset, length, startPos, endPos);
+    }
+
+    private native void nativeHandleDataSourceFD(boolean isCurrent, long srcId,
+            FileDescriptor fd, long offset, long length,
+            long startPos, long endPos) throws IOException;
+
+    /**
+     * @throws IllegalStateException if it is called in an invalid state
+     * @throws IllegalArgumentException if dataSource is not a valid Media2DataSource
+     */
+    private void handleDataSource(boolean isCurrent, long srcId, Media2DataSource dataSource,
+            long startPos, long endPos) {
+        nativeHandleDataSourceCallback(isCurrent, srcId, dataSource, startPos, endPos);
+    }
+
+    private native void nativeHandleDataSourceCallback(
+            boolean isCurrent, long srcId, Media2DataSource dataSource,
+            long startPos, long endPos);
+
+    // return true if there is a next data source, false otherwise.
+    // This function should be always called on |mHandlerThread|.
+    private boolean prepareNextDataSource() {
+        HandlerThread handlerThread = mHandlerThread;
+        if (handlerThread != null && Looper.myLooper() != handlerThread.getLooper()) {
+            Log.e(TAG, "prepareNextDataSource: called on wrong looper");
+        }
+
+        boolean hasNextDSD;
+        int state = getState();
+        synchronized (mSrcLock) {
+            hasNextDSD = !mNextSourceInfos.isEmpty();
+            if (state == PLAYER_STATE_ERROR || state == PLAYER_STATE_IDLE) {
+                // Current source has not been prepared yet.
+                return hasNextDSD;
+            }
+
+            SourceInfo nextSource = mNextSourceInfos.peek();
+            if (!hasNextDSD || nextSource.mStateAsNextSource != NEXT_SOURCE_STATE_INIT) {
+                // There is no next source or it's in preparing or prepared state.
+                return hasNextDSD;
+            }
+
+            try {
+                nextSource.mStateAsNextSource = NEXT_SOURCE_STATE_PREPARING;
+                handleDataSource(false /* isCurrent */, nextSource.mDSD, nextSource.mId);
+            } catch (Exception e) {
+                Message msg = mTaskHandler.obtainMessage(
+                        MEDIA_ERROR, MEDIA_ERROR_IO, MEDIA_ERROR_UNKNOWN, null);
+                mTaskHandler.handleMessage(msg, nextSource.mId);
+
+                mNextSourceInfos.poll();
+                return prepareNextDataSource();
+            }
+        }
+        return hasNextDSD;
+    }
+
+    // This function should be always called on |mHandlerThread|.
+    private void playNextDataSource() {
+        HandlerThread handlerThread = mHandlerThread;
+        if (handlerThread != null && Looper.myLooper() != handlerThread.getLooper()) {
+            Log.e(TAG, "playNextDataSource: called on wrong looper");
+        }
+
+        boolean hasNextDSD = false;
+        synchronized (mSrcLock) {
+            if (!mNextSourceInfos.isEmpty()) {
+                hasNextDSD = true;
+                SourceInfo nextSourceInfo = mNextSourceInfos.peek();
+                if (nextSourceInfo.mStateAsNextSource == NEXT_SOURCE_STATE_PREPARED) {
+                    // Switch to next source only when it has been prepared.
+                    mCurrentSourceInfo = mNextSourceInfos.poll();
+
+                    long srcId = mCurrentSourceInfo.mId;
+                    try {
+                        nativePlayNextDataSource(srcId);
+                    } catch (Exception e) {
+                        Message msg2 = mTaskHandler.obtainMessage(
+                                MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null);
+                        mTaskHandler.handleMessage(msg2, srcId);
+                        // Keep |mNextSourcePlayPending|
+                        hasNextDSD = prepareNextDataSource();
+                    }
+                    if (hasNextDSD) {
+                        stayAwake(true);
+
+                        // Now a new current src is playing.
+                        // Wait for MEDIA_INFO_DATA_SOURCE_START to prepare next source.
+                    }
+                } else if (nextSourceInfo.mStateAsNextSource == NEXT_SOURCE_STATE_INIT) {
+                    hasNextDSD = prepareNextDataSource();
+                }
+            }
+        }
+
+        if (!hasNextDSD) {
+            sendEvent(new EventNotifier() {
+                @Override
+                public void notify(EventCallback callback) {
+                    callback.onInfo(
+                            MediaPlayer2.this, null, MEDIA_INFO_DATA_SOURCE_LIST_END, 0);
+                }
+            });
+        }
+    }
+
+    private native void nativePlayNextDataSource(long srcId);
 
     /**
      * Configures the player to loop on the current data source.
@@ -463,7 +1104,16 @@
      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
      */
     // This is an asynchronous call.
-    public abstract Object loopCurrent(boolean loop);
+    public Object loopCurrent(boolean loop) {
+        return addTask(new Task(CALL_COMPLETED_LOOP_CURRENT, false) {
+            @Override
+            void process() {
+                setLooping(loop);
+            }
+        });
+    }
+
+    private native void setLooping(boolean looping);
 
     /**
      * Sets the volume of the audio of the media to play, expressed as a linear multiplier
@@ -476,14 +1126,26 @@
      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
      */
     // This is an asynchronous call.
-    public abstract Object setPlayerVolume(float volume);
+    public Object setPlayerVolume(float volume) {
+        return addTask(new Task(CALL_COMPLETED_SET_PLAYER_VOLUME, false) {
+            @Override
+            void process() {
+                mVolume = volume;
+                native_setVolume(volume);
+            }
+        });
+    }
+
+    private native void native_setVolume(float volume);
 
     /**
      * Returns the current volume of this player.
      * Note that it does not take into account the associated stream volume.
      * @return the player volume.
      */
-    public abstract float getPlayerVolume();
+    public float getPlayerVolume() {
+        return mVolume;
+    }
 
     /**
      * @return the maximum volume that can be used in {@link #setPlayerVolume(float)}.
@@ -505,7 +1167,20 @@
      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
      */
     // This is an asynchronous call.
-    public abstract Object notifyWhenCommandLabelReached(@NonNull Object label);
+    public Object notifyWhenCommandLabelReached(@NonNull Object label) {
+        return addTask(new Task(CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED, false) {
+            @Override
+            void process() {
+                sendEvent(new EventNotifier() {
+                    @Override
+                    public void notify(EventCallback callback) {
+                        callback.onCommandLabelReached(
+                                MediaPlayer2.this, label);
+                    }
+                });
+            }
+        });
+    }
 
     /**
      * Sets the {@link SurfaceHolder} to use for displaying the video
@@ -520,7 +1195,22 @@
      * @param sh the SurfaceHolder to use for video display
      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
      */
-    public abstract Object setDisplay(SurfaceHolder sh);
+    public Object setDisplay(SurfaceHolder sh) {
+        return addTask(new Task(CALL_COMPLETED_SET_DISPLAY, false) {
+            @Override
+            void process() {
+                mSurfaceHolder = sh;
+                Surface surface;
+                if (sh != null) {
+                    surface = sh.getSurface();
+                } else {
+                    surface = null;
+                }
+                native_setVideoSurface(surface);
+                updateSurfaceScreenOn();
+            }
+        });
+    }
 
     /**
      * Sets the {@link Surface} to be used as the sink for the video portion of
@@ -541,7 +1231,21 @@
      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
      */
     // This is an asynchronous call.
-    public abstract Object setSurface(Surface surface);
+    public Object setSurface(Surface surface) {
+        return addTask(new Task(CALL_COMPLETED_SET_SURFACE, false) {
+            @Override
+            void process() {
+                if (mScreenOnWhilePlaying && surface != null) {
+                    Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective for Surface");
+                }
+                mSurfaceHolder = null;
+                native_setVideoSurface(surface);
+                updateSurfaceScreenOn();
+            }
+        });
+    }
+
+    private native void native_setVideoSurface(Surface surface);
 
     /**
      * Set the low-level power management behavior for this MediaPlayer2. This
@@ -562,7 +1266,42 @@
      * @see android.os.PowerManager
      */
     // This is an asynchronous call.
-    public abstract Object setWakeMode(Context context, int mode);
+    public Object setWakeMode(Context context, int mode) {
+        return addTask(new Task(CALL_COMPLETED_SET_WAKE_MODE, false) {
+            @Override
+            void process() {
+                boolean washeld = false;
+
+                if (mWakeLock != null) {
+                    if (mWakeLock.isHeld()) {
+                        washeld = true;
+                        mWakeLock.release();
+                    }
+                    mWakeLock = null;
+                }
+
+                PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+                ActivityManager am =
+                        (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+                List<RunningAppProcessInfo> runningAppsProcInfo = am.getRunningAppProcesses();
+                int pid = android.os.Process.myPid();
+                String name = "pid " + String.valueOf(pid);
+                if (runningAppsProcInfo != null) {
+                    for (RunningAppProcessInfo procInfo : runningAppsProcInfo) {
+                        if (procInfo.pid == pid) {
+                            name = procInfo.processName;
+                            break;
+                        }
+                    }
+                }
+                mWakeLock = pm.newWakeLock(mode | PowerManager.ON_AFTER_RELEASE, name);
+                mWakeLock.setReferenceCounted(false);
+                if (washeld) {
+                    mWakeLock.acquire();
+                }
+            }
+        });
+    }
 
     /**
      * Control whether we should use the attached SurfaceHolder to keep the
@@ -575,7 +1314,39 @@
      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
      */
     // This is an asynchronous call.
-    public abstract Object setScreenOnWhilePlaying(boolean screenOn);
+    public Object setScreenOnWhilePlaying(boolean screenOn) {
+        return addTask(new Task(CALL_COMPLETED_SET_SCREEN_ON_WHILE_PLAYING, false) {
+            @Override
+            void process() {
+                if (mScreenOnWhilePlaying != screenOn) {
+                    if (screenOn && mSurfaceHolder == null) {
+                        Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective"
+                                + " without a SurfaceHolder");
+                    }
+                    mScreenOnWhilePlaying = screenOn;
+                    updateSurfaceScreenOn();
+                }
+            }
+        });
+    }
+
+    private void stayAwake(boolean awake) {
+        if (mWakeLock != null) {
+            if (awake && !mWakeLock.isHeld()) {
+                mWakeLock.acquire();
+            } else if (!awake && mWakeLock.isHeld()) {
+                mWakeLock.release();
+            }
+        }
+        mStayAwake = awake;
+        updateSurfaceScreenOn();
+    }
+
+    private void updateSurfaceScreenOn() {
+        if (mSurfaceHolder != null) {
+            mSurfaceHolder.setKeepScreenOn(mScreenOnWhilePlaying && mStayAwake);
+        }
+    }
 
     /**
      * Cancels a pending command.
@@ -584,17 +1355,26 @@
      * @return {@code false} if the task could not be cancelled; {@code true} otherwise.
      */
     // This is a synchronous call.
-    public abstract boolean cancelCommand(Object token);
+    public boolean cancelCommand(Object token) {
+        synchronized (mTaskLock) {
+            return mPendingTasks.remove(token);
+        }
+    }
 
     /**
      * Discards all pending commands.
      */
     // This is a synchronous call.
-    public abstract void clearPendingCommands();
+    public void clearPendingCommands() {
+        synchronized (mTaskLock) {
+            mPendingTasks.clear();
+        }
+    }
 
     //--------------------------------------------------------------------------
     // Explicit Routing
     //--------------------
+    private AudioDeviceInfo mPreferredDevice = null;
 
     /**
      * Specifies an audio device (via an {@link AudioDeviceInfo} object) to route
@@ -606,14 +1386,28 @@
      */
     // This is a synchronous call.
     @Override
-    public abstract boolean setPreferredDevice(AudioDeviceInfo deviceInfo);
+    public boolean setPreferredDevice(AudioDeviceInfo deviceInfo) {
+        boolean status = native_setPreferredDevice(deviceInfo);
+        if (status) {
+            synchronized (this) {
+                mPreferredDevice = deviceInfo;
+            }
+        }
+        return status;
+    }
+
+    private native boolean native_setPreferredDevice(AudioDeviceInfo device);
 
     /**
      * Returns the selected output specified by {@link #setPreferredDevice}. Note that this
      * is not guaranteed to correspond to the actual device being used for playback.
      */
     @Override
-    public abstract AudioDeviceInfo getPreferredDevice();
+    public AudioDeviceInfo getPreferredDevice() {
+        synchronized (this) {
+            return mPreferredDevice;
+        }
+    }
 
     /**
      * Returns an {@link AudioDeviceInfo} identifying the current routing of this MediaPlayer2
@@ -622,7 +1416,7 @@
      * selected device when the player was last active.
      */
     @Override
-    public abstract AudioDeviceInfo getRoutedDevice();
+    public native AudioDeviceInfo getRoutedDevice();
 
     /**
      * Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of routing
@@ -634,8 +1428,16 @@
      */
     // This is a synchronous call.
     @Override
-    public abstract void addOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener,
-            Handler handler);
+    public void addOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener,
+            Handler handler) {
+        if (listener == null) {
+            throw new IllegalArgumentException("addOnRoutingChangedListener: listener is NULL");
+        }
+        RoutingDelegate routingDelegate = new RoutingDelegate(this, listener, handler);
+        native_addDeviceCallback(routingDelegate);
+    }
+
+    private native void native_addDeviceCallback(RoutingDelegate rd);
 
     /**
      * Removes an {@link AudioRouting.OnRoutingChangedListener} which has been previously added
@@ -645,7 +1447,14 @@
      */
     // This is a synchronous call.
     @Override
-    public abstract void removeOnRoutingChangedListener(
+    public void removeOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("removeOnRoutingChangedListener: listener is NULL");
+        }
+        native_removeDeviceCallback(listener);
+    }
+
+    private native void native_removeDeviceCallback(
             AudioRouting.OnRoutingChangedListener listener);
 
     /**
@@ -658,7 +1467,9 @@
      * notification {@code EventCallback.onVideoSizeChanged} when the size
      * is available.
      */
-    public abstract VideoSize getVideoSize();
+    public VideoSize getVideoSize() {
+        return mVideoSize;
+    }
 
     /**
      * Return Metrics data about the current player.
@@ -669,7 +1480,13 @@
      *
      * Additional vendor-specific fields may also be present in the return value.
      */
-    public abstract PersistableBundle getMetrics();
+    public PersistableBundle getMetrics() {
+        PersistableBundle bundle = native_getMetrics();
+        return bundle;
+    }
+
+    private native PersistableBundle native_getMetrics();
+
 
     /**
      * Gets the current buffering management params used by the source component.
@@ -682,9 +1499,7 @@
      */
     // TODO: make it public when ready
     @NonNull
-    BufferingParams getBufferingParams() {
-        return new BufferingParams.Builder().build();
-    }
+    native BufferingParams getBufferingParams();
 
     /**
      * Sets buffering management params.
@@ -698,7 +1513,18 @@
      */
     // TODO: make it public when ready
     // This is an asynchronous call.
-    abstract Object setBufferingParams(@NonNull BufferingParams params);
+    Object setBufferingParams(@NonNull BufferingParams params) {
+        return addTask(new Task(CALL_COMPLETED_SET_BUFFERING_PARAMS, false) {
+            @Override
+            void process() {
+                checkArgument(params != null, "the BufferingParams cannot be null");
+                native_setBufferingParams(params);
+            }
+        });
+    }
+
+    private native void native_setBufferingParams(@NonNull BufferingParams params);
+
 
     /**
      * Sets playback rate using {@link PlaybackParams}. The object sets its internal
@@ -710,7 +1536,17 @@
      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
      */
     // This is an asynchronous call.
-    public abstract Object setPlaybackParams(@NonNull PlaybackParams params);
+    public Object setPlaybackParams(@NonNull PlaybackParams params) {
+        return addTask(new Task(CALL_COMPLETED_SET_PLAYBACK_PARAMS, false) {
+            @Override
+            void process() {
+                checkArgument(params != null, "the PlaybackParams cannot be null");
+                native_setPlaybackParams(params);
+            }
+        });
+    }
+
+    private native void native_setPlaybackParams(@NonNull PlaybackParams params);
 
     /**
      * Gets the playback params, containing the current playback rate.
@@ -719,7 +1555,7 @@
      * @throws IllegalStateException if the internal player engine has not been initialized.
      */
     @NonNull
-    public abstract PlaybackParams getPlaybackParams();
+    public native PlaybackParams getPlaybackParams();
 
     /**
      * Sets A/V sync mode.
@@ -728,7 +1564,17 @@
      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
      */
     // This is an asynchronous call.
-    public abstract Object setSyncParams(@NonNull SyncParams params);
+    public Object setSyncParams(@NonNull SyncParams params) {
+        return addTask(new Task(CALL_COMPLETED_SET_SYNC_PARAMS, false) {
+            @Override
+            void process() {
+                checkArgument(params != null, "the SyncParams cannot be null");
+                native_setSyncParams(params);
+            }
+        });
+    }
+
+    private native void native_setSyncParams(@NonNull SyncParams params);
 
     /**
      * Gets the A/V sync mode.
@@ -737,7 +1583,7 @@
      * @throws IllegalStateException if the internal player engine has not been initialized.
      */
     @NonNull
-    public abstract SyncParams getSyncParams();
+    public native SyncParams getSyncParams();
 
     /**
      * Moves the media to specified time position.
@@ -821,7 +1667,47 @@
      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
      */
     // This is an asynchronous call.
-    public abstract Object seekTo(long msec, @SeekMode int mode);
+    public Object seekTo(long msec, @SeekMode int mode) {
+        return addTask(new Task(CALL_COMPLETED_SEEK_TO, true) {
+            @Override
+            void process() {
+                if (mode < SEEK_PREVIOUS_SYNC || mode > SEEK_CLOSEST) {
+                    final String msg = "Illegal seek mode: " + mode;
+                    throw new IllegalArgumentException(msg);
+                }
+                // TODO: pass long to native, instead of truncating here.
+                long posMs = msec;
+                if (posMs > Integer.MAX_VALUE) {
+                    Log.w(TAG, "seekTo offset " + posMs + " is too large, cap to "
+                            + Integer.MAX_VALUE);
+                    posMs = Integer.MAX_VALUE;
+                } else if (posMs < Integer.MIN_VALUE) {
+                    Log.w(TAG, "seekTo offset " + posMs + " is too small, cap to "
+                            + Integer.MIN_VALUE);
+                    posMs = Integer.MIN_VALUE;
+                }
+
+                synchronized (mTaskLock) {
+                    if (mIsPreviousCommandSeekTo
+                            && mPreviousSeekPos == posMs
+                            && mPreviousSeekMode == mode) {
+                        throw new CommandSkippedException(
+                                "same as previous seekTo");
+                    }
+                }
+
+                native_seekTo(posMs, mode);
+
+                synchronized (mTaskLock) {
+                    mIsPreviousCommandSeekTo = true;
+                    mPreviousSeekPos = posMs;
+                    mPreviousSeekMode = mode;
+                }
+            }
+        });
+    }
+
+    private native void native_seekTo(long msec, int mode);
 
     /**
      * Get current playback position as a {@link MediaTimestamp}.
@@ -842,15 +1728,17 @@
      * @see MediaTimestamp
      */
     @Nullable
-    public abstract MediaTimestamp getTimestamp();
-
-    /**
-     * Resets the MediaPlayer2 to its uninitialized state. After calling
-     * this method, you will have to initialize it again by setting the
-     * data source and calling prepare().
-     */
-    // This is a synchronous call.
-    public abstract void reset();
+    public MediaTimestamp getTimestamp() {
+        try {
+            // TODO: get the timestamp from native side
+            return new MediaTimestamp(
+                    getCurrentPosition() * 1000L,
+                    System.nanoTime(),
+                    getState() == PLAYER_STATE_PLAYING ? getPlaybackParams().getSpeed() : 0.f);
+        } catch (IllegalStateException e) {
+            return null;
+        }
+    }
 
     /**
      * Checks whether the MediaPlayer2 is looping or non-looping.
@@ -858,7 +1746,7 @@
      * @return true if the MediaPlayer2 is currently looping, false otherwise
      */
     // This is a synchronous call.
-    public abstract boolean isLooping();
+    public native boolean isLooping();
 
     /**
      * Sets the audio session ID.
@@ -878,7 +1766,17 @@
      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
      */
     // This is an asynchronous call.
-    public abstract Object setAudioSessionId(int sessionId);
+    public Object setAudioSessionId(int sessionId) {
+        keepAudioSessionIdAlive(sessionId);
+        return addTask(new Task(CALL_COMPLETED_SET_AUDIO_SESSION_ID, false) {
+            @Override
+            void process() {
+                native_setAudioSessionId(sessionId);
+            }
+        });
+    }
+
+    private native void native_setAudioSessionId(int sessionId);
 
     /**
      * Returns the audio session ID.
@@ -888,7 +1786,7 @@
      * contructed.
      */
     // This is a synchronous call.
-    public abstract int getAudioSessionId();
+    public native int getAudioSessionId();
 
     /**
      * Attaches an auxiliary effect to the player. A typical auxiliary effect is a reverberation
@@ -906,7 +1804,16 @@
      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
      */
     // This is an asynchronous call.
-    public abstract Object attachAuxEffect(int effectId);
+    public Object attachAuxEffect(int effectId) {
+        return addTask(new Task(CALL_COMPLETED_ATTACH_AUX_EFFECT, false) {
+            @Override
+            void process() {
+                native_attachAuxEffect(effectId);
+            }
+        });
+    }
+
+    private native void native_attachAuxEffect(int effectId);
 
     /**
      * Sets the send level of the player to the attached auxiliary effect.
@@ -922,20 +1829,72 @@
      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
      */
     // This is an asynchronous call.
-    public abstract Object setAuxEffectSendLevel(float level);
+    public Object setAuxEffectSendLevel(float level) {
+        return addTask(new Task(CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL, false) {
+            @Override
+            void process() {
+                native_setAuxEffectSendLevel(level);
+            }
+        });
+    }
+
+    private native void native_setAuxEffectSendLevel(float level);
+
+    private static native void native_stream_event_onTearDown(
+            long nativeCallbackPtr, long userDataPtr);
+    private static native void native_stream_event_onStreamPresentationEnd(
+            long nativeCallbackPtr, long userDataPtr);
+    private static native void native_stream_event_onStreamDataRequest(
+            long jAudioTrackPtr, long nativeCallbackPtr, long userDataPtr);
+
+    /* Do not change these values (starting with INVOKE_ID) without updating
+     * their counterparts in include/media/mediaplayer2.h!
+     */
+    private static final int INVOKE_ID_GET_TRACK_INFO = 1;
+    private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE = 2;
+    private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE_FD = 3;
+    private static final int INVOKE_ID_SELECT_TRACK = 4;
+    private static final int INVOKE_ID_DESELECT_TRACK = 5;
+    private static final int INVOKE_ID_GET_SELECTED_TRACK = 7;
+
+    /**
+     * Invoke a generic method on the native player using opaque protocol
+     * buffer message for the request and reply. Both payloads' format is a
+     * convention between the java caller and the native player.
+     *
+     * @param msg PlayerMessage for the extension.
+     *
+     * @return PlayerMessage with the data returned by the
+     * native player.
+     */
+    private PlayerMessage invoke(PlayerMessage msg) {
+        byte[] ret = native_invoke(msg.toByteArray());
+        if (ret == null) {
+            return null;
+        }
+        try {
+            return PlayerMessage.parseFrom(ret);
+        } catch (InvalidProtocolBufferException e) {
+            return null;
+        }
+    }
+
+    private native byte[] native_invoke(byte[] request);
 
     /**
      * Class for MediaPlayer2 to return each audio/video/subtitle track's metadata.
      *
      * @see MediaPlayer2#getTrackInfo
      */
-    public abstract static class TrackInfo {
+    public static class TrackInfo {
         /**
          * Gets the track type.
          * @return TrackType which indicates if the track is video, audio, timed text.
          */
         @UnsupportedAppUsage
-        public abstract int getTrackType();
+        public int getTrackType() {
+            return mTrackType;
+        }
 
         /**
          * Gets the language code of the track.
@@ -944,13 +1903,22 @@
          * ISO-639-2 language code, "und", is returned.
          */
         @UnsupportedAppUsage
-        public abstract String getLanguage();
+        public String getLanguage() {
+            String language = mFormat.getString(MediaFormat.KEY_LANGUAGE);
+            return language == null ? "und" : language;
+        }
 
         /**
          * Gets the {@link MediaFormat} of the track.  If the format is
          * unknown or could not be determined, null is returned.
          */
-        public abstract MediaFormat getFormat();
+        public MediaFormat getFormat() {
+            if (mTrackType == MEDIA_TRACK_TYPE_TIMEDTEXT
+                    || mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) {
+                return mFormat;
+            }
+            return null;
+        }
 
         public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0;
         public static final int MEDIA_TRACK_TYPE_VIDEO = 1;
@@ -962,8 +1930,56 @@
         public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4;
         public static final int MEDIA_TRACK_TYPE_METADATA = 5;
 
+        final int mTrackType;
+        final MediaFormat mFormat;
+
+        TrackInfo(Iterator<Value> in) {
+            mTrackType = in.next().getInt32Value();
+            // TODO: build the full MediaFormat; currently we are using createSubtitleFormat
+            // even for audio/video tracks, meaning we only set the mime and language.
+            String mime = in.next().getStringValue();
+            String language = in.next().getStringValue();
+            mFormat = MediaFormat.createSubtitleFormat(mime, language);
+
+            if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) {
+                mFormat.setInteger(MediaFormat.KEY_IS_AUTOSELECT, in.next().getInt32Value());
+                mFormat.setInteger(MediaFormat.KEY_IS_DEFAULT, in.next().getInt32Value());
+                mFormat.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, in.next().getInt32Value());
+            }
+        }
+
+        /** @hide */
+        TrackInfo(int type, MediaFormat format) {
+            mTrackType = type;
+            mFormat = format;
+        }
+
         @Override
-        public abstract String toString();
+        public String toString() {
+            StringBuilder out = new StringBuilder(128);
+            out.append(getClass().getName());
+            out.append('{');
+            switch (mTrackType) {
+                case MEDIA_TRACK_TYPE_VIDEO:
+                    out.append("VIDEO");
+                    break;
+                case MEDIA_TRACK_TYPE_AUDIO:
+                    out.append("AUDIO");
+                    break;
+                case MEDIA_TRACK_TYPE_TIMEDTEXT:
+                    out.append("TIMEDTEXT");
+                    break;
+                case MEDIA_TRACK_TYPE_SUBTITLE:
+                    out.append("SUBTITLE");
+                    break;
+                default:
+                    out.append("UNKNOWN");
+                    break;
+            }
+            out.append(", " + mFormat.toString());
+            out.append("}");
+            return out.toString();
+        }
     };
 
     /**
@@ -974,7 +1990,30 @@
      * addTimedTextSource method is called.
      * @throws IllegalStateException if it is called in an invalid state.
      */
-    public abstract List<TrackInfo> getTrackInfo();
+    public List<TrackInfo> getTrackInfo() {
+        TrackInfo[] trackInfo = getInbandTrackInfo();
+        return Arrays.asList(trackInfo);
+    }
+
+    private TrackInfo[] getInbandTrackInfo() throws IllegalStateException {
+        PlayerMessage request = PlayerMessage.newBuilder()
+                .addValues(Value.newBuilder().setInt32Value(INVOKE_ID_GET_TRACK_INFO))
+                .build();
+        PlayerMessage response = invoke(request);
+        if (response == null) {
+            return null;
+        }
+        Iterator<Value> in = response.getValuesList().iterator();
+        int size = in.next().getInt32Value();
+        if (size == 0) {
+            return null;
+        }
+        TrackInfo[] trackInfo = new TrackInfo[size];
+        for (int i = 0; i < size; ++i) {
+            trackInfo[i] = new TrackInfo(in);
+        }
+        return trackInfo;
+    }
 
     /**
      * Returns the index of the audio, video, or subtitle track currently selected for playback,
@@ -993,7 +2032,17 @@
      * @see #selectTrack(int)
      * @see #deselectTrack(int)
      */
-    public abstract int getSelectedTrack(int trackType);
+    public int getSelectedTrack(int trackType) {
+        PlayerMessage request = PlayerMessage.newBuilder()
+                .addValues(Value.newBuilder().setInt32Value(INVOKE_ID_GET_SELECTED_TRACK))
+                .addValues(Value.newBuilder().setInt32Value(trackType))
+                .build();
+        PlayerMessage response = invoke(request);
+        if (response == null) {
+            return -1;
+        }
+        return response.getValues(0).getInt32Value();
+    }
 
     /**
      * Selects a track.
@@ -1024,7 +2073,14 @@
      * @see MediaPlayer2#getTrackInfo
      */
     // This is an asynchronous call.
-    public abstract Object selectTrack(int index);
+    public Object selectTrack(int index) {
+        return addTask(new Task(CALL_COMPLETED_SELECT_TRACK, false) {
+            @Override
+            void process() {
+                selectOrDeselectTrack(index, true /* select */);
+            }
+        });
+    }
 
     /**
      * Deselect a track.
@@ -1041,13 +2097,450 @@
      * @see MediaPlayer2#getTrackInfo
      */
     // This is an asynchronous call.
-    public abstract Object deselectTrack(int index);
+    public Object deselectTrack(int index) {
+        return addTask(new Task(CALL_COMPLETED_DESELECT_TRACK, false) {
+            @Override
+            void process() {
+                selectOrDeselectTrack(index, false /* select */);
+            }
+        });
+    }
+
+    private void selectOrDeselectTrack(int index, boolean select)
+            throws IllegalStateException {
+        PlayerMessage request = PlayerMessage.newBuilder()
+                .addValues(Value.newBuilder().setInt32Value(
+                            select ? INVOKE_ID_SELECT_TRACK : INVOKE_ID_DESELECT_TRACK))
+                .addValues(Value.newBuilder().setInt32Value(index))
+                .build();
+        invoke(request);
+    }
+
+    /* Do not change these values without updating their counterparts
+     * in include/media/mediaplayer2.h!
+     */
+    private static final int MEDIA_NOP = 0; // interface test message
+    private static final int MEDIA_PREPARED = 1;
+    private static final int MEDIA_PLAYBACK_COMPLETE = 2;
+    private static final int MEDIA_BUFFERING_UPDATE = 3;
+    private static final int MEDIA_SEEK_COMPLETE = 4;
+    private static final int MEDIA_SET_VIDEO_SIZE = 5;
+    private static final int MEDIA_STARTED = 6;
+    private static final int MEDIA_PAUSED = 7;
+    private static final int MEDIA_STOPPED = 8;
+    private static final int MEDIA_SKIPPED = 9;
+    private static final int MEDIA_NOTIFY_TIME = 98;
+    private static final int MEDIA_TIMED_TEXT = 99;
+    private static final int MEDIA_ERROR = 100;
+    private static final int MEDIA_INFO = 200;
+    private static final int MEDIA_SUBTITLE_DATA = 201;
+    private static final int MEDIA_META_DATA = 202;
+    private static final int MEDIA_DRM_INFO = 210;
+
+    private class TaskHandler extends Handler {
+        private MediaPlayer2 mMediaPlayer;
+
+        TaskHandler(MediaPlayer2 mp, Looper looper) {
+            super(looper);
+            mMediaPlayer = mp;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            handleMessage(msg, 0);
+        }
+
+        public void handleMessage(Message msg, long srcId) {
+            if (mMediaPlayer.mNativeContext == 0) {
+                Log.w(TAG, "mediaplayer2 went away with unhandled events");
+                return;
+            }
+            final int what = msg.arg1;
+            final int extra = msg.arg2;
+
+            final SourceInfo sourceInfo = getSourceInfoById(srcId);
+            if (sourceInfo == null) {
+                return;
+            }
+            final DataSourceDesc dsd = sourceInfo.mDSD;
+
+            switch(msg.what) {
+                case MEDIA_PREPARED:
+                {
+                    if (dsd != null) {
+                        sendEvent(new EventNotifier() {
+                            @Override
+                            public void notify(EventCallback callback) {
+                                callback.onInfo(
+                                        mMediaPlayer, dsd, MEDIA_INFO_PREPARED, 0);
+                            }
+                        });
+                    }
+
+                    synchronized (mSrcLock) {
+                        SourceInfo nextSourceInfo = mNextSourceInfos.peek();
+                        Log.i(TAG, "MEDIA_PREPARED: srcId=" + srcId
+                                + ", curSrc=" + mCurrentSourceInfo
+                                + ", nextSrc=" + nextSourceInfo);
+
+                        if (isCurrentSource(srcId)) {
+                            prepareNextDataSource();
+                        } else if (isNextSource(srcId)) {
+                            nextSourceInfo.mStateAsNextSource = NEXT_SOURCE_STATE_PREPARED;
+                            if (nextSourceInfo.mPlayPendingAsNextSource) {
+                                playNextDataSource();
+                            }
+                        }
+                    }
+
+                    synchronized (mTaskLock) {
+                        if (mCurrentTask != null
+                                && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE
+                                && mCurrentTask.mDSD == dsd
+                                && mCurrentTask.mNeedToWaitForEventToComplete) {
+                            mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR);
+                            mCurrentTask = null;
+                            processPendingTask_l();
+                        }
+                    }
+                    return;
+                }
+
+                case MEDIA_DRM_INFO:
+                {
+                    if (msg.obj == null) {
+                        Log.w(TAG, "MEDIA_DRM_INFO msg.obj=NULL");
+                    } else if (msg.obj instanceof byte[]) {
+                        // The PlayerMessage was parsed already in postEventFromNative
+                        final DrmInfo drmInfo;
+
+                        synchronized (mDrmLock) {
+                            if (mDrmInfo != null) {
+                                drmInfo = mDrmInfo.makeCopy();
+                            } else {
+                                drmInfo = null;
+                            }
+                        }
+
+                        // notifying the client outside the lock
+                        if (drmInfo != null) {
+                            sendDrmEvent(new DrmEventNotifier() {
+                                @Override
+                                public void notify(DrmEventCallback callback) {
+                                    callback.onDrmInfo(
+                                            mMediaPlayer, dsd, drmInfo);
+                                }
+                            });
+                        }
+                    } else {
+                        Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + msg.obj);
+                    }
+                    return;
+                }
+
+                case MEDIA_PLAYBACK_COMPLETE:
+                {
+                    if (isCurrentSource(srcId)) {
+                        sendEvent(new EventNotifier() {
+                            @Override
+                            public void notify(EventCallback callback) {
+                                callback.onInfo(
+                                        mMediaPlayer, dsd, MEDIA_INFO_DATA_SOURCE_END, 0);
+                            }
+                        });
+                        stayAwake(false);
+
+                        synchronized (mSrcLock) {
+                            SourceInfo nextSourceInfo = mNextSourceInfos.peek();
+                            if (nextSourceInfo != null) {
+                                nextSourceInfo.mPlayPendingAsNextSource = true;
+                            }
+                            Log.i(TAG, "MEDIA_PLAYBACK_COMPLETE: srcId=" + srcId
+                                    + ", curSrc=" + mCurrentSourceInfo
+                                    + ", nextSrc=" + nextSourceInfo);
+                        }
+
+                        playNextDataSource();
+                    }
+
+                    return;
+                }
+
+                case MEDIA_STOPPED:
+                case MEDIA_STARTED:
+                case MEDIA_PAUSED:
+                case MEDIA_SKIPPED:
+                case MEDIA_NOTIFY_TIME:
+                {
+                    // Do nothing. The client should have enough information with
+                    // {@link EventCallback#onMediaTimeDiscontinuity}.
+                    break;
+                }
+
+                case MEDIA_BUFFERING_UPDATE:
+                {
+                    final int percent = msg.arg1;
+                    sendEvent(new EventNotifier() {
+                        @Override
+                        public void notify(EventCallback callback) {
+                            callback.onInfo(
+                                    mMediaPlayer, dsd, MEDIA_INFO_BUFFERING_UPDATE, percent);
+                        }
+                    });
+
+                    SourceInfo src = getSourceInfoById(srcId);
+                    if (src != null) {
+                        src.mBufferedPercentage.set(percent);
+                    }
+
+                    return;
+                }
+
+                case MEDIA_SEEK_COMPLETE:
+                {
+                    synchronized (mTaskLock) {
+                        if (!mPendingTasks.isEmpty()
+                                && mPendingTasks.get(0).mMediaCallType != CALL_COMPLETED_SEEK_TO
+                                && getState() == PLAYER_STATE_PLAYING) {
+                            mIsPreviousCommandSeekTo = false;
+                        }
+
+                        if (mCurrentTask != null
+                                && mCurrentTask.mMediaCallType == CALL_COMPLETED_SEEK_TO
+                                && mCurrentTask.mNeedToWaitForEventToComplete) {
+                            mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR);
+                            mCurrentTask = null;
+                            processPendingTask_l();
+                        }
+                    }
+                    return;
+                }
+
+                case MEDIA_SET_VIDEO_SIZE:
+                {
+                    final int width = msg.arg1;
+                    final int height = msg.arg2;
+
+                    mVideoSize = new VideoSize(width, height);
+                    sendEvent(new EventNotifier() {
+                        @Override
+                        public void notify(EventCallback callback) {
+                            callback.onVideoSizeChanged(
+                                    mMediaPlayer, dsd, mVideoSize);
+                        }
+                    });
+                    return;
+                }
+
+                case MEDIA_ERROR:
+                {
+                    Log.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")");
+                    sendEvent(new EventNotifier() {
+                        @Override
+                        public void notify(EventCallback callback) {
+                            callback.onError(
+                                    mMediaPlayer, dsd, what, extra);
+                        }
+                    });
+                    sendEvent(new EventNotifier() {
+                        @Override
+                        public void notify(EventCallback callback) {
+                            callback.onInfo(
+                                    mMediaPlayer, dsd, MEDIA_INFO_DATA_SOURCE_END, 0);
+                        }
+                    });
+                    stayAwake(false);
+                    return;
+                }
+
+                case MEDIA_INFO:
+                {
+                    switch (msg.arg1) {
+                        case MEDIA_INFO_VIDEO_TRACK_LAGGING:
+                            Log.i(TAG, "Info (" + msg.arg1 + "," + msg.arg2 + ")");
+                            break;
+                    }
+
+                    sendEvent(new EventNotifier() {
+                        @Override
+                        public void notify(EventCallback callback) {
+                            callback.onInfo(
+                                    mMediaPlayer, dsd, what, extra);
+                        }
+                    });
+
+                    if (msg.arg1 == MEDIA_INFO_DATA_SOURCE_START) {
+                        if (isCurrentSource(srcId)) {
+                            prepareNextDataSource();
+                        }
+                    }
+
+                    // No real default action so far.
+                    return;
+                }
+
+                case MEDIA_TIMED_TEXT:
+                {
+                    final TimedText text;
+                    if (msg.obj instanceof byte[]) {
+                        PlayerMessage playerMsg;
+                        try {
+                            playerMsg = PlayerMessage.parseFrom((byte[]) msg.obj);
+                        } catch (InvalidProtocolBufferException e) {
+                            Log.w(TAG, "Failed to parse timed text.", e);
+                            return;
+                        }
+                        text = TimedTextUtil.parsePlayerMessage(playerMsg);
+                    } else {
+                        text = null;
+                    }
+
+                    sendEvent(new EventNotifier() {
+                        @Override
+                        public void notify(EventCallback callback) {
+                            callback.onTimedText(
+                                    mMediaPlayer, dsd, text);
+                        }
+                    });
+                    return;
+                }
+
+                case MEDIA_SUBTITLE_DATA:
+                {
+                    if (msg.obj instanceof byte[]) {
+                        PlayerMessage playerMsg;
+                        try {
+                            playerMsg = PlayerMessage.parseFrom((byte[]) msg.obj);
+                        } catch (InvalidProtocolBufferException e) {
+                            Log.w(TAG, "Failed to parse subtitle data.", e);
+                            return;
+                        }
+                        Iterator<Value> in = playerMsg.getValuesList().iterator();
+                        SubtitleData data = new SubtitleData(
+                                in.next().getInt32Value(),  // trackIndex
+                                in.next().getInt64Value(),  // startTimeUs
+                                in.next().getInt64Value(),  // durationUs
+                                in.next().getBytesValue().toByteArray());  // data
+                        sendEvent(new EventNotifier() {
+                            @Override
+                            public void notify(EventCallback callback) {
+                                callback.onSubtitleData(
+                                        mMediaPlayer, dsd, data);
+                            }
+                        });
+                    }
+                    return;
+                }
+
+                case MEDIA_META_DATA:
+                {
+                    final TimedMetaData data;
+                    if (msg.obj instanceof byte[]) {
+                        PlayerMessage playerMsg;
+                        try {
+                            playerMsg = PlayerMessage.parseFrom((byte[]) msg.obj);
+                        } catch (InvalidProtocolBufferException e) {
+                            Log.w(TAG, "Failed to parse timed meta data.", e);
+                            return;
+                        }
+                        Iterator<Value> in = playerMsg.getValuesList().iterator();
+                        data = new TimedMetaData(
+                                in.next().getInt64Value(),  // timestampUs
+                                in.next().getBytesValue().toByteArray());  // metaData
+                    } else {
+                        data = null;
+                    }
+
+                    sendEvent(new EventNotifier() {
+                        @Override
+                        public void notify(EventCallback callback) {
+                            callback.onTimedMetaDataAvailable(
+                                    mMediaPlayer, dsd, data);
+                        }
+                    });
+                    return;
+                }
+
+                case MEDIA_NOP: // interface test message - ignore
+                {
+                    break;
+                }
+
+                default:
+                {
+                    Log.e(TAG, "Unknown message type " + msg.what);
+                    return;
+                }
+            }
+        }
+    }
+
+    /*
+     * Called from native code when an interesting event happens.  This method
+     * just uses the TaskHandler system to post the event back to the main app thread.
+     * We use a weak reference to the original MediaPlayer2 object so that the native
+     * code is safe from the object disappearing from underneath it.  (This is
+     * the cookie passed to native_setup().)
+     */
+    private static void postEventFromNative(Object mediaplayer2Ref, long srcId,
+                                            int what, int arg1, int arg2, byte[] obj) {
+        final MediaPlayer2 mp = (MediaPlayer2) ((WeakReference) mediaplayer2Ref).get();
+        if (mp == null) {
+            return;
+        }
+
+        switch (what) {
+            case MEDIA_DRM_INFO:
+                // We need to derive mDrmInfo before prepare() returns so processing it here
+                // before the notification is sent to TaskHandler below. TaskHandler runs in the
+                // notification looper so its handleMessage might process the event after prepare()
+                // has returned.
+                Log.v(TAG, "postEventFromNative MEDIA_DRM_INFO");
+                if (obj != null) {
+                    PlayerMessage playerMsg;
+                    try {
+                        playerMsg = PlayerMessage.parseFrom(obj);
+                    } catch (InvalidProtocolBufferException e) {
+                        Log.w(TAG, "MEDIA_DRM_INFO failed to parse msg.obj " + obj);
+                        break;
+                    }
+                    DrmInfo drmInfo = new DrmInfo(playerMsg);
+                    synchronized (mp.mDrmLock) {
+                        mp.mDrmInfo = drmInfo;
+                    }
+                } else {
+                    Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + obj);
+                }
+                break;
+
+            case MEDIA_PREPARED:
+                // By this time, we've learned about DrmInfo's presence or absence. This is meant
+                // mainly for prepare() use case. For prepare(), this still can run to a race
+                // condition b/c MediaPlayerNative releases the prepare() lock before calling notify
+                // so we also set mDrmInfoResolved in prepare().
+                synchronized (mp.mDrmLock) {
+                    mp.mDrmInfoResolved = true;
+                }
+                break;
+        }
+
+        if (mp.mTaskHandler != null) {
+            Message m = mp.mTaskHandler.obtainMessage(what, arg1, arg2, obj);
+
+            mp.mTaskHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mp.mTaskHandler.handleMessage(m, srcId);
+                }
+            });
+        }
+    }
 
     /**
      * Interface definition for callbacks to be invoked when the player has the corresponding
      * events.
      */
-    public abstract static class EventCallback {
+    public static class EventCallback {
         /**
          * Called to indicate the video size
          *
@@ -1157,6 +2650,10 @@
                 MediaPlayer2 mp, DataSourceDesc dsd, @NonNull SubtitleData data) { }
     }
 
+    private final Object mEventCbLock = new Object();
+    private ArrayList<Pair<Executor, EventCallback>> mEventCallbackRecords =
+            new ArrayList<Pair<Executor, EventCallback>>();
+
     /**
      * Registers the callback to be invoked for various events covered by {@link EventCallback}.
      *
@@ -1164,8 +2661,19 @@
      * @param eventCallback the callback that will be run
      */
     // This is a synchronous call.
-    public abstract void registerEventCallback(@NonNull @CallbackExecutor Executor executor,
-            @NonNull EventCallback eventCallback);
+    public void registerEventCallback(@NonNull @CallbackExecutor Executor executor,
+            @NonNull EventCallback eventCallback) {
+        if (eventCallback == null) {
+            throw new IllegalArgumentException("Illegal null EventCallback");
+        }
+        if (executor == null) {
+            throw new IllegalArgumentException(
+                    "Illegal null Executor for the EventCallback");
+        }
+        synchronized (mEventCbLock) {
+            mEventCallbackRecords.add(new Pair(executor, eventCallback));
+        }
+    }
 
     /**
      * Unregisters the {@link EventCallback}.
@@ -1173,7 +2681,55 @@
      * @param eventCallback the callback to be unregistered
      */
     // This is a synchronous call.
-    public abstract void unregisterEventCallback(EventCallback eventCallback);
+    public void unregisterEventCallback(EventCallback eventCallback) {
+        synchronized (mEventCbLock) {
+            for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) {
+                if (cb.second == eventCallback) {
+                    mEventCallbackRecords.remove(cb);
+                }
+            }
+        }
+    }
+
+    private static void checkArgument(boolean expression, String errorMessage) {
+        if (!expression) {
+            throw new IllegalArgumentException(errorMessage);
+        }
+    }
+
+    private void sendEvent(final EventNotifier notifier) {
+        synchronized (mEventCbLock) {
+            try {
+                for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) {
+                    cb.first.execute(() -> notifier.notify(cb.second));
+                }
+            } catch (RejectedExecutionException e) {
+                // The executor has been shut down.
+                Log.w(TAG, "The executor has been shut down. Ignoring event.");
+            }
+        }
+    }
+
+    private void sendDrmEvent(final DrmEventNotifier notifier) {
+        synchronized (mDrmEventCbLock) {
+            try {
+                for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) {
+                    cb.first.execute(() -> notifier.notify(cb.second));
+                }
+            } catch (RejectedExecutionException e) {
+                // The executor has been shut down.
+                Log.w(TAG, "The executor has been shut down. Ignoring drm event.");
+            }
+        }
+    }
+
+    private interface EventNotifier {
+        void notify(EventCallback callback);
+    }
+
+    private interface DrmEventNotifier {
+        void notify(DrmEventCallback callback);
+    }
 
     /* Do not change these values without updating their counterparts
      * in include/media/MediaPlayer2Types.h!
@@ -1515,7 +3071,7 @@
     public static final int CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED =
             SEPARATE_CALL_COMPLETED_CALLBACK_START;
 
-    /** The player just completed a call {@link #prepareDrm}.
+    /** The player just completed a call {@link #prepareDrm(DataSourceDesc, UUID)}.
      * @see DrmEventCallback#onDrmPrepared
      * @hide
      */
@@ -1593,7 +3149,7 @@
     public static final int CALL_STATUS_SKIPPED = 5;
 
     /** Status code represents that DRM operation is called before preparing a DRM scheme through
-     *  {@link #prepareDrm}.
+     *  {@link #prepareDrm(DataSourceDesc, UUID)}.
      * @see EventCallback#onCallCompleted
      */
     public static final int CALL_STATUS_NO_DRM_SCHEME = 6;
@@ -1622,11 +3178,11 @@
      * 'securityLevel', which has to be set after DRM scheme creation but
      * before the DRM session is opened.
      *
-     * The only allowed DRM calls in this listener are {@link #getDrmPropertyString}
-     * and {@link #setDrmPropertyString}.
+     * The only allowed DRM calls in this listener are
+     * {@link MediaPlayer2#getDrmPropertyString(DataSourceDesc, String)}
+     * and {@link MediaPlayer2#setDrmPropertyString(DataSourceDesc, String, String)}.
      */
-    public interface OnDrmConfigHelper
-    {
+    public interface OnDrmConfigHelper {
         /**
          * Called to give the app the opportunity to configure DRM before the session is created
          *
@@ -1640,18 +3196,24 @@
      * Register a callback to be invoked for configuration of the DRM object before
      * the session is created.
      * The callback will be invoked synchronously during the execution
-     * of {@link #prepareDrm(UUID uuid)}.
+     * of {@link #prepareDrm(DataSourceDesc, UUID)}.
      *
      * @param listener the callback that will be run
      */
     // This is a synchronous call.
-    public abstract void setOnDrmConfigHelper(OnDrmConfigHelper listener);
+    public void setOnDrmConfigHelper(OnDrmConfigHelper listener) {
+        synchronized (mDrmLock) {
+            mOnDrmConfigHelper = listener;
+        }
+    }
+
+    private OnDrmConfigHelper mOnDrmConfigHelper;
 
     /**
      * Interface definition for callbacks to be invoked when the player has the corresponding
      * DRM events.
      */
-    public abstract static class DrmEventCallback {
+    public static class DrmEventCallback {
         /**
          * Called to indicate DRM info is available
          *
@@ -1663,8 +3225,8 @@
         public void onDrmInfo(MediaPlayer2 mp, DataSourceDesc dsd, DrmInfo drmInfo) { }
 
         /**
-         * Called to notify the client that {@link #prepareDrm} is finished and ready for
-         * key request/response.
+         * Called to notify the client that {@link MediaPlayer2#prepareDrm(DataSourceDesc, UUID)}
+         * is finished and ready for key request/response.
          *
          * @param mp the {@code MediaPlayer2} associated with this callback
          * @param dsd the DataSourceDesc of this data source
@@ -1674,6 +3236,10 @@
                 MediaPlayer2 mp, DataSourceDesc dsd, @PrepareDrmStatusCode int status) { }
     }
 
+    private final Object mDrmEventCbLock = new Object();
+    private ArrayList<Pair<Executor, DrmEventCallback>> mDrmEventCallbackRecords =
+            new ArrayList<Pair<Executor, DrmEventCallback>>();
+
     /**
      * Registers the callback to be invoked for various DRM events.
      *
@@ -1681,8 +3247,19 @@
      * @param executor the executor through which the callback should be invoked
      */
     // This is a synchronous call.
-    public abstract void registerDrmEventCallback(@NonNull @CallbackExecutor Executor executor,
-            @NonNull DrmEventCallback eventCallback);
+    public void registerDrmEventCallback(@NonNull @CallbackExecutor Executor executor,
+            @NonNull DrmEventCallback eventCallback) {
+        if (eventCallback == null) {
+            throw new IllegalArgumentException("Illegal null EventCallback");
+        }
+        if (executor == null) {
+            throw new IllegalArgumentException(
+                    "Illegal null Executor for the EventCallback");
+        }
+        synchronized (mDrmEventCbLock) {
+            mDrmEventCallbackRecords.add(new Pair(executor, eventCallback));
+        }
+    }
 
     /**
      * Unregisters the {@link DrmEventCallback}.
@@ -1690,7 +3267,15 @@
      * @param eventCallback the callback to be unregistered
      */
     // This is a synchronous call.
-    public abstract void unregisterDrmEventCallback(DrmEventCallback eventCallback);
+    public void unregisterDrmEventCallback(DrmEventCallback eventCallback) {
+        synchronized (mDrmEventCbLock) {
+            for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) {
+                if (cb.second == eventCallback) {
+                    mDrmEventCallbackRecords.remove(cb);
+                }
+            }
+        }
+    }
 
     /**
      * The status codes for {@link DrmEventCallback#onDrmPrepared} listener.
@@ -1738,19 +3323,42 @@
     public @interface PrepareDrmStatusCode {}
 
     /**
-     * Retrieves the DRM Info associated with the current source
+     * Retrieves the DRM Info associated with the given source
+     *
+     * @param dsd The DRM protected data source
      *
      * @throws IllegalStateException if called before being prepared
      */
-    public abstract DrmInfo getDrmInfo();
+    public DrmInfo getDrmInfo(@NonNull DataSourceDesc dsd) {
+        // TODO: this implementation only works when dsd is the only data source
+        DrmInfo drmInfo = null;
+
+        // there is not much point if the app calls getDrmInfo within an OnDrmInfoListenet;
+        // regardless below returns drmInfo anyway instead of raising an exception
+        synchronized (mDrmLock) {
+            if (!mDrmInfoResolved && mDrmInfo == null) {
+                final String msg = "The Player has not been prepared yet";
+                Log.v(TAG, msg);
+                throw new IllegalStateException(msg);
+            }
+
+            if (mDrmInfo != null) {
+                drmInfo = mDrmInfo.makeCopy();
+            }
+        }  // synchronized
+
+        return drmInfo;
+    }
 
     /**
-     * Prepares the DRM for the current source
+     * Prepares the DRM for the given data source
      * <p>
      * If {@link OnDrmConfigHelper} is registered, it will be called during
      * preparation to allow configuration of the DRM properties before opening the
-     * DRM session. It should be used only for a series of {@link #getDrmPropertyString}
-     * and {@link #setDrmPropertyString} calls and refrain from any lengthy operation.
+     * DRM session. It should be used only for a series of
+     * {@link #getDrmPropertyString(DataSourceDesc, String)} and
+     * {@link #setDrmPropertyString(DataSourceDesc, String, String)} calls
+     * and refrain from any lengthy operation.
      * <p>
      * If the device has not been provisioned before, this call also provisions the device
      * which involves accessing the provisioning server and can take a variable time to
@@ -1765,40 +3373,233 @@
      * sequence (e.g., before or after prepareDrm returns).
      * <p>
      *
+     * @param dsd The DRM protected data source
+     *
      * @param uuid The UUID of the crypto scheme. If not known beforehand, it can be retrieved
-     * from the source through {@code getDrmInfo} or registering a
+     * from the source through {@link #getDrmInfo(DataSourceDesc)} or registering a
      * {@link DrmEventCallback#onDrmInfo}.
      *
      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
      */
     // This is an asynchronous call.
-    public abstract Object prepareDrm(@NonNull UUID uuid);
+    public Object prepareDrm(@NonNull DataSourceDesc dsd, @NonNull UUID uuid) {
+        // TODO: this implementation only works when dsd is the only data source
+        return addTask(new Task(CALL_COMPLETED_PREPARE_DRM, true) {
+            @Override
+            void process() {
+                int status = PREPARE_DRM_STATUS_SUCCESS;
+                boolean sendEvent = true;
+
+                try {
+                    doPrepareDrm(dsd, uuid);
+                } catch (ResourceBusyException e) {
+                    status = PREPARE_DRM_STATUS_RESOURCE_BUSY;
+                } catch (UnsupportedSchemeException e) {
+                    status = PREPARE_DRM_STATUS_UNSUPPORTED_SCHEME;
+                } catch (NotProvisionedException e) {
+                    Log.w(TAG, "prepareDrm: NotProvisionedException");
+
+                    // handle provisioning internally; it'll reset mPrepareDrmInProgress
+                    status = handleProvisioninig(dsd, uuid);
+
+                    if (status == PREPARE_DRM_STATUS_SUCCESS) {
+                        // DrmEventCallback will be fired in provisioning
+                        sendEvent = false;
+                    } else {
+                        synchronized (mDrmLock) {
+                            cleanDrmObj();
+                        }
+
+                        switch (status) {
+                            case PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR:
+                                Log.e(TAG, "prepareDrm: Provisioning was required but failed "
+                                        + "due to a network error.");
+                                break;
+
+                            case PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR:
+                                Log.e(TAG, "prepareDrm: Provisioning was required but the request "
+                                        + "was denied by the server.");
+                                break;
+
+                            case PREPARE_DRM_STATUS_PREPARATION_ERROR:
+                            default:
+                                Log.e(TAG, "prepareDrm: Post-provisioning preparation failed.");
+                                break;
+                        }
+                    }
+                } catch (Exception e) {
+                    status = PREPARE_DRM_STATUS_PREPARATION_ERROR;
+                }
+
+                if (sendEvent) {
+                    final int prepareDrmStatus = status;
+                    sendDrmEvent(new DrmEventNotifier() {
+                        @Override
+                        public void notify(DrmEventCallback callback) {
+                            callback.onDrmPrepared(
+                                    MediaPlayer2.this, dsd, prepareDrmStatus);
+                        }
+                    });
+
+                    synchronized (mTaskLock) {
+                        mCurrentTask = null;
+                        processPendingTask_l();
+                    }
+                }
+            }
+        });
+    }
+
+    private void doPrepareDrm(@NonNull DataSourceDesc dsd, @NonNull UUID uuid)
+            throws UnsupportedSchemeException, ResourceBusyException,
+                   NotProvisionedException {
+        Log.v(TAG, "prepareDrm: uuid: " + uuid + " mOnDrmConfigHelper: " + mOnDrmConfigHelper);
+
+        synchronized (mDrmLock) {
+            // only allowing if tied to a protected source; might relax for releasing offline keys
+            if (mDrmInfo == null) {
+                final String msg = "prepareDrm(): Wrong usage: The player must be prepared and "
+                        + "DRM info be retrieved before this call.";
+                Log.e(TAG, msg);
+                throw new IllegalStateException(msg);
+            }
+
+            if (mActiveDrmScheme) {
+                final String msg = "prepareDrm(): Wrong usage: There is already "
+                        + "an active DRM scheme with " + mDrmUUID;
+                Log.e(TAG, msg);
+                throw new IllegalStateException(msg);
+            }
+
+            if (mPrepareDrmInProgress) {
+                final String msg = "prepareDrm(): Wrong usage: There is already "
+                        + "a pending prepareDrm call.";
+                Log.e(TAG, msg);
+                throw new IllegalStateException(msg);
+            }
+
+            if (mDrmProvisioningInProgress) {
+                final String msg = "prepareDrm(): Unexpectd: Provisioning is already in progress.";
+                Log.e(TAG, msg);
+                throw new IllegalStateException(msg);
+            }
+
+            // shouldn't need this; just for safeguard
+            cleanDrmObj();
+
+            mPrepareDrmInProgress = true;
+
+            try {
+                // only creating the DRM object to allow pre-openSession configuration
+                prepareDrm_createDrmStep(uuid);
+            } catch (Exception e) {
+                Log.w(TAG, "prepareDrm(): Exception ", e);
+                mPrepareDrmInProgress = false;
+                throw e;
+            }
+
+            mDrmConfigAllowed = true;
+        }  // synchronized
+
+        // call the callback outside the lock
+        if (mOnDrmConfigHelper != null)  {
+            mOnDrmConfigHelper.onDrmConfig(this, dsd);
+        }
+
+        synchronized (mDrmLock) {
+            mDrmConfigAllowed = false;
+            boolean earlyExit = false;
+
+            try {
+                prepareDrm_openSessionStep(uuid);
+
+                mDrmUUID = uuid;
+                mActiveDrmScheme = true;
+                mPrepareDrmInProgress = false;
+            } catch (IllegalStateException e) {
+                final String msg = "prepareDrm(): Wrong usage: The player must be "
+                        + "in the prepared state to call prepareDrm().";
+                Log.e(TAG, msg);
+                earlyExit = true;
+                mPrepareDrmInProgress = false;
+                throw new IllegalStateException(msg);
+            } catch (NotProvisionedException e) {
+                Log.w(TAG, "prepareDrm: NotProvisionedException", e);
+                throw e;
+            } catch (Exception e) {
+                Log.e(TAG, "prepareDrm: Exception " + e);
+                earlyExit = true;
+                mPrepareDrmInProgress = false;
+                throw e;
+            } finally {
+                if (earlyExit) {  // clean up object if didn't succeed
+                    cleanDrmObj();
+                }
+            }  // finally
+        }  // synchronized
+    }
 
     /**
-     * Releases the DRM session
+     * Releases the DRM session for the given data source
      * <p>
      * The player has to have an active DRM session and be in stopped, or prepared
      * state before this call is made.
-     * A {@code reset()} call will release the DRM session implicitly.
+     * A {@link #reset()} call will release the DRM session implicitly.
+     *
+     * @param dsd The DRM protected data source
      *
      * @throws NoDrmSchemeException if there is no active DRM session to release
      */
     // This is a synchronous call.
-    public abstract void releaseDrm()
-            throws NoDrmSchemeException;
+    public void releaseDrm(@NonNull DataSourceDesc dsd)
+            throws NoDrmSchemeException {
+        // TODO: this implementation only works when dsd is the only data source
+        synchronized (mDrmLock) {
+            Log.v(TAG, "releaseDrm:");
+
+            if (!mActiveDrmScheme) {
+                Log.e(TAG, "releaseDrm(): No active DRM scheme to release.");
+                throw new NoDrmSchemeException(
+                        "releaseDrm: No active DRM scheme to release.");
+            }
+
+            try {
+                // we don't have the player's state in this layer. The below call raises
+                // exception if we're in a non-stopped/prepared state.
+
+                // for cleaning native/mediaserver crypto object
+                native_releaseDrm();
+
+                // for cleaning client-side MediaDrm object; only called if above has succeeded
+                cleanDrmObj();
+
+                mActiveDrmScheme = false;
+            } catch (IllegalStateException e) {
+                Log.w(TAG, "releaseDrm: Exception ", e);
+                throw new IllegalStateException(
+                        "releaseDrm: The player is not in a valid state.");
+            } catch (Exception e) {
+                Log.e(TAG, "releaseDrm: Exception ", e);
+            }
+        }  // synchronized
+    }
+
+    private native void native_releaseDrm();
 
     /**
      * A key request/response exchange occurs between the app and a license server
-     * to obtain or release keys used to decrypt encrypted content.
+     * to obtain or release keys used to decrypt the given data source.
      * <p>
-     * getDrmKeyRequest() is used to obtain an opaque key request byte array that is
+     * {@code getDrmKeyRequest()} is used to obtain an opaque key request byte array that is
      * delivered to the license server.  The opaque key request byte array is returned
      * in KeyRequest.data.  The recommended URL to deliver the key request to is
-     * returned in KeyRequest.defaultUrl.
+     * returned in {@code KeyRequest.defaultUrl}.
      * <p>
      * After the app has received the key request response from the server,
      * it should deliver to the response to the DRM engine plugin using the method
-     * {@link #provideDrmKeyResponse}.
+     * {@link #provideDrmKeyResponse(DataSourceDesc, byte[], byte[])}.
+     *
+     * @param dsd the DRM protected data source
      *
      * @param keySetId is the key-set identifier of the offline keys being released when keyType is
      * {@link MediaDrm#KEY_TYPE_RELEASE}. It should be set to null for other key requests, when
@@ -1825,24 +3626,67 @@
      * @throws NoDrmSchemeException if there is no active DRM session
      */
     @NonNull
-    public abstract MediaDrm.KeyRequest getDrmKeyRequest(
+    public MediaDrm.KeyRequest getDrmKeyRequest(
+            @NonNull DataSourceDesc dsd,
             @Nullable byte[] keySetId, @Nullable byte[] initData,
             @Nullable String mimeType, @MediaDrm.KeyType int keyType,
             @Nullable Map<String, String> optionalParameters)
-            throws NoDrmSchemeException;
+            throws NoDrmSchemeException {
+        // TODO: this implementation only works when dsd is the only data source
+        Log.v(TAG, "getDrmKeyRequest: "
+                + " keySetId: " + keySetId + " initData:" + initData + " mimeType: " + mimeType
+                + " keyType: " + keyType + " optionalParameters: " + optionalParameters);
+
+        synchronized (mDrmLock) {
+            if (!mActiveDrmScheme) {
+                Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException");
+                throw new NoDrmSchemeException(
+                        "getDrmKeyRequest: Has to set a DRM scheme first.");
+            }
+
+            try {
+                byte[] scope = (keyType != MediaDrm.KEY_TYPE_RELEASE)
+                        ? mDrmSessionId :  // sessionId for KEY_TYPE_STREAMING/OFFLINE
+                        keySetId;  // keySetId for KEY_TYPE_RELEASE
+
+                HashMap<String, String> hmapOptionalParameters =
+                                                (optionalParameters != null)
+                                                ? new HashMap<String, String>(optionalParameters) :
+                                                null;
+
+                MediaDrm.KeyRequest request = mDrmObj.getKeyRequest(scope, initData, mimeType,
+                                                              keyType, hmapOptionalParameters);
+                Log.v(TAG, "getDrmKeyRequest:   --> request: " + request);
+
+                return request;
+
+            } catch (NotProvisionedException e) {
+                Log.w(TAG, "getDrmKeyRequest NotProvisionedException: "
+                        + "Unexpected. Shouldn't have reached here.");
+                throw new IllegalStateException("getDrmKeyRequest: Unexpected provisioning error.");
+            } catch (Exception e) {
+                Log.w(TAG, "getDrmKeyRequest Exception " + e);
+                throw e;
+            }
+
+        }  // synchronized
+    }
 
     /**
-     * A key response is received from the license server by the app, then it is
-     * provided to the DRM engine plugin using provideDrmKeyResponse. When the
-     * response is for an offline key request, a key-set identifier is returned that
+     * A key response is received from the license server by the app for the given DRM protected
+     * data source, then provided to the DRM engine plugin using {@code provideDrmKeyResponse}.
+     * <p>
+     * When the response is for an offline key request, a key-set identifier is returned that
      * can be used to later restore the keys to a new session with the method
-     * {@link # restoreDrmKeys}.
+     * {@link #restoreDrmKeys(DataSourceDesc, byte[])}.
      * When the response is for a streaming or release request, null is returned.
      *
-     * @param keySetId When the response is for a release request, keySetId identifies
-     * the saved key associated with the release request (i.e., the same keySetId
-     * passed to the earlier {@ link # getDrmKeyRequest} call. It MUST be null when the
-     * response is for either streaming or offline key requests.
+     * @param dsd the DRM protected data source
+     *
+     * @param keySetId When the response is for a release request, keySetId identifies the saved
+     * key associated with the release request (i.e., the same keySetId passed to the earlier
+     * {@link # getDrmKeyRequest(DataSourceDesc, byte[], byte[], String, int, Map)} call).
+     * It MUST be null when the response is for either streaming or offline key requests.
      *
      * @param response the byte array response from the server
      *
@@ -1851,76 +3695,827 @@
      * server rejected the request
      */
     // This is a synchronous call.
-    public abstract byte[] provideDrmKeyResponse(
+    public byte[] provideDrmKeyResponse(
+            @NonNull DataSourceDesc dsd,
             @Nullable byte[] keySetId, @NonNull byte[] response)
-            throws NoDrmSchemeException, DeniedByServerException;
+            throws NoDrmSchemeException, DeniedByServerException {
+        // TODO: this implementation only works when dsd is the only data source
+        Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId + " response: " + response);
+
+        synchronized (mDrmLock) {
+
+            if (!mActiveDrmScheme) {
+                Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException");
+                throw new NoDrmSchemeException(
+                        "getDrmKeyRequest: Has to set a DRM scheme first.");
+            }
+
+            try {
+                byte[] scope = (keySetId == null)
+                                ? mDrmSessionId :     // sessionId for KEY_TYPE_STREAMING/OFFLINE
+                                keySetId;           // keySetId for KEY_TYPE_RELEASE
+
+                byte[] keySetResult = mDrmObj.provideKeyResponse(scope, response);
+
+                Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId + " response: " + response
+                        + " --> " + keySetResult);
+
+
+                return keySetResult;
+
+            } catch (NotProvisionedException e) {
+                Log.w(TAG, "provideDrmKeyResponse NotProvisionedException: "
+                        + "Unexpected. Shouldn't have reached here.");
+                throw new IllegalStateException("provideDrmKeyResponse: "
+                        + "Unexpected provisioning error.");
+            } catch (Exception e) {
+                Log.w(TAG, "provideDrmKeyResponse Exception " + e);
+                throw e;
+            }
+        }  // synchronized
+    }
 
     /**
-     * Restore persisted offline keys into a new session.  keySetId identifies the
-     * keys to load, obtained from a prior call to {@link #provideDrmKeyResponse}.
+     * Restore persisted offline keys into a new session for the given DRM protected data source.
+     * {@code keySetId} identifies the keys to load, obtained from a prior call to
+     * {@link #provideDrmKeyResponse(DataSourceDesc, byte[], byte[])}.
+     *
+     * @param dsd the DRM protected data source
      *
      * @param keySetId identifies the saved key set to restore
      *
      * @throws NoDrmSchemeException if there is no active DRM session
      */
     // This is a synchronous call.
-    public abstract void restoreDrmKeys(@NonNull byte[] keySetId)
-            throws NoDrmSchemeException;
+    public void restoreDrmKeys(
+            @NonNull DataSourceDesc dsd,
+            @NonNull byte[] keySetId)
+            throws NoDrmSchemeException {
+        // TODO: this implementation only works when dsd is the only data source
+        Log.v(TAG, "restoreDrmKeys: keySetId: " + keySetId);
+
+        synchronized (mDrmLock) {
+            if (!mActiveDrmScheme) {
+                Log.w(TAG, "restoreDrmKeys NoDrmSchemeException");
+                throw new NoDrmSchemeException(
+                        "restoreDrmKeys: Has to set a DRM scheme first.");
+            }
+
+            try {
+                mDrmObj.restoreKeys(mDrmSessionId, keySetId);
+            } catch (Exception e) {
+                Log.w(TAG, "restoreKeys Exception " + e);
+                throw e;
+            }
+        }  // synchronized
+    }
 
     /**
-     * Read a DRM engine plugin String property value, given the property name string.
-     * <p>
+     * Read a DRM engine plugin String property value, given the DRM protected data source
+     * and property name string.
+     *
+     * @param dsd the DRM protected data source
+     *
      * @param propertyName the property name
      *
      * Standard fields names are:
      * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION},
      * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS}
+     *
+     * @throws NoDrmSchemeException if there is no active DRM session
      */
     @NonNull
-    public abstract String getDrmPropertyString(
+    public String getDrmPropertyString(
+            @NonNull DataSourceDesc dsd,
             @NonNull @MediaDrm.StringProperty String propertyName)
-            throws NoDrmSchemeException;
+            throws NoDrmSchemeException {
+        // TODO: this implementation only works when dsd is the only data source
+        Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName);
+
+        String value;
+        synchronized (mDrmLock) {
+
+            if (!mActiveDrmScheme && !mDrmConfigAllowed) {
+                Log.w(TAG, "getDrmPropertyString NoDrmSchemeException");
+                throw new NoDrmSchemeException(
+                        "getDrmPropertyString: Has to prepareDrm() first.");
+            }
+
+            try {
+                value = mDrmObj.getPropertyString(propertyName);
+            } catch (Exception e) {
+                Log.w(TAG, "getDrmPropertyString Exception " + e);
+                throw e;
+            }
+        }  // synchronized
+
+        Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName + " --> value: " + value);
+
+        return value;
+    }
 
     /**
-     * Set a DRM engine plugin String property value.
-     * <p>
+     * Set a DRM engine plugin String property value for the given data source.
+     *
+     * @param dsd the DRM protected data source
      * @param propertyName the property name
      * @param value the property value
      *
      * Standard fields names are:
      * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION},
      * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS}
+     *
+     * @throws NoDrmSchemeException if there is no active DRM session
      */
     // This is a synchronous call.
-    public abstract void setDrmPropertyString(
+    public void setDrmPropertyString(
+            @NonNull DataSourceDesc dsd,
             @NonNull @MediaDrm.StringProperty String propertyName, @NonNull String value)
-            throws NoDrmSchemeException;
+            throws NoDrmSchemeException {
+        // TODO: this implementation only works when dsd is the only data source
+        Log.v(TAG, "setDrmPropertyString: propertyName: " + propertyName + " value: " + value);
+
+        synchronized (mDrmLock) {
+
+            if (!mActiveDrmScheme && !mDrmConfigAllowed) {
+                Log.w(TAG, "setDrmPropertyString NoDrmSchemeException");
+                throw new NoDrmSchemeException(
+                        "setDrmPropertyString: Has to prepareDrm() first.");
+            }
+
+            try {
+                mDrmObj.setPropertyString(propertyName, value);
+            } catch (Exception e) {
+                Log.w(TAG, "setDrmPropertyString Exception " + e);
+                throw e;
+            }
+        }  // synchronized
+    }
 
     /**
      * Encapsulates the DRM properties of the source.
      */
-    public abstract static class DrmInfo {
+    public static final class DrmInfo {
+        private Map<UUID, byte[]> mMapPssh;
+        private UUID[] mSupportedSchemes;
+
         /**
          * Returns the PSSH info of the data source for each supported DRM scheme.
          */
-        public abstract Map<UUID, byte[]> getPssh();
+        public Map<UUID, byte[]> getPssh() {
+            return mMapPssh;
+        }
 
         /**
          * Returns the intersection of the data source and the device DRM schemes.
          * It effectively identifies the subset of the source's DRM schemes which
          * are supported by the device too.
          */
-        public abstract List<UUID> getSupportedSchemes();
+        public List<UUID> getSupportedSchemes() {
+            return Arrays.asList(mSupportedSchemes);
+        }
+
+        private DrmInfo(Map<UUID, byte[]> pssh, UUID[] supportedSchemes) {
+            mMapPssh = pssh;
+            mSupportedSchemes = supportedSchemes;
+        }
+
+        private DrmInfo(PlayerMessage msg) {
+            Log.v(TAG, "DrmInfo(" + msg + ")");
+
+            Iterator<Value> in = msg.getValuesList().iterator();
+            byte[] pssh = in.next().getBytesValue().toByteArray();
+
+            Log.v(TAG, "DrmInfo() PSSH: " + arrToHex(pssh));
+            mMapPssh = parsePSSH(pssh, pssh.length);
+            Log.v(TAG, "DrmInfo() PSSH: " + mMapPssh);
+
+            int supportedDRMsCount = in.next().getInt32Value();
+            mSupportedSchemes = new UUID[supportedDRMsCount];
+            for (int i = 0; i < supportedDRMsCount; i++) {
+                byte[] uuid = new byte[16];
+                in.next().getBytesValue().copyTo(uuid, 0);
+
+                mSupportedSchemes[i] = bytesToUUID(uuid);
+
+                Log.v(TAG, "DrmInfo() supportedScheme[" + i + "]: " + mSupportedSchemes[i]);
+            }
+
+            Log.v(TAG, "DrmInfo() psshsize: " + pssh.length
+                    + " supportedDRMsCount: " + supportedDRMsCount);
+        }
+
+        private DrmInfo makeCopy() {
+            return new DrmInfo(this.mMapPssh, this.mSupportedSchemes);
+        }
+
+        private String arrToHex(byte[] bytes) {
+            String out = "0x";
+            for (int i = 0; i < bytes.length; i++) {
+                out += String.format("%02x", bytes[i]);
+            }
+
+            return out;
+        }
+
+        private UUID bytesToUUID(byte[] uuid) {
+            long msb = 0, lsb = 0;
+            for (int i = 0; i < 8; i++) {
+                msb |= (((long) uuid[i]     & 0xff) << (8 * (7 - i)));
+                lsb |= (((long) uuid[i + 8] & 0xff) << (8 * (7 - i)));
+            }
+
+            return new UUID(msb, lsb);
+        }
+
+        private Map<UUID, byte[]> parsePSSH(byte[] pssh, int psshsize) {
+            Map<UUID, byte[]> result = new HashMap<UUID, byte[]>();
+
+            final int uuidSize = 16;
+            final int dataLenSize = 4;
+
+            int len = psshsize;
+            int numentries = 0;
+            int i = 0;
+
+            while (len > 0) {
+                if (len < uuidSize) {
+                    Log.w(TAG, String.format("parsePSSH: len is too short to parse "
+                                             + "UUID: (%d < 16) pssh: %d", len, psshsize));
+                    return null;
+                }
+
+                byte[] subset = Arrays.copyOfRange(pssh, i, i + uuidSize);
+                UUID uuid = bytesToUUID(subset);
+                i += uuidSize;
+                len -= uuidSize;
+
+                // get data length
+                if (len < 4) {
+                    Log.w(TAG, String.format("parsePSSH: len is too short to parse "
+                                             + "datalen: (%d < 4) pssh: %d", len, psshsize));
+                    return null;
+                }
+
+                subset = Arrays.copyOfRange(pssh, i, i + dataLenSize);
+                int datalen = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN)
+                        ? ((subset[3] & 0xff) << 24) | ((subset[2] & 0xff) << 16)
+                        | ((subset[1] & 0xff) <<  8) |  (subset[0] & 0xff)        :
+                        ((subset[0] & 0xff) << 24) | ((subset[1] & 0xff) << 16)
+                        | ((subset[2] & 0xff) <<  8) |  (subset[3] & 0xff);
+                i += dataLenSize;
+                len -= dataLenSize;
+
+                if (len < datalen) {
+                    Log.w(TAG, String.format("parsePSSH: len is too short to parse "
+                                             + "data: (%d < %d) pssh: %d", len, datalen, psshsize));
+                    return null;
+                }
+
+                byte[] data = Arrays.copyOfRange(pssh, i, i + datalen);
+
+                // skip the data
+                i += datalen;
+                len -= datalen;
+
+                Log.v(TAG, String.format("parsePSSH[%d]: <%s, %s> pssh: %d",
+                                         numentries, uuid, arrToHex(data), psshsize));
+                numentries++;
+                result.put(uuid, data);
+            }
+
+            return result;
+        }
     };  // DrmInfo
 
     /**
-     * Thrown when a DRM method is called before preparing a DRM scheme through prepareDrm().
+     * Thrown when a DRM method is called before preparing a DRM scheme through
+     * {@link MediaPlayer2#prepareDrm(DataSourceDesc, UUID)}.
      * Extends MediaDrm.MediaDrmException
      */
-    public abstract static class NoDrmSchemeException extends MediaDrmException {
-          protected NoDrmSchemeException(String detailMessage) {
-              super(detailMessage);
-          }
+    public static final class NoDrmSchemeException extends MediaDrmException {
+        public NoDrmSchemeException(String detailMessage) {
+            super(detailMessage);
+        }
+    }
+
+    private native void native_prepareDrm(@NonNull byte[] uuid, @NonNull byte[] drmSessionId);
+
+    // Modular DRM helpers
+
+    private void prepareDrm_createDrmStep(@NonNull UUID uuid)
+            throws UnsupportedSchemeException {
+        Log.v(TAG, "prepareDrm_createDrmStep: UUID: " + uuid);
+
+        try {
+            mDrmObj = new MediaDrm(uuid);
+            Log.v(TAG, "prepareDrm_createDrmStep: Created mDrmObj=" + mDrmObj);
+        } catch (Exception e) { // UnsupportedSchemeException
+            Log.e(TAG, "prepareDrm_createDrmStep: MediaDrm failed with " + e);
+            throw e;
+        }
+    }
+
+    private void prepareDrm_openSessionStep(@NonNull UUID uuid)
+            throws NotProvisionedException, ResourceBusyException {
+        Log.v(TAG, "prepareDrm_openSessionStep: uuid: " + uuid);
+
+        // TODO: don't need an open session for a future specialKeyReleaseDrm mode but we should do
+        // it anyway so it raises provisioning error if needed. We'd rather handle provisioning
+        // at prepareDrm/openSession rather than getDrmKeyRequest/provideDrmKeyResponse
+        try {
+            mDrmSessionId = mDrmObj.openSession();
+            Log.v(TAG, "prepareDrm_openSessionStep: mDrmSessionId=" + mDrmSessionId);
+
+            // Sending it down to native/mediaserver to create the crypto object
+            // This call could simply fail due to bad player state, e.g., after play().
+            native_prepareDrm(getByteArrayFromUUID(uuid), mDrmSessionId);
+            Log.v(TAG, "prepareDrm_openSessionStep: native_prepareDrm/Crypto succeeded");
+
+        } catch (Exception e) { //ResourceBusyException, NotProvisionedException
+            Log.e(TAG, "prepareDrm_openSessionStep: open/crypto failed with " + e);
+            throw e;
+        }
+    }
+
+    // Instantiated from the native side
+    @SuppressWarnings("unused")
+    private static class StreamEventCallback extends AudioTrack.StreamEventCallback {
+        public long mJAudioTrackPtr;
+        public long mNativeCallbackPtr;
+        public long mUserDataPtr;
+
+        StreamEventCallback(long jAudioTrackPtr, long nativeCallbackPtr, long userDataPtr) {
+            super();
+            mJAudioTrackPtr = jAudioTrackPtr;
+            mNativeCallbackPtr = nativeCallbackPtr;
+            mUserDataPtr = userDataPtr;
+        }
+
+        @Override
+        public void onTearDown(AudioTrack track) {
+            native_stream_event_onTearDown(mNativeCallbackPtr, mUserDataPtr);
+        }
+
+        @Override
+        public void onPresentationEnded(AudioTrack track) {
+            native_stream_event_onStreamPresentationEnd(mNativeCallbackPtr, mUserDataPtr);
+        }
+
+        @Override
+        public void onDataRequest(AudioTrack track, int size) {
+            native_stream_event_onStreamDataRequest(
+                    mJAudioTrackPtr, mNativeCallbackPtr, mUserDataPtr);
+        }
+    }
+
+    private class ProvisioningThread extends Thread {
+        public static final int TIMEOUT_MS = 60000;
+
+        private final DataSourceDesc mDSD;
+        private UUID mUuid;
+        private String mUrlStr;
+        private Object mDrmLock;
+        private MediaPlayer2 mMediaPlayer;
+        private int mStatus;
+        public  int status() {
+            return mStatus;
+        }
+
+        public ProvisioningThread(MediaDrm.ProvisionRequest request,
+                DataSourceDesc dsd,
+                UUID uuid, MediaPlayer2 mediaPlayer) {
+            // lock is held by the caller
+            mDSD = dsd;
+            mDrmLock = mediaPlayer.mDrmLock;
+            mMediaPlayer = mediaPlayer;
+
+            mUrlStr = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData());
+            mUuid = uuid;
+
+            mStatus = PREPARE_DRM_STATUS_PREPARATION_ERROR;
+
+            Log.v(TAG, "handleProvisioninig: Thread is initialised url: " + mUrlStr);
+        }
+
+        public void run() {
+
+            byte[] response = null;
+            boolean provisioningSucceeded = false;
+            try {
+                URL url = new URL(mUrlStr);
+                final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+                try {
+                    connection.setRequestMethod("POST");
+                    connection.setDoOutput(false);
+                    connection.setDoInput(true);
+                    connection.setConnectTimeout(TIMEOUT_MS);
+                    connection.setReadTimeout(TIMEOUT_MS);
+
+                    connection.connect();
+                    response = readInputStreamFully(connection.getInputStream());
+
+                    Log.v(TAG, "handleProvisioninig: Thread run: response "
+                            + response.length + " " + response);
+                } catch (Exception e) {
+                    mStatus = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR;
+                    Log.w(TAG, "handleProvisioninig: Thread run: connect " + e + " url: " + url);
+                } finally {
+                    connection.disconnect();
+                }
+            } catch (Exception e)   {
+                mStatus = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR;
+                Log.w(TAG, "handleProvisioninig: Thread run: openConnection " + e);
+            }
+
+            if (response != null) {
+                try {
+                    mDrmObj.provideProvisionResponse(response);
+                    Log.v(TAG, "handleProvisioninig: Thread run: "
+                            + "provideProvisionResponse SUCCEEDED!");
+
+                    provisioningSucceeded = true;
+                } catch (Exception e) {
+                    mStatus = PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR;
+                    Log.w(TAG, "handleProvisioninig: Thread run: "
+                            + "provideProvisionResponse " + e);
+                }
+            }
+
+            boolean succeeded = false;
+
+            synchronized (mDrmLock) {
+                // continuing with prepareDrm
+                if (provisioningSucceeded) {
+                    succeeded = mMediaPlayer.resumePrepareDrm(mUuid);
+                    mStatus = (succeeded)
+                            ? PREPARE_DRM_STATUS_SUCCESS :
+                            PREPARE_DRM_STATUS_PREPARATION_ERROR;
+                }
+                mMediaPlayer.mDrmProvisioningInProgress = false;
+                mMediaPlayer.mPrepareDrmInProgress = false;
+                if (!succeeded) {
+                    cleanDrmObj();  // cleaning up if it hasn't gone through while in the lock
+                }
+            }  // synchronized
+
+            // calling the callback outside the lock
+            sendDrmEvent(new DrmEventNotifier() {
+                @Override
+                public void notify(DrmEventCallback callback) {
+                    callback.onDrmPrepared(
+                            mMediaPlayer, mDSD, mStatus);
+                }
+            });
+
+            synchronized (mTaskLock) {
+                if (mCurrentTask != null
+                        && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE_DRM
+                        && mCurrentTask.mNeedToWaitForEventToComplete) {
+                    mCurrentTask = null;
+                    processPendingTask_l();
+                }
+            }
+        }
+
+        /**
+         * Returns a byte[] containing the remainder of 'in', closing it when done.
+         */
+        private byte[] readInputStreamFully(InputStream in) throws IOException {
+            try {
+                return readInputStreamFullyNoClose(in);
+            } finally {
+                in.close();
+            }
+        }
+
+        /**
+         * Returns a byte[] containing the remainder of 'in'.
+         */
+        private byte[] readInputStreamFullyNoClose(InputStream in) throws IOException {
+            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+            byte[] buffer = new byte[1024];
+            int count;
+            while ((count = in.read(buffer)) != -1) {
+                bytes.write(buffer, 0, count);
+            }
+            return bytes.toByteArray();
+        }
+    }  // ProvisioningThread
+
+    private int handleProvisioninig(DataSourceDesc dsd, UUID uuid) {
+        synchronized (mDrmLock) {
+            if (mDrmProvisioningInProgress) {
+                Log.e(TAG, "handleProvisioninig: Unexpected mDrmProvisioningInProgress");
+                return PREPARE_DRM_STATUS_PREPARATION_ERROR;
+            }
+
+            MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest();
+            if (provReq == null) {
+                Log.e(TAG, "handleProvisioninig: getProvisionRequest returned null.");
+                return PREPARE_DRM_STATUS_PREPARATION_ERROR;
+            }
+
+            Log.v(TAG, "handleProvisioninig provReq "
+                    + " data: " + provReq.getData() + " url: " + provReq.getDefaultUrl());
+
+            // networking in a background thread
+            mDrmProvisioningInProgress = true;
+
+            mDrmProvisioningThread = new ProvisioningThread(provReq, dsd, uuid, this);
+            mDrmProvisioningThread.start();
+
+            return PREPARE_DRM_STATUS_SUCCESS;
+        }
+    }
+
+    private boolean resumePrepareDrm(UUID uuid) {
+        Log.v(TAG, "resumePrepareDrm: uuid: " + uuid);
+
+        // mDrmLock is guaranteed to be held
+        boolean success = false;
+        try {
+            // resuming
+            prepareDrm_openSessionStep(uuid);
+
+            mDrmUUID = uuid;
+            mActiveDrmScheme = true;
+
+            success = true;
+        } catch (Exception e) {
+            Log.w(TAG, "handleProvisioninig: Thread run native_prepareDrm resume failed with " + e);
+            // mDrmObj clean up is done by the caller
+        }
+
+        return success;
+    }
+
+    private void resetDrmState() {
+        synchronized (mDrmLock) {
+            Log.v(TAG, "resetDrmState:"
+                    + " mDrmInfo=" + mDrmInfo
+                    + " mDrmProvisioningThread=" + mDrmProvisioningThread
+                    + " mPrepareDrmInProgress=" + mPrepareDrmInProgress
+                    + " mActiveDrmScheme=" + mActiveDrmScheme);
+
+            mDrmInfoResolved = false;
+            mDrmInfo = null;
+
+            if (mDrmProvisioningThread != null) {
+                // timeout; relying on HttpUrlConnection
+                try {
+                    mDrmProvisioningThread.join();
+                } catch (InterruptedException e) {
+                    Log.w(TAG, "resetDrmState: ProvThread.join Exception " + e);
+                }
+                mDrmProvisioningThread = null;
+            }
+
+            mPrepareDrmInProgress = false;
+            mActiveDrmScheme = false;
+
+            cleanDrmObj();
+        }  // synchronized
+    }
+
+    private void cleanDrmObj() {
+        // the caller holds mDrmLock
+        Log.v(TAG, "cleanDrmObj: mDrmObj=" + mDrmObj + " mDrmSessionId=" + mDrmSessionId);
+
+        if (mDrmSessionId != null)    {
+            mDrmObj.closeSession(mDrmSessionId);
+            mDrmSessionId = null;
+        }
+        if (mDrmObj != null) {
+            mDrmObj.release();
+            mDrmObj = null;
+        }
+    }
+
+    private static byte[] getByteArrayFromUUID(@NonNull UUID uuid) {
+        long msb = uuid.getMostSignificantBits();
+        long lsb = uuid.getLeastSignificantBits();
+
+        byte[] uuidBytes = new byte[16];
+        for (int i = 0; i < 8; ++i) {
+            uuidBytes[i] = (byte) (msb >>> (8 * (7 - i)));
+            uuidBytes[8 + i] = (byte) (lsb >>> (8 * (7 - i)));
+        }
+
+        return uuidBytes;
+    }
+
+    // Modular DRM end
+
+    private static class TimedTextUtil {
+        // These keys must be in sync with the keys in TextDescription2.h
+        private static final int KEY_START_TIME                     = 7; // int
+        private static final int KEY_STRUCT_TEXT_POS               = 14; // TextPos
+        private static final int KEY_STRUCT_TEXT                   = 16; // Text
+        private static final int KEY_GLOBAL_SETTING               = 101;
+        private static final int KEY_LOCAL_SETTING                = 102;
+
+        private static TimedText parsePlayerMessage(PlayerMessage playerMsg) {
+            if (playerMsg.getValuesCount() == 0) {
+                return null;
+            }
+
+            String textChars = null;
+            Rect textBounds = null;
+            Iterator<Value> in = playerMsg.getValuesList().iterator();
+            int type = in.next().getInt32Value();
+            if (type == KEY_LOCAL_SETTING) {
+                type = in.next().getInt32Value();
+                if (type != KEY_START_TIME) {
+                    return null;
+                }
+                int startTimeMs = in.next().getInt32Value();
+
+                type = in.next().getInt32Value();
+                if (type != KEY_STRUCT_TEXT) {
+                    return null;
+                }
+
+                byte[] text = in.next().getBytesValue().toByteArray();
+                if (text == null || text.length == 0) {
+                    textChars = null;
+                } else {
+                    textChars = new String(text);
+                }
+
+            } else if (type != KEY_GLOBAL_SETTING) {
+                Log.w(TAG, "Invalid timed text key found: " + type);
+                return null;
+            }
+            if (in.hasNext()) {
+                type = in.next().getInt32Value();
+                if (type == KEY_STRUCT_TEXT_POS) {
+                    int top = in.next().getInt32Value();
+                    int left = in.next().getInt32Value();
+                    int bottom = in.next().getInt32Value();
+                    int right = in.next().getInt32Value();
+                    textBounds = new Rect(left, top, right, bottom);
+                }
+            }
+            return new TimedText(textChars, textBounds);
+        }
+    }
+
+    private Object addTask(Task task) {
+        synchronized (mTaskLock) {
+            mPendingTasks.add(task);
+            processPendingTask_l();
+        }
+        return task;
+    }
+
+    @GuardedBy("mTaskLock")
+    private void processPendingTask_l() {
+        if (mCurrentTask != null) {
+            return;
+        }
+        if (!mPendingTasks.isEmpty()) {
+            Task task = mPendingTasks.remove(0);
+            mCurrentTask = task;
+            mTaskHandler.post(task);
+        }
+    }
+
+    private abstract class Task implements Runnable {
+        private final int mMediaCallType;
+        private final boolean mNeedToWaitForEventToComplete;
+        private DataSourceDesc mDSD;
+
+        Task(int mediaCallType, boolean needToWaitForEventToComplete) {
+            mMediaCallType = mediaCallType;
+            mNeedToWaitForEventToComplete = needToWaitForEventToComplete;
+        }
+
+        abstract void process() throws IOException, NoDrmSchemeException;
+
+        @Override
+        public void run() {
+            int status = CALL_STATUS_NO_ERROR;
+            try {
+                if (mMediaCallType != CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED
+                        && getState() == PLAYER_STATE_ERROR) {
+                    status = CALL_STATUS_INVALID_OPERATION;
+                } else {
+                    if (mMediaCallType == CALL_COMPLETED_SEEK_TO) {
+                        synchronized (mTaskLock) {
+                            if (!mPendingTasks.isEmpty()) {
+                                Task nextTask = mPendingTasks.get(0);
+                                if (nextTask.mMediaCallType == mMediaCallType) {
+                                    throw new CommandSkippedException(
+                                            "consecutive seekTo is skipped except last one");
+                                }
+                            }
+                        }
+                    }
+                    process();
+                }
+            } catch (IllegalStateException e) {
+                status = CALL_STATUS_INVALID_OPERATION;
+            } catch (IllegalArgumentException e) {
+                status = CALL_STATUS_BAD_VALUE;
+            } catch (SecurityException e) {
+                status = CALL_STATUS_PERMISSION_DENIED;
+            } catch (IOException e) {
+                status = CALL_STATUS_ERROR_IO;
+            } catch (NoDrmSchemeException e) {
+                status = CALL_STATUS_NO_DRM_SCHEME;
+            } catch (CommandSkippedException e) {
+                status = CALL_STATUS_SKIPPED;
+            } catch (Exception e) {
+                status = CALL_STATUS_ERROR_UNKNOWN;
+            }
+            mDSD = getCurrentDataSource();
+
+            if (mMediaCallType != CALL_COMPLETED_SEEK_TO) {
+                synchronized (mTaskLock) {
+                    mIsPreviousCommandSeekTo = false;
+                }
+            }
+
+            // TODO: Make native implementations asynchronous and let them send notifications.
+            if (!mNeedToWaitForEventToComplete || status != CALL_STATUS_NO_ERROR) {
+
+                sendCompleteNotification(status);
+
+                synchronized (mTaskLock) {
+                    mCurrentTask = null;
+                    processPendingTask_l();
+                }
+            }
+        }
+
+        private void sendCompleteNotification(int status) {
+            // In {@link #notifyWhenCommandLabelReached} case, a separate callback
+            // {@link #onCommandLabelReached} is already called in {@code process()}.
+            // CALL_COMPLETED_PREPARE_DRM is sent via DrmEventCallback#onDrmPrepared
+            if (mMediaCallType == CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED
+                    || mMediaCallType == CALL_COMPLETED_PREPARE_DRM) {
+                return;
+            }
+            sendEvent(new EventNotifier() {
+                @Override
+                public void notify(EventCallback callback) {
+                    callback.onCallCompleted(
+                            MediaPlayer2.this, mDSD, mMediaCallType, status);
+                }
+            });
+        }
+    };
+
+    private final class CommandSkippedException extends RuntimeException {
+        CommandSkippedException(String detailMessage) {
+            super(detailMessage);
+        }
+    };
+
+    private final class SourceInfo {
+        final DataSourceDesc mDSD;
+        final long mId = mSrcIdGenerator.getAndIncrement();
+        AtomicInteger mBufferedPercentage = new AtomicInteger(0);
+
+        // m*AsNextSource (below) only applies to pending data sources in the playlist;
+        // the meanings of mCurrentSourceInfo.{mStateAsNextSource,mPlayPendingAsNextSource}
+        // are undefined.
+        int mStateAsNextSource = NEXT_SOURCE_STATE_INIT;
+        boolean mPlayPendingAsNextSource = false;
+
+        SourceInfo(DataSourceDesc dsd) {
+            this.mDSD = dsd;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("%s(%d)", SourceInfo.class.getName(), mId);
+        }
+
+    }
+
+    private SourceInfo getSourceInfoById(long srcId) {
+        synchronized (mSrcLock) {
+            if (isCurrentSource(srcId)) {
+                return mCurrentSourceInfo;
+            }
+            if (isNextSource(srcId)) {
+                return mNextSourceInfos.peek();
+            }
+        }
+        return null;
+    }
+
+    private boolean isCurrentSource(long srcId) {
+        synchronized (mSrcLock) {
+            return mCurrentSourceInfo != null && mCurrentSourceInfo.mId == srcId;
+        }
+    }
+
+    private boolean isNextSource(long srcId) {
+        SourceInfo nextSourceInfo = mNextSourceInfos.peek();
+        return nextSourceInfo != null && nextSourceInfo.mId == srcId;
     }
 
     public static final class MetricsConstants {
@@ -2015,4 +4610,19 @@
         public static final String ERROR_CODE = "android.media.mediaplayer.errcode";
 
     }
+
+    private void keepAudioSessionIdAlive(int sessionId) {
+        synchronized (mSessionIdLock) {
+            if (mDummyAudioTrack != null) {
+                if (mDummyAudioTrack.getAudioSessionId() == sessionId) {
+                    return;
+                }
+                mDummyAudioTrack.release();
+            }
+            // TODO: parameters can be optimized
+            mDummyAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100,
+                    AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, 2,
+                    AudioTrack.MODE_STATIC, sessionId);
+        }
+    }
 }
diff --git a/media/java/android/media/MediaPlayer2Impl.java b/media/java/android/media/MediaPlayer2Impl.java
deleted file mode 100644
index babf24e..0000000
--- a/media/java/android/media/MediaPlayer2Impl.java
+++ /dev/null
@@ -1,2980 +0,0 @@
-/*
- * 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.CallbackExecutor;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.ActivityManager.RunningAppProcessInfo;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.graphics.Rect;
-import android.media.MediaPlayer2Proto.PlayerMessage;
-import android.media.MediaPlayer2Proto.Value;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PersistableBundle;
-import android.os.PowerManager;
-import android.util.Log;
-import android.util.Pair;
-import android.view.Surface;
-import android.view.SurfaceHolder;
-
-import com.android.framework.protobuf.InvalidProtocolBufferException;
-import com.android.internal.annotations.GuardedBy;
-
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.ref.WeakReference;
-import java.net.HttpCookie;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.nio.ByteOrder;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Queue;
-import java.util.UUID;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.Executor;
-import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-
-/**
- * @hide
- */
-public final class MediaPlayer2Impl extends MediaPlayer2 {
-    static {
-        System.loadLibrary("media2_jni");
-        native_init();
-    }
-
-    private static final int NEXT_SOURCE_STATE_ERROR = -1;
-    private static final int NEXT_SOURCE_STATE_INIT = 0;
-    private static final int NEXT_SOURCE_STATE_PREPARING = 1;
-    private static final int NEXT_SOURCE_STATE_PREPARED = 2;
-
-    private final static String TAG = "MediaPlayer2Impl";
-
-    private Context mContext;
-
-    private long mNativeContext;  // accessed by native methods
-    private long mNativeSurfaceTexture;  // accessed by native methods
-    private int mListenerContext;  // accessed by native methods
-    private SurfaceHolder mSurfaceHolder;
-    private PowerManager.WakeLock mWakeLock = null;
-    private boolean mScreenOnWhilePlaying;
-    private boolean mStayAwake;
-
-    private final Object mSrcLock = new Object();
-    //--- guarded by |mSrcLock| start
-    private SourceInfo mCurrentSourceInfo;
-    private final Queue<SourceInfo> mNextSourceInfos = new ConcurrentLinkedQueue<>();
-    //--- guarded by |mSrcLock| end
-    private final AtomicLong mSrcIdGenerator = new AtomicLong(0);
-
-    private volatile float mVolume = 1.0f;
-    private VideoSize mVideoSize = new VideoSize(0, 0);
-
-    // Modular DRM
-    private final Object mDrmLock = new Object();
-    //--- guarded by |mDrmLock| start
-    private UUID mDrmUUID;
-    private DrmInfoImpl mDrmInfoImpl;
-    private MediaDrm mDrmObj;
-    private byte[] mDrmSessionId;
-    private boolean mDrmInfoResolved;
-    private boolean mActiveDrmScheme;
-    private boolean mDrmConfigAllowed;
-    private boolean mDrmProvisioningInProgress;
-    private boolean mPrepareDrmInProgress;
-    private ProvisioningThread mDrmProvisioningThread;
-    //--- guarded by |mDrmLock| end
-
-    private HandlerThread mHandlerThread;
-    private final TaskHandler mTaskHandler;
-    private final Object mTaskLock = new Object();
-    @GuardedBy("mTaskLock")
-    private final List<Task> mPendingTasks = new LinkedList<>();
-    @GuardedBy("mTaskLock")
-    private Task mCurrentTask;
-
-    @GuardedBy("mTaskLock")
-    boolean mIsPreviousCommandSeekTo = false;
-    // |mPreviousSeekPos| and |mPreviousSeekMode| are valid only when |mIsPreviousCommandSeekTo|
-    // is true, and they are accessed on |mHandlerThread| only.
-    long mPreviousSeekPos = -1;
-    int mPreviousSeekMode = SEEK_PREVIOUS_SYNC;
-
-    @GuardedBy("this")
-    private boolean mReleased;
-
-    /**
-     * Default constructor.
-     * <p>When done with the MediaPlayer2Impl, you should call {@link #close()},
-     * to free the resources. If not released, too many MediaPlayer2Impl instances may
-     * result in an exception.</p>
-     */
-    public MediaPlayer2Impl(Context context) {
-        mContext = context;
-        mHandlerThread = new HandlerThread("MediaPlayer2TaskThread");
-        mHandlerThread.start();
-        Looper looper = mHandlerThread.getLooper();
-        mTaskHandler = new TaskHandler(this, looper);
-
-        /* Native setup requires a weak reference to our object.
-         * It's easier to create it here than in C++.
-         */
-        native_setup(new WeakReference<MediaPlayer2Impl>(this));
-    }
-
-    @Override
-    public void close() {
-        super.close();
-        release();
-    }
-
-    @Override
-    public Object play() {
-        return addTask(new Task(CALL_COMPLETED_PLAY, false) {
-            @Override
-            void process() {
-                stayAwake(true);
-                _start();
-            }
-        });
-    }
-
-    private native void _start() throws IllegalStateException;
-
-    @Override
-    public Object prepare() {
-        return addTask(new Task(CALL_COMPLETED_PREPARE, true) {
-            @Override
-            void process() {
-                _prepare();
-            }
-        });
-    }
-
-    public native void _prepare();
-
-    @Override
-    public Object pause() {
-        return addTask(new Task(CALL_COMPLETED_PAUSE, false) {
-            @Override
-            void process() {
-                stayAwake(false);
-
-                _pause();
-            }
-        });
-    }
-
-    private native void _pause() throws IllegalStateException;
-
-    @Override
-    public Object skipToNext() {
-        return addTask(new Task(CALL_COMPLETED_SKIP_TO_NEXT, false) {
-            @Override
-            void process() {
-                if (getState() == PLAYER_STATE_PLAYING) {
-                    pause();
-                }
-                playNextDataSource();
-            }
-        });
-    }
-
-    @Override
-    public native long getCurrentPosition();
-
-    @Override
-    public native long getDuration();
-
-    @Override
-    public long getBufferedPosition() {
-        // Use cached buffered percent for now.
-        int bufferedPercentage;
-        synchronized (mSrcLock) {
-            if (mCurrentSourceInfo == null) {
-                bufferedPercentage = 0;
-            } else {
-                bufferedPercentage = mCurrentSourceInfo.mBufferedPercentage.get();
-            }
-        }
-        return getDuration() * bufferedPercentage / 100;
-    }
-
-    @Override
-    public @MediaPlayer2State int getState() {
-        return native_getState();
-    }
-
-    private native int native_getState();
-
-    @Override
-    public Object setAudioAttributes(@NonNull AudioAttributes attributes) {
-        return addTask(new Task(CALL_COMPLETED_SET_AUDIO_ATTRIBUTES, false) {
-            @Override
-            void process() {
-                if (attributes == null) {
-                    final String msg = "Cannot set AudioAttributes to null";
-                    throw new IllegalArgumentException(msg);
-                }
-                native_setAudioAttributes(attributes);
-            }
-        });
-    }
-
-    @Override
-    public @NonNull AudioAttributes getAudioAttributes() {
-        return native_getAudioAttributes();
-    }
-
-    @Override
-    public Object setDataSource(@NonNull DataSourceDesc dsd) {
-        return addTask(new Task(CALL_COMPLETED_SET_DATA_SOURCE, false) {
-            @Override
-            void process() throws IOException {
-                checkArgument(dsd != null, "the DataSourceDesc cannot be null");
-                int state = getState();
-                if (state != PLAYER_STATE_ERROR && state != PLAYER_STATE_IDLE) {
-                    throw new IllegalStateException("called in wrong state " + state);
-                }
-
-                synchronized (mSrcLock) {
-                    mCurrentSourceInfo = new SourceInfo(dsd);
-                    handleDataSource(true /* isCurrent */, dsd, mCurrentSourceInfo.mId);
-                }
-            }
-        });
-    }
-
-    @Override
-    public Object setNextDataSource(@NonNull DataSourceDesc dsd) {
-        return addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCE, false) {
-            @Override
-            void process() {
-                checkArgument(dsd != null, "the DataSourceDesc cannot be null");
-                synchronized (mSrcLock) {
-                    mNextSourceInfos.clear();
-                    mNextSourceInfos.add(new SourceInfo(dsd));
-                }
-                prepareNextDataSource();
-            }
-        });
-    }
-
-    @Override
-    public Object setNextDataSources(@NonNull List<DataSourceDesc> dsds) {
-        return addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCES, false) {
-            @Override
-            void process() {
-                if (dsds == null || dsds.size() == 0) {
-                    throw new IllegalArgumentException("data source list cannot be null or empty.");
-                }
-                for (DataSourceDesc dsd : dsds) {
-                    if (dsd == null) {
-                        throw new IllegalArgumentException(
-                                "DataSourceDesc in the source list cannot be null.");
-                    }
-                }
-
-                synchronized (mSrcLock) {
-                    mNextSourceInfos.clear();
-                    for (DataSourceDesc dsd : dsds) {
-                        mNextSourceInfos.add(new SourceInfo(dsd));
-                    }
-                }
-                prepareNextDataSource();
-            }
-        });
-    }
-
-    @Override
-    public Object clearNextDataSources() {
-        return addTask(new Task(CALL_COMPLETED_CLEAR_NEXT_DATA_SOURCES, false) {
-            @Override
-            void process() {
-                mNextSourceInfos.clear();
-            }
-        });
-    }
-
-    @Override
-    public DataSourceDesc getCurrentDataSource() {
-        synchronized (mSrcLock) {
-            return mCurrentSourceInfo == null ? null : mCurrentSourceInfo.mDSD;
-        }
-    }
-
-    @Override
-    public Object loopCurrent(boolean loop) {
-        return addTask(new Task(CALL_COMPLETED_LOOP_CURRENT, false) {
-            @Override
-            void process() {
-                setLooping(loop);
-            }
-        });
-    }
-
-    private native void setLooping(boolean looping);
-
-    @Override
-    public Object setPlayerVolume(float volume) {
-        return addTask(new Task(CALL_COMPLETED_SET_PLAYER_VOLUME, false) {
-            @Override
-            void process() {
-                mVolume = volume;
-                native_setVolume(volume);
-            }
-        });
-    }
-
-    private native void native_setVolume(float volume);
-
-    @Override
-    public float getPlayerVolume() {
-        return mVolume;
-    }
-
-    @Override
-    public float getMaxPlayerVolume() {
-        return 1.0f;
-    }
-
-    /* Do not change these values (starting with INVOKE_ID) without updating
-     * their counterparts in include/media/mediaplayer2.h!
-     */
-    private static final int INVOKE_ID_GET_TRACK_INFO = 1;
-    private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE = 2;
-    private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE_FD = 3;
-    private static final int INVOKE_ID_SELECT_TRACK = 4;
-    private static final int INVOKE_ID_DESELECT_TRACK = 5;
-    private static final int INVOKE_ID_GET_SELECTED_TRACK = 7;
-
-    /**
-     * Invoke a generic method on the native player using opaque protocol
-     * buffer message for the request and reply. Both payloads' format is a
-     * convention between the java caller and the native player.
-     *
-     * @param msg PlayerMessage for the extension.
-     *
-     * @return PlayerMessage with the data returned by the
-     * native player.
-     */
-    private PlayerMessage invoke(PlayerMessage msg) {
-        byte[] ret = native_invoke(msg.toByteArray());
-        if (ret == null) {
-            return null;
-        }
-        try {
-            return PlayerMessage.parseFrom(ret);
-        } catch (InvalidProtocolBufferException e) {
-            return null;
-        }
-    }
-
-    private native byte[] native_invoke(byte[] request);
-
-    @Override
-    public Object notifyWhenCommandLabelReached(Object label) {
-        return addTask(new Task(CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED, false) {
-            @Override
-            void process() {
-                sendEvent(new EventNotifier() {
-                    @Override
-                    public void notify(EventCallback callback) {
-                        callback.onCommandLabelReached(
-                                MediaPlayer2Impl.this, label);
-                    }
-                });
-            }
-        });
-    }
-
-    @Override
-    public Object setDisplay(SurfaceHolder sh) {
-        return addTask(new Task(CALL_COMPLETED_SET_DISPLAY, false) {
-            @Override
-            void process() {
-                mSurfaceHolder = sh;
-                Surface surface;
-                if (sh != null) {
-                    surface = sh.getSurface();
-                } else {
-                    surface = null;
-                }
-                native_setVideoSurface(surface);
-                updateSurfaceScreenOn();
-            }
-        });
-    }
-
-    @Override
-    public Object setSurface(Surface surface) {
-        return addTask(new Task(CALL_COMPLETED_SET_SURFACE, false) {
-            @Override
-            void process() {
-                if (mScreenOnWhilePlaying && surface != null) {
-                    Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective for Surface");
-                }
-                mSurfaceHolder = null;
-                native_setVideoSurface(surface);
-                updateSurfaceScreenOn();
-            }
-        });
-    }
-
-    private native void native_setVideoSurface(Surface surface);
-
-    @Override
-    public boolean cancelCommand(Object token) {
-        synchronized (mTaskLock) {
-            return mPendingTasks.remove(token);
-        }
-    }
-
-    @Override
-    public void clearPendingCommands() {
-        synchronized (mTaskLock) {
-            mPendingTasks.clear();
-        }
-    }
-
-    private void handleDataSource(boolean isCurrent, @NonNull DataSourceDesc dsd, long srcId)
-            throws IOException {
-        checkArgument(dsd != null, "the DataSourceDesc cannot be null");
-
-        switch (dsd.getType()) {
-            case DataSourceDesc.TYPE_CALLBACK:
-                handleDataSource(isCurrent,
-                                 srcId,
-                                 dsd.getMedia2DataSource(),
-                                 dsd.getStartPosition(),
-                                 dsd.getEndPosition());
-                break;
-
-            case DataSourceDesc.TYPE_FD:
-                handleDataSource(isCurrent,
-                                 srcId,
-                                 dsd.getFileDescriptor(),
-                                 dsd.getFileDescriptorOffset(),
-                                 dsd.getFileDescriptorLength(),
-                                 dsd.getStartPosition(),
-                                 dsd.getEndPosition());
-                break;
-
-            case DataSourceDesc.TYPE_URI:
-                handleDataSource(isCurrent,
-                                 srcId,
-                                 dsd.getUriContext(),
-                                 dsd.getUri(),
-                                 dsd.getUriHeaders(),
-                                 dsd.getUriCookies(),
-                                 dsd.getStartPosition(),
-                                 dsd.getEndPosition());
-                break;
-
-            default:
-                break;
-        }
-    }
-
-    /**
-     * To provide cookies for the subsequent HTTP requests, you can install your own default cookie
-     * handler and use other variants of setDataSource APIs instead. Alternatively, you can use
-     * this API to pass the cookies as a list of HttpCookie. If the app has not installed
-     * a CookieHandler already, this API creates a CookieManager and populates its CookieStore with
-     * the provided cookies. If the app has installed its own handler already, this API requires the
-     * handler to be of CookieManager type such that the API can update the manager’s CookieStore.
-     *
-     * <p><strong>Note</strong> that the cross domain redirection is allowed by default,
-     * but that can be changed with key/value pairs through the headers parameter with
-     * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to
-     * disallow or allow cross domain redirection.
-     *
-     * @throws IllegalArgumentException if cookies are provided and the installed handler is not
-     *                                  a CookieManager
-     * @throws IllegalStateException    if it is called in an invalid state
-     * @throws NullPointerException     if context or uri is null
-     * @throws IOException              if uri has a file scheme and an I/O error occurs
-     */
-    private void handleDataSource(
-            boolean isCurrent, long srcId,
-            @NonNull Context context, @NonNull Uri uri,
-            @Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies,
-            long startPos, long endPos)
-            throws IOException {
-        // The context and URI usually belong to the calling user. Get a resolver for that user.
-        final ContentResolver resolver = context.getContentResolver();
-        final String scheme = uri.getScheme();
-        if (ContentResolver.SCHEME_FILE.equals(scheme)) {
-            handleDataSource(isCurrent, srcId, uri.getPath(), null, null, startPos, endPos);
-            return;
-        }
-
-        final int ringToneType = RingtoneManager.getDefaultType(uri);
-        try {
-            AssetFileDescriptor afd;
-            // Try requested Uri locally first
-            if (ContentResolver.SCHEME_CONTENT.equals(scheme) && ringToneType != -1) {
-                afd = RingtoneManager.openDefaultRingtoneUri(context, uri);
-                if (attemptDataSource(isCurrent, srcId, afd, startPos, endPos)) {
-                    return;
-                }
-                final Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri(
-                        context, ringToneType);
-                afd = resolver.openAssetFileDescriptor(actualUri, "r");
-            } else {
-                afd = resolver.openAssetFileDescriptor(uri, "r");
-            }
-            if (attemptDataSource(isCurrent, srcId, afd, startPos, endPos)) {
-                return;
-            }
-        } catch (NullPointerException | SecurityException | IOException ex) {
-            Log.w(TAG, "Couldn't open " + uri + ": " + ex);
-            // Fallback to media server
-        }
-        handleDataSource(isCurrent, srcId, uri.toString(), headers, cookies, startPos, endPos);
-    }
-
-    private boolean attemptDataSource(boolean isCurrent, long srcId, AssetFileDescriptor afd,
-            long startPos, long endPos) throws IOException {
-        try {
-            if (afd.getDeclaredLength() < 0) {
-                handleDataSource(isCurrent,
-                        srcId,
-                        afd.getFileDescriptor(),
-                        0,
-                        DataSourceDesc.LONG_MAX,
-                        startPos,
-                        endPos);
-            } else {
-                handleDataSource(isCurrent,
-                        srcId,
-                        afd.getFileDescriptor(),
-                        afd.getStartOffset(),
-                        afd.getDeclaredLength(),
-                        startPos,
-                        endPos);
-            }
-            return true;
-        } catch (NullPointerException | SecurityException | IOException ex) {
-            Log.w(TAG, "Couldn't open srcId:" + srcId + ": " + ex);
-            return false;
-        } finally {
-            if (afd != null) {
-                afd.close();
-            }
-        }
-    }
-
-    private void handleDataSource(
-            boolean isCurrent, long srcId,
-            String path, Map<String, String> headers, List<HttpCookie> cookies,
-            long startPos, long endPos)
-            throws IOException {
-        String[] keys = null;
-        String[] values = null;
-
-        if (headers != null) {
-            keys = new String[headers.size()];
-            values = new String[headers.size()];
-
-            int i = 0;
-            for (Map.Entry<String, String> entry: headers.entrySet()) {
-                keys[i] = entry.getKey();
-                values[i] = entry.getValue();
-                ++i;
-            }
-        }
-        handleDataSource(isCurrent, srcId, path, keys, values, cookies, startPos, endPos);
-    }
-
-    private void handleDataSource(boolean isCurrent, long srcId,
-            String path, String[] keys, String[] values, List<HttpCookie> cookies,
-            long startPos, long endPos)
-            throws IOException {
-        final Uri uri = Uri.parse(path);
-        final String scheme = uri.getScheme();
-        if ("file".equals(scheme)) {
-            path = uri.getPath();
-        } else if (scheme != null) {
-            // handle non-file sources
-            Media2Utils.storeCookies(cookies);
-            nativeHandleDataSourceUrl(
-                isCurrent,
-                srcId,
-                Media2HTTPService.createHTTPService(path),
-                path,
-                keys,
-                values,
-                startPos,
-                endPos);
-            return;
-        }
-
-        final File file = new File(path);
-        if (file.exists()) {
-            FileInputStream is = new FileInputStream(file);
-            FileDescriptor fd = is.getFD();
-            handleDataSource(isCurrent, srcId, fd, 0, DataSourceDesc.LONG_MAX, startPos, endPos);
-            is.close();
-        } else {
-            throw new IOException("handleDataSource failed.");
-        }
-    }
-
-    private native void nativeHandleDataSourceUrl(
-            boolean isCurrent, long srcId,
-            Media2HTTPService httpService, String path, String[] keys, String[] values,
-            long startPos, long endPos)
-            throws IOException;
-
-    /**
-     * Sets the data source (FileDescriptor) to use. The FileDescriptor must be
-     * seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility
-     * to close the file descriptor. It is safe to do so as soon as this call returns.
-     *
-     * @throws IllegalStateException if it is called in an invalid state
-     * @throws IllegalArgumentException if fd is not a valid FileDescriptor
-     * @throws IOException if fd can not be read
-     */
-    private void handleDataSource(
-            boolean isCurrent, long srcId,
-            FileDescriptor fd, long offset, long length,
-            long startPos, long endPos) throws IOException {
-        nativeHandleDataSourceFD(isCurrent, srcId, fd, offset, length, startPos, endPos);
-    }
-
-    private native void nativeHandleDataSourceFD(boolean isCurrent, long srcId,
-            FileDescriptor fd, long offset, long length,
-            long startPos, long endPos) throws IOException;
-
-    /**
-     * @throws IllegalStateException if it is called in an invalid state
-     * @throws IllegalArgumentException if dataSource is not a valid Media2DataSource
-     */
-    private void handleDataSource(boolean isCurrent, long srcId, Media2DataSource dataSource,
-            long startPos, long endPos) {
-        nativeHandleDataSourceCallback(isCurrent, srcId, dataSource, startPos, endPos);
-    }
-
-    private native void nativeHandleDataSourceCallback(
-            boolean isCurrent, long srcId, Media2DataSource dataSource,
-            long startPos, long endPos);
-
-    // return true if there is a next data source, false otherwise.
-    // This function should be always called on |mHandlerThread|.
-    private boolean prepareNextDataSource() {
-        HandlerThread handlerThread = mHandlerThread;
-        if (handlerThread != null && Looper.myLooper() != handlerThread.getLooper()) {
-            Log.e(TAG, "prepareNextDataSource: called on wrong looper");
-        }
-
-        boolean hasNextDSD;
-        int state = getState();
-        synchronized (mSrcLock) {
-            hasNextDSD = !mNextSourceInfos.isEmpty();
-            if (state == PLAYER_STATE_ERROR || state == PLAYER_STATE_IDLE) {
-                // Current source has not been prepared yet.
-                return hasNextDSD;
-            }
-
-            SourceInfo nextSource = mNextSourceInfos.peek();
-            if (!hasNextDSD || nextSource.mStateAsNextSource != NEXT_SOURCE_STATE_INIT) {
-                // There is no next source or it's in preparing or prepared state.
-                return hasNextDSD;
-            }
-
-            try {
-                nextSource.mStateAsNextSource = NEXT_SOURCE_STATE_PREPARING;
-                handleDataSource(false /* isCurrent */, nextSource.mDSD, nextSource.mId);
-            } catch (Exception e) {
-                Message msg = mTaskHandler.obtainMessage(
-                        MEDIA_ERROR, MEDIA_ERROR_IO, MEDIA_ERROR_UNKNOWN, null);
-                mTaskHandler.handleMessage(msg, nextSource.mId);
-
-                mNextSourceInfos.poll();
-                return prepareNextDataSource();
-            }
-        }
-        return hasNextDSD;
-    }
-
-    // This function should be always called on |mHandlerThread|.
-    private void playNextDataSource() {
-        HandlerThread handlerThread = mHandlerThread;
-        if (handlerThread != null && Looper.myLooper() != handlerThread.getLooper()) {
-            Log.e(TAG, "playNextDataSource: called on wrong looper");
-        }
-
-        boolean hasNextDSD = false;
-        synchronized (mSrcLock) {
-            if (!mNextSourceInfos.isEmpty()) {
-                hasNextDSD = true;
-                SourceInfo nextSourceInfo = mNextSourceInfos.peek();
-                if (nextSourceInfo.mStateAsNextSource == NEXT_SOURCE_STATE_PREPARED) {
-                    // Switch to next source only when it has been prepared.
-                    mCurrentSourceInfo = mNextSourceInfos.poll();
-
-                    long srcId = mCurrentSourceInfo.mId;
-                    try {
-                        nativePlayNextDataSource(srcId);
-                    } catch (Exception e) {
-                        Message msg2 = mTaskHandler.obtainMessage(
-                                MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null);
-                        mTaskHandler.handleMessage(msg2, srcId);
-                        // Keep |mNextSourcePlayPending|
-                        hasNextDSD = prepareNextDataSource();
-                    }
-                    if (hasNextDSD) {
-                        stayAwake(true);
-
-                        // Now a new current src is playing.
-                        // Wait for MEDIA_INFO_DATA_SOURCE_START to prepare next source.
-                    }
-                } else if (nextSourceInfo.mStateAsNextSource == NEXT_SOURCE_STATE_INIT) {
-                    hasNextDSD = prepareNextDataSource();
-                }
-            }
-        }
-
-        if (!hasNextDSD) {
-            sendEvent(new EventNotifier() {
-                @Override
-                public void notify(EventCallback callback) {
-                    callback.onInfo(
-                            MediaPlayer2Impl.this, null, MEDIA_INFO_DATA_SOURCE_LIST_END, 0);
-                }
-            });
-        }
-    }
-
-    private native void nativePlayNextDataSource(long srcId);
-
-    //--------------------------------------------------------------------------
-    // Explicit Routing
-    //--------------------
-    private AudioDeviceInfo mPreferredDevice = null;
-
-    @Override
-    public boolean setPreferredDevice(AudioDeviceInfo deviceInfo) {
-        boolean status = native_setPreferredDevice(deviceInfo);
-        if (status == true) {
-            synchronized (this) {
-                mPreferredDevice = deviceInfo;
-            }
-        }
-        return status;
-    }
-
-    @Override
-    public AudioDeviceInfo getPreferredDevice() {
-        synchronized (this) {
-            return mPreferredDevice;
-        }
-    }
-
-    @Override
-    public native AudioDeviceInfo getRoutedDevice();
-
-    @Override
-    public void addOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener,
-            Handler handler) {
-        if (listener == null) {
-            throw new IllegalArgumentException("addOnRoutingChangedListener: listener is NULL");
-        }
-        RoutingDelegate routingDelegate = new RoutingDelegate(this, listener, handler);
-        native_addDeviceCallback(routingDelegate);
-    }
-
-    @Override
-    public void removeOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener) {
-        if (listener == null) {
-            throw new IllegalArgumentException("removeOnRoutingChangedListener: listener is NULL");
-        }
-        native_removeDeviceCallback(listener);
-    }
-
-    private native boolean native_setPreferredDevice(AudioDeviceInfo device);
-    private native void native_addDeviceCallback(RoutingDelegate rd);
-    private native void native_removeDeviceCallback(
-            AudioRouting.OnRoutingChangedListener listener);
-
-    @Override
-    public Object setWakeMode(Context context, int mode) {
-        return addTask(new Task(CALL_COMPLETED_SET_WAKE_MODE, false) {
-            @Override
-            void process() {
-                boolean washeld = false;
-
-                if (mWakeLock != null) {
-                    if (mWakeLock.isHeld()) {
-                        washeld = true;
-                        mWakeLock.release();
-                    }
-                    mWakeLock = null;
-                }
-
-                PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-                ActivityManager am =
-                        (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
-                List<RunningAppProcessInfo> runningAppsProcInfo = am.getRunningAppProcesses();
-                int pid = android.os.Process.myPid();
-                String name = "pid " + String.valueOf(pid);
-                if (runningAppsProcInfo != null) {
-                    for (RunningAppProcessInfo procInfo : runningAppsProcInfo) {
-                        if (procInfo.pid == pid) {
-                            name = procInfo.processName;
-                            break;
-                        }
-                    }
-                }
-                mWakeLock = pm.newWakeLock(mode | PowerManager.ON_AFTER_RELEASE, name);
-                mWakeLock.setReferenceCounted(false);
-                if (washeld) {
-                    mWakeLock.acquire();
-                }
-            }
-        });
-    }
-
-    @Override
-    public Object setScreenOnWhilePlaying(boolean screenOn) {
-        return addTask(new Task(CALL_COMPLETED_SET_SCREEN_ON_WHILE_PLAYING, false) {
-            @Override
-            void process() {
-                if (mScreenOnWhilePlaying != screenOn) {
-                    if (screenOn && mSurfaceHolder == null) {
-                        Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective"
-                                + " without a SurfaceHolder");
-                    }
-                    mScreenOnWhilePlaying = screenOn;
-                    updateSurfaceScreenOn();
-                }
-            }
-        });
-    }
-
-    private void stayAwake(boolean awake) {
-        if (mWakeLock != null) {
-            if (awake && !mWakeLock.isHeld()) {
-                mWakeLock.acquire();
-            } else if (!awake && mWakeLock.isHeld()) {
-                mWakeLock.release();
-            }
-        }
-        mStayAwake = awake;
-        updateSurfaceScreenOn();
-    }
-
-    private void updateSurfaceScreenOn() {
-        if (mSurfaceHolder != null) {
-            mSurfaceHolder.setKeepScreenOn(mScreenOnWhilePlaying && mStayAwake);
-        }
-    }
-
-    @Override
-    public VideoSize getVideoSize() {
-        return mVideoSize;
-    }
-
-    @Override
-    public PersistableBundle getMetrics() {
-        PersistableBundle bundle = native_getMetrics();
-        return bundle;
-    }
-
-    private native PersistableBundle native_getMetrics();
-
-    @Override
-    @NonNull
-    native BufferingParams getBufferingParams();
-
-    @Override
-    Object setBufferingParams(@NonNull BufferingParams params) {
-        return addTask(new Task(CALL_COMPLETED_SET_BUFFERING_PARAMS, false) {
-            @Override
-            void process() {
-                checkArgument(params != null, "the BufferingParams cannot be null");
-                _setBufferingParams(params);
-            }
-        });
-    }
-
-    private native void _setBufferingParams(@NonNull BufferingParams params);
-
-    @Override
-    public Object setPlaybackParams(@NonNull PlaybackParams params) {
-        return addTask(new Task(CALL_COMPLETED_SET_PLAYBACK_PARAMS, false) {
-            @Override
-            void process() {
-                checkArgument(params != null, "the PlaybackParams cannot be null");
-                _setPlaybackParams(params);
-            }
-        });
-    }
-
-    private native void _setPlaybackParams(@NonNull PlaybackParams params);
-
-    @Override
-    @NonNull
-    public native PlaybackParams getPlaybackParams();
-
-    @Override
-    public Object setSyncParams(@NonNull SyncParams params) {
-        return addTask(new Task(CALL_COMPLETED_SET_SYNC_PARAMS, false) {
-            @Override
-            void process() {
-                checkArgument(params != null, "the SyncParams cannot be null");
-                _setSyncParams(params);
-            }
-        });
-    }
-
-    private native void _setSyncParams(@NonNull SyncParams params);
-
-    @Override
-    @NonNull
-    public native SyncParams getSyncParams();
-
-    @Override
-    public Object seekTo(final long msec, @SeekMode int mode) {
-        return addTask(new Task(CALL_COMPLETED_SEEK_TO, true) {
-            @Override
-            void process() {
-                if (mode < SEEK_PREVIOUS_SYNC || mode > SEEK_CLOSEST) {
-                    final String msg = "Illegal seek mode: " + mode;
-                    throw new IllegalArgumentException(msg);
-                }
-                // TODO: pass long to native, instead of truncating here.
-                long posMs = msec;
-                if (posMs > Integer.MAX_VALUE) {
-                    Log.w(TAG, "seekTo offset " + posMs + " is too large, cap to "
-                            + Integer.MAX_VALUE);
-                    posMs = Integer.MAX_VALUE;
-                } else if (posMs < Integer.MIN_VALUE) {
-                    Log.w(TAG, "seekTo offset " + posMs + " is too small, cap to "
-                            + Integer.MIN_VALUE);
-                    posMs = Integer.MIN_VALUE;
-                }
-
-                synchronized (mTaskLock) {
-                    if (mIsPreviousCommandSeekTo
-                            && mPreviousSeekPos == posMs
-                            && mPreviousSeekMode == mode) {
-                        throw new CommandSkippedException(
-                                "same as previous seekTo");
-                    }
-                }
-
-                _seekTo(posMs, mode);
-
-                synchronized (mTaskLock) {
-                    mIsPreviousCommandSeekTo = true;
-                    mPreviousSeekPos = posMs;
-                    mPreviousSeekMode = mode;
-                }
-            }
-        });
-    }
-
-    private native final void _seekTo(long msec, int mode);
-
-    @Override
-    @Nullable
-    public MediaTimestamp getTimestamp()
-    {
-        try {
-            // TODO: get the timestamp from native side
-            return new MediaTimestamp(
-                    getCurrentPosition() * 1000L,
-                    System.nanoTime(),
-                    getState() == PLAYER_STATE_PLAYING ? getPlaybackParams().getSpeed() : 0.f);
-        } catch (IllegalStateException e) {
-            return null;
-        }
-    }
-
-    @Override
-    public void reset() {
-        synchronized (mEventCbLock) {
-            mEventCallbackRecords.clear();
-        }
-        synchronized (mDrmEventCbLock) {
-            mDrmEventCallbackRecords.clear();
-        }
-        synchronized (mSrcLock) {
-            mCurrentSourceInfo = null;
-            mNextSourceInfos.clear();
-        }
-
-        synchronized (mTaskLock) {
-            mPendingTasks.clear();
-            mIsPreviousCommandSeekTo = false;
-        }
-
-        stayAwake(false);
-        _reset();
-        // make sure none of the listeners get called anymore
-        if (mTaskHandler != null) {
-            mTaskHandler.removeCallbacksAndMessages(null);
-        }
-
-        resetDrmState();
-    }
-
-    private native void _reset();
-
-    // Keep KEY_PARAMETER_* in sync with include/media/mediaplayer2.h
-    private final static int KEY_PARAMETER_AUDIO_ATTRIBUTES = 1400;
-
-    // return true if the parameter is set successfully, false otherwise
-    private native boolean native_setAudioAttributes(AudioAttributes audioAttributes);
-    private native AudioAttributes native_getAudioAttributes();
-
-    @Override
-    public native boolean isLooping();
-
-    @Override
-    public Object setAudioSessionId(int sessionId) {
-        return addTask(new Task(CALL_COMPLETED_SET_AUDIO_SESSION_ID, false) {
-            @Override
-            void process() {
-                _setAudioSessionId(sessionId);
-            }
-        });
-    }
-
-    private native void _setAudioSessionId(int sessionId);
-
-    @Override
-    public native int getAudioSessionId();
-
-    @Override
-    public Object attachAuxEffect(int effectId) {
-        return addTask(new Task(CALL_COMPLETED_ATTACH_AUX_EFFECT, false) {
-            @Override
-            void process() {
-                _attachAuxEffect(effectId);
-            }
-        });
-    }
-
-    private native void _attachAuxEffect(int effectId);
-
-    @Override
-    public Object setAuxEffectSendLevel(float level) {
-        return addTask(new Task(CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL, false) {
-            @Override
-            void process() {
-                _setAuxEffectSendLevel(level);
-            }
-        });
-    }
-
-    private native void _setAuxEffectSendLevel(float level);
-
-    private static native final void native_init();
-    private native final void native_setup(Object mediaplayer2_this);
-    private native final void native_finalize();
-
-    private static native final void native_stream_event_onTearDown(
-            long nativeCallbackPtr, long userDataPtr);
-    private static native final void native_stream_event_onStreamPresentationEnd(
-            long nativeCallbackPtr, long userDataPtr);
-    private static native final void native_stream_event_onStreamDataRequest(
-            long jAudioTrackPtr, long nativeCallbackPtr, long userDataPtr);
-
-    /**
-     * Class for MediaPlayer2 to return each audio/video/subtitle track's metadata.
-     *
-     * @see android.media.MediaPlayer2#getTrackInfo
-     */
-    public static final class TrackInfoImpl extends TrackInfo {
-        @Override
-        public int getTrackType() {
-            return mTrackType;
-        }
-
-        @Override
-        public String getLanguage() {
-            String language = mFormat.getString(MediaFormat.KEY_LANGUAGE);
-            return language == null ? "und" : language;
-        }
-
-        @Override
-        public MediaFormat getFormat() {
-            if (mTrackType == MEDIA_TRACK_TYPE_TIMEDTEXT
-                    || mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) {
-                return mFormat;
-            }
-            return null;
-        }
-
-        final int mTrackType;
-        final MediaFormat mFormat;
-
-        TrackInfoImpl(Iterator<Value> in) {
-            mTrackType = in.next().getInt32Value();
-            // TODO: build the full MediaFormat; currently we are using createSubtitleFormat
-            // even for audio/video tracks, meaning we only set the mime and language.
-            String mime = in.next().getStringValue();
-            String language = in.next().getStringValue();
-            mFormat = MediaFormat.createSubtitleFormat(mime, language);
-
-            if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) {
-                mFormat.setInteger(MediaFormat.KEY_IS_AUTOSELECT, in.next().getInt32Value());
-                mFormat.setInteger(MediaFormat.KEY_IS_DEFAULT, in.next().getInt32Value());
-                mFormat.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, in.next().getInt32Value());
-            }
-        }
-
-        /** @hide */
-        TrackInfoImpl(int type, MediaFormat format) {
-            mTrackType = type;
-            mFormat = format;
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder out = new StringBuilder(128);
-            out.append(getClass().getName());
-            out.append('{');
-            switch (mTrackType) {
-            case MEDIA_TRACK_TYPE_VIDEO:
-                out.append("VIDEO");
-                break;
-            case MEDIA_TRACK_TYPE_AUDIO:
-                out.append("AUDIO");
-                break;
-            case MEDIA_TRACK_TYPE_TIMEDTEXT:
-                out.append("TIMEDTEXT");
-                break;
-            case MEDIA_TRACK_TYPE_SUBTITLE:
-                out.append("SUBTITLE");
-                break;
-            default:
-                out.append("UNKNOWN");
-                break;
-            }
-            out.append(", " + mFormat.toString());
-            out.append("}");
-            return out.toString();
-        }
-    };
-
-    @Override
-    public List<TrackInfo> getTrackInfo() {
-        TrackInfoImpl trackInfo[] = getInbandTrackInfoImpl();
-        return Arrays.asList(trackInfo);
-    }
-
-    private TrackInfoImpl[] getInbandTrackInfoImpl() throws IllegalStateException {
-        PlayerMessage request = PlayerMessage.newBuilder()
-                .addValues(Value.newBuilder().setInt32Value(INVOKE_ID_GET_TRACK_INFO))
-                .build();
-        PlayerMessage response = invoke(request);
-        if (response == null) {
-            return null;
-        }
-        Iterator<Value> in = response.getValuesList().iterator();
-        int size = in.next().getInt32Value();
-        if (size == 0) {
-            return null;
-        }
-        TrackInfoImpl trackInfo[] = new TrackInfoImpl[size];
-        for (int i = 0; i < size; ++i) {
-            trackInfo[i] = new TrackInfoImpl(in);
-        }
-        return trackInfo;
-    }
-
-    @Override
-    public int getSelectedTrack(int trackType) {
-        PlayerMessage request = PlayerMessage.newBuilder()
-                .addValues(Value.newBuilder().setInt32Value(INVOKE_ID_GET_SELECTED_TRACK))
-                .addValues(Value.newBuilder().setInt32Value(trackType))
-                .build();
-        PlayerMessage response = invoke(request);
-        if (response == null) {
-            return -1;
-        }
-        return response.getValues(0).getInt32Value();
-    }
-
-    @Override
-    public Object selectTrack(int index) {
-        return addTask(new Task(CALL_COMPLETED_SELECT_TRACK, false) {
-            @Override
-            void process() {
-                selectOrDeselectTrack(index, true /* select */);
-            }
-        });
-    }
-
-    @Override
-    public Object deselectTrack(int index) {
-        return addTask(new Task(CALL_COMPLETED_DESELECT_TRACK, false) {
-            @Override
-            void process() {
-                selectOrDeselectTrack(index, false /* select */);
-            }
-        });
-    }
-
-    private void selectOrDeselectTrack(int index, boolean select)
-            throws IllegalStateException {
-        PlayerMessage request = PlayerMessage.newBuilder()
-                .addValues(Value.newBuilder().setInt32Value(
-                            select? INVOKE_ID_SELECT_TRACK: INVOKE_ID_DESELECT_TRACK))
-                .addValues(Value.newBuilder().setInt32Value(index))
-                .build();
-        invoke(request);
-    }
-
-    // Have to declare protected for finalize() since it is protected
-    // in the base class Object.
-    @Override
-    protected void finalize() throws Throwable {
-        super.finalize();
-        native_finalize();
-    }
-
-    private synchronized void release() {
-        if (mReleased) {
-            return;
-        }
-        stayAwake(false);
-        updateSurfaceScreenOn();
-        synchronized (mEventCbLock) {
-            mEventCallbackRecords.clear();
-        }
-        if (mHandlerThread != null) {
-            mHandlerThread.quitSafely();
-            mHandlerThread = null;
-        }
-
-        // Modular DRM clean up
-        mOnDrmConfigHelper = null;
-        synchronized (mDrmEventCbLock) {
-            mDrmEventCallbackRecords.clear();
-        }
-        resetDrmState();
-
-        _release();
-        mReleased = true;
-    }
-
-    private native void _release();
-
-    /* Do not change these values without updating their counterparts
-     * in include/media/mediaplayer2.h!
-     */
-    private static final int MEDIA_NOP = 0; // interface test message
-    private static final int MEDIA_PREPARED = 1;
-    private static final int MEDIA_PLAYBACK_COMPLETE = 2;
-    private static final int MEDIA_BUFFERING_UPDATE = 3;
-    private static final int MEDIA_SEEK_COMPLETE = 4;
-    private static final int MEDIA_SET_VIDEO_SIZE = 5;
-    private static final int MEDIA_STARTED = 6;
-    private static final int MEDIA_PAUSED = 7;
-    private static final int MEDIA_STOPPED = 8;
-    private static final int MEDIA_SKIPPED = 9;
-    private static final int MEDIA_NOTIFY_TIME = 98;
-    private static final int MEDIA_TIMED_TEXT = 99;
-    private static final int MEDIA_ERROR = 100;
-    private static final int MEDIA_INFO = 200;
-    private static final int MEDIA_SUBTITLE_DATA = 201;
-    private static final int MEDIA_META_DATA = 202;
-    private static final int MEDIA_DRM_INFO = 210;
-
-    private class TaskHandler extends Handler {
-        private MediaPlayer2Impl mMediaPlayer;
-
-        public TaskHandler(MediaPlayer2Impl mp, Looper looper) {
-            super(looper);
-            mMediaPlayer = mp;
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            handleMessage(msg, 0);
-        }
-
-        public void handleMessage(Message msg, long srcId) {
-            if (mMediaPlayer.mNativeContext == 0) {
-                Log.w(TAG, "mediaplayer2 went away with unhandled events");
-                return;
-            }
-            final int what = msg.arg1;
-            final int extra = msg.arg2;
-
-            final SourceInfo sourceInfo = getSourceInfoById(srcId);
-            if (sourceInfo == null) {
-                return;
-            }
-            final DataSourceDesc dsd = sourceInfo.mDSD;
-
-            switch(msg.what) {
-                case MEDIA_PREPARED:
-                {
-                    if (dsd != null) {
-                        sendEvent(new EventNotifier() {
-                            @Override
-                            public void notify(EventCallback callback) {
-                                callback.onInfo(
-                                        mMediaPlayer, dsd, MEDIA_INFO_PREPARED, 0);
-                            }
-                        });
-                    }
-
-                    synchronized (mSrcLock) {
-                        SourceInfo nextSourceInfo = mNextSourceInfos.peek();
-                        Log.i(TAG, "MEDIA_PREPARED: srcId=" + srcId
-                                + ", curSrc=" + mCurrentSourceInfo
-                                + ", nextSrc=" + nextSourceInfo);
-
-                        if (isCurrentSource(srcId)) {
-                            prepareNextDataSource();
-                        } else if (isNextSource(srcId)) {
-                            nextSourceInfo.mStateAsNextSource = NEXT_SOURCE_STATE_PREPARED;
-                            if (nextSourceInfo.mPlayPendingAsNextSource) {
-                                playNextDataSource();
-                            }
-                        }
-                    }
-
-                    synchronized (mTaskLock) {
-                        if (mCurrentTask != null
-                                && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE
-                                && mCurrentTask.mDSD == dsd
-                                && mCurrentTask.mNeedToWaitForEventToComplete) {
-                            mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR);
-                            mCurrentTask = null;
-                            processPendingTask_l();
-                        }
-                    }
-                    return;
-                }
-
-                case MEDIA_DRM_INFO:
-                {
-                    if (msg.obj == null) {
-                        Log.w(TAG, "MEDIA_DRM_INFO msg.obj=NULL");
-                    } else if (msg.obj instanceof byte[]) {
-                        // The PlayerMessage was parsed already in postEventFromNative
-                        final DrmInfoImpl drmInfo;
-
-                        synchronized (mDrmLock) {
-                            if (mDrmInfoImpl != null) {
-                                drmInfo = mDrmInfoImpl.makeCopy();
-                            } else {
-                                drmInfo = null;
-                            }
-                        }
-
-                        // notifying the client outside the lock
-                        if (drmInfo != null) {
-                            sendDrmEvent(new DrmEventNotifier() {
-                                @Override
-                                public void notify(DrmEventCallback callback) {
-                                    callback.onDrmInfo(
-                                            mMediaPlayer, dsd, drmInfo);
-                                }
-                            });
-                        }
-                    } else {
-                        Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + msg.obj);
-                    }
-                    return;
-                }
-
-                case MEDIA_PLAYBACK_COMPLETE:
-                {
-                    if (isCurrentSource(srcId)) {
-                        sendEvent(new EventNotifier() {
-                            @Override
-                            public void notify(EventCallback callback) {
-                                callback.onInfo(
-                                        mMediaPlayer, dsd, MEDIA_INFO_DATA_SOURCE_END, 0);
-                            }
-                        });
-                        stayAwake(false);
-
-                        synchronized (mSrcLock) {
-                            SourceInfo nextSourceInfo = mNextSourceInfos.peek();
-                            if (nextSourceInfo != null) {
-                                nextSourceInfo.mPlayPendingAsNextSource = true;
-                            }
-                            Log.i(TAG, "MEDIA_PLAYBACK_COMPLETE: srcId=" + srcId
-                                    + ", curSrc=" + mCurrentSourceInfo
-                                    + ", nextSrc=" + nextSourceInfo);
-                        }
-
-                        playNextDataSource();
-                    }
-
-                    return;
-                }
-
-                case MEDIA_STOPPED:
-                case MEDIA_STARTED:
-                case MEDIA_PAUSED:
-                case MEDIA_SKIPPED:
-                case MEDIA_NOTIFY_TIME:
-                {
-                    // Do nothing. The client should have enough information with
-                    // {@link EventCallback#onMediaTimeDiscontinuity}.
-                    break;
-                }
-
-                case MEDIA_BUFFERING_UPDATE:
-                {
-                    final int percent = msg.arg1;
-                    sendEvent(new EventNotifier() {
-                        @Override
-                        public void notify(EventCallback callback) {
-                            callback.onInfo(
-                                    mMediaPlayer, dsd, MEDIA_INFO_BUFFERING_UPDATE, percent);
-                        }
-                    });
-
-                    SourceInfo src = getSourceInfoById(srcId);
-                    if (src != null) {
-                        src.mBufferedPercentage.set(percent);
-                    }
-
-                    return;
-                }
-
-                case MEDIA_SEEK_COMPLETE:
-                {
-                    synchronized (mTaskLock) {
-                        if (!mPendingTasks.isEmpty()
-                                && mPendingTasks.get(0).mMediaCallType != CALL_COMPLETED_SEEK_TO
-                                && getState() == PLAYER_STATE_PLAYING) {
-                            mIsPreviousCommandSeekTo = false;
-                        }
-
-                        if (mCurrentTask != null
-                                && mCurrentTask.mMediaCallType == CALL_COMPLETED_SEEK_TO
-                                && mCurrentTask.mNeedToWaitForEventToComplete) {
-                            mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR);
-                            mCurrentTask = null;
-                            processPendingTask_l();
-                        }
-                    }
-                    return;
-                }
-
-                case MEDIA_SET_VIDEO_SIZE:
-                {
-                    final int width = msg.arg1;
-                    final int height = msg.arg2;
-
-                    mVideoSize = new VideoSize(width, height);
-                    sendEvent(new EventNotifier() {
-                        @Override
-                        public void notify(EventCallback callback) {
-                            callback.onVideoSizeChanged(
-                                    mMediaPlayer, dsd, mVideoSize);
-                        }
-                    });
-                    return;
-                }
-
-                case MEDIA_ERROR:
-                {
-                    Log.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")");
-                    sendEvent(new EventNotifier() {
-                        @Override
-                        public void notify(EventCallback callback) {
-                            callback.onError(
-                                    mMediaPlayer, dsd, what, extra);
-                        }
-                    });
-                    sendEvent(new EventNotifier() {
-                        @Override
-                        public void notify(EventCallback callback) {
-                            callback.onInfo(
-                                    mMediaPlayer, dsd, MEDIA_INFO_DATA_SOURCE_END, 0);
-                        }
-                    });
-                    stayAwake(false);
-                    return;
-                }
-
-                case MEDIA_INFO:
-                {
-                    switch (msg.arg1) {
-                        case MEDIA_INFO_VIDEO_TRACK_LAGGING:
-                            Log.i(TAG, "Info (" + msg.arg1 + "," + msg.arg2 + ")");
-                            break;
-                    }
-
-                    sendEvent(new EventNotifier() {
-                        @Override
-                        public void notify(EventCallback callback) {
-                            callback.onInfo(
-                                    mMediaPlayer, dsd, what, extra);
-                        }
-                    });
-
-                    if (msg.arg1 == MEDIA_INFO_DATA_SOURCE_START) {
-                        if (isCurrentSource(srcId)) {
-                            prepareNextDataSource();
-                        }
-                    }
-
-                    // No real default action so far.
-                    return;
-                }
-
-                case MEDIA_TIMED_TEXT:
-                {
-                    final TimedText text;
-                    if (msg.obj instanceof byte[]) {
-                        PlayerMessage playerMsg;
-                        try {
-                            playerMsg = PlayerMessage.parseFrom((byte[]) msg.obj);
-                        } catch (InvalidProtocolBufferException e) {
-                            Log.w(TAG, "Failed to parse timed text.", e);
-                            return;
-                        }
-                        text = TimedTextUtil.parsePlayerMessage(playerMsg);
-                    } else {
-                        text = null;
-                    }
-
-                    sendEvent(new EventNotifier() {
-                        @Override
-                        public void notify(EventCallback callback) {
-                            callback.onTimedText(
-                                    mMediaPlayer, dsd, text);
-                        }
-                    });
-                    return;
-                }
-
-                case MEDIA_SUBTITLE_DATA:
-                {
-                    if (msg.obj instanceof byte[]) {
-                        PlayerMessage playerMsg;
-                        try {
-                            playerMsg = PlayerMessage.parseFrom((byte[]) msg.obj);
-                        } catch (InvalidProtocolBufferException e) {
-                            Log.w(TAG, "Failed to parse subtitle data.", e);
-                            return;
-                        }
-                        Iterator<Value> in = playerMsg.getValuesList().iterator();
-                        SubtitleData data = new SubtitleData(
-                                in.next().getInt32Value(),  // trackIndex
-                                in.next().getInt64Value(),  // startTimeUs
-                                in.next().getInt64Value(),  // durationUs
-                                in.next().getBytesValue().toByteArray());  // data
-                        sendEvent(new EventNotifier() {
-                            @Override
-                            public void notify(EventCallback callback) {
-                                callback.onSubtitleData(
-                                        mMediaPlayer, dsd, data);
-                            }
-                        });
-                    }
-                    return;
-                }
-
-                case MEDIA_META_DATA:
-                {
-                    final TimedMetaData data;
-                    if (msg.obj instanceof byte[]) {
-                        PlayerMessage playerMsg;
-                        try {
-                            playerMsg = PlayerMessage.parseFrom((byte[]) msg.obj);
-                        } catch (InvalidProtocolBufferException e) {
-                            Log.w(TAG, "Failed to parse timed meta data.", e);
-                            return;
-                        }
-                        Iterator<Value> in = playerMsg.getValuesList().iterator();
-                        data = new TimedMetaData(
-                                in.next().getInt64Value(),  // timestampUs
-                                in.next().getBytesValue().toByteArray());  // metaData
-                    } else {
-                        data = null;
-                    }
-
-                    sendEvent(new EventNotifier() {
-                        @Override
-                        public void notify(EventCallback callback) {
-                            callback.onTimedMetaDataAvailable(
-                                    mMediaPlayer, dsd, data);
-                        }
-                    });
-                    return;
-                }
-
-                case MEDIA_NOP: // interface test message - ignore
-                {
-                    break;
-                }
-
-                default:
-                {
-                    Log.e(TAG, "Unknown message type " + msg.what);
-                    return;
-                }
-            }
-        }
-
-    }
-
-    /*
-     * Called from native code when an interesting event happens.  This method
-     * just uses the TaskHandler system to post the event back to the main app thread.
-     * We use a weak reference to the original MediaPlayer2 object so that the native
-     * code is safe from the object disappearing from underneath it.  (This is
-     * the cookie passed to native_setup().)
-     */
-    private static void postEventFromNative(Object mediaplayer2_ref, long srcId,
-                                            int what, int arg1, int arg2, byte[] obj)
-    {
-        final MediaPlayer2Impl mp = (MediaPlayer2Impl)((WeakReference)mediaplayer2_ref).get();
-        if (mp == null) {
-            return;
-        }
-
-        switch (what) {
-        case MEDIA_DRM_INFO:
-            // We need to derive mDrmInfoImpl before prepare() returns so processing it here
-            // before the notification is sent to TaskHandler below. TaskHandler runs in the
-            // notification looper so its handleMessage might process the event after prepare()
-            // has returned.
-            Log.v(TAG, "postEventFromNative MEDIA_DRM_INFO");
-            if (obj != null) {
-                PlayerMessage playerMsg;
-                try {
-                    playerMsg = PlayerMessage.parseFrom(obj);
-                } catch (InvalidProtocolBufferException e) {
-                    Log.w(TAG, "MEDIA_DRM_INFO failed to parse msg.obj " + obj);
-                    break;
-                }
-                DrmInfoImpl drmInfo = new DrmInfoImpl(playerMsg);
-                synchronized (mp.mDrmLock) {
-                    mp.mDrmInfoImpl = drmInfo;
-                }
-            } else {
-                Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + obj);
-            }
-            break;
-
-        case MEDIA_PREPARED:
-            // By this time, we've learned about DrmInfo's presence or absence. This is meant
-            // mainly for prepare() use case. For prepare(), this still can run to a race
-            // condition b/c MediaPlayerNative releases the prepare() lock before calling notify
-            // so we also set mDrmInfoResolved in prepare().
-            synchronized (mp.mDrmLock) {
-                mp.mDrmInfoResolved = true;
-            }
-            break;
-
-        }
-
-        if (mp.mTaskHandler != null) {
-            Message m = mp.mTaskHandler.obtainMessage(what, arg1, arg2, obj);
-
-            mp.mTaskHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    mp.mTaskHandler.handleMessage(m, srcId);
-                }
-            });
-        }
-    }
-
-    private final Object mEventCbLock = new Object();
-    private ArrayList<Pair<Executor, EventCallback> > mEventCallbackRecords
-        = new ArrayList<Pair<Executor, EventCallback> >();
-
-    /**
-     * Register a callback to be invoked when the media source is ready
-     * for playback.
-     *
-     * @param eventCallback the callback that will be run
-     * @param executor the executor through which the callback should be invoked
-     */
-    @Override
-    public void registerEventCallback(@NonNull @CallbackExecutor Executor executor,
-            @NonNull EventCallback eventCallback) {
-        if (eventCallback == null) {
-            throw new IllegalArgumentException("Illegal null EventCallback");
-        }
-        if (executor == null) {
-            throw new IllegalArgumentException(
-                    "Illegal null Executor for the EventCallback");
-        }
-        synchronized (mEventCbLock) {
-            mEventCallbackRecords.add(new Pair(executor, eventCallback));
-        }
-    }
-
-    /**
-     * Clears the {@link EventCallback}.
-     */
-    @Override
-    public void unregisterEventCallback(EventCallback eventCallback) {
-        synchronized (mEventCbLock) {
-            for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) {
-                if (cb.second == eventCallback) {
-                    mEventCallbackRecords.remove(cb);
-                }
-            }
-        }
-    }
-
-    private static void checkArgument(boolean expression, String errorMessage) {
-        if (!expression) {
-            throw new IllegalArgumentException(errorMessage);
-        }
-    }
-
-    private void sendEvent(final EventNotifier notifier) {
-        synchronized (mEventCbLock) {
-            try {
-                for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) {
-                    cb.first.execute(() -> notifier.notify(cb.second));
-                }
-            } catch (RejectedExecutionException e) {
-                // The executor has been shut down.
-                Log.w(TAG, "The executor has been shut down. Ignoring event.");
-            }
-        }
-    }
-
-    private void sendDrmEvent(final DrmEventNotifier notifier) {
-        synchronized (mDrmEventCbLock) {
-            try {
-                for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) {
-                    cb.first.execute(() -> notifier.notify(cb.second));
-                }
-            } catch (RejectedExecutionException e) {
-                // The executor has been shut down.
-                Log.w(TAG, "The executor has been shut down. Ignoring drm event.");
-            }
-        }
-    }
-
-    private interface EventNotifier {
-        void notify(EventCallback callback);
-    }
-
-    private interface DrmEventNotifier {
-        void notify(DrmEventCallback callback);
-    }
-
-    // Modular DRM begin
-
-    /**
-     * Register a callback to be invoked for configuration of the DRM object before
-     * the session is created.
-     * The callback will be invoked synchronously during the execution
-     * of {@link #prepareDrm(UUID uuid)}.
-     *
-     * @param listener the callback that will be run
-     */
-    @Override
-    public void setOnDrmConfigHelper(OnDrmConfigHelper listener)
-    {
-        synchronized (mDrmLock) {
-            mOnDrmConfigHelper = listener;
-        } // synchronized
-    }
-
-    private OnDrmConfigHelper mOnDrmConfigHelper;
-
-    private final Object mDrmEventCbLock = new Object();
-    private ArrayList<Pair<Executor, DrmEventCallback> > mDrmEventCallbackRecords
-        = new ArrayList<Pair<Executor, DrmEventCallback> >();
-
-    @Override
-    public void registerDrmEventCallback(@NonNull @CallbackExecutor Executor executor,
-            @NonNull DrmEventCallback eventCallback) {
-        if (eventCallback == null) {
-            throw new IllegalArgumentException("Illegal null EventCallback");
-        }
-        if (executor == null) {
-            throw new IllegalArgumentException(
-                    "Illegal null Executor for the EventCallback");
-        }
-        synchronized (mDrmEventCbLock) {
-            mDrmEventCallbackRecords.add(new Pair(executor, eventCallback));
-        }
-    }
-
-    @Override
-    public void unregisterDrmEventCallback(DrmEventCallback eventCallback) {
-        synchronized (mDrmEventCbLock) {
-            for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) {
-                if (cb.second == eventCallback) {
-                    mDrmEventCallbackRecords.remove(cb);
-                }
-            }
-        }
-    }
-
-    /**
-     * Retrieves the DRM Info associated with the current source
-     *
-     * @throws IllegalStateException if called before prepare()
-     */
-    @Override
-    public DrmInfo getDrmInfo() {
-        DrmInfoImpl drmInfo = null;
-
-        // there is not much point if the app calls getDrmInfo within an OnDrmInfoListenet;
-        // regardless below returns drmInfo anyway instead of raising an exception
-        synchronized (mDrmLock) {
-            if (!mDrmInfoResolved && mDrmInfoImpl == null) {
-                final String msg = "The Player has not been prepared yet";
-                Log.v(TAG, msg);
-                throw new IllegalStateException(msg);
-            }
-
-            if (mDrmInfoImpl != null) {
-                drmInfo = mDrmInfoImpl.makeCopy();
-            }
-        }   // synchronized
-
-        return drmInfo;
-    }
-
-    @Override
-    public Object prepareDrm(@NonNull UUID uuid) {
-        return addTask(new Task(CALL_COMPLETED_PREPARE_DRM, true) {
-            @Override
-            void process() {
-                int status = PREPARE_DRM_STATUS_SUCCESS;
-                boolean sendEvent = true;
-
-                try {
-                    doPrepareDrm(uuid);
-                } catch (ResourceBusyException e) {
-                    status = PREPARE_DRM_STATUS_RESOURCE_BUSY;
-                } catch (UnsupportedSchemeException e) {
-                    status = PREPARE_DRM_STATUS_UNSUPPORTED_SCHEME;
-                } catch (NotProvisionedException e) {
-                    Log.w(TAG, "prepareDrm: NotProvisionedException");
-
-                    // handle provisioning internally; it'll reset mPrepareDrmInProgress
-                    status = HandleProvisioninig(uuid);
-
-                    if (status == PREPARE_DRM_STATUS_SUCCESS) {
-                        // DrmEventCallback will be fired in provisioning
-                        sendEvent = false;
-                    } else {
-                        synchronized (mDrmLock) {
-                            cleanDrmObj();
-                        }
-
-                        switch (status) {
-                        case PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR:
-                            Log.e(TAG, "prepareDrm: Provisioning was required but failed " +
-                                    "due to a network error.");
-                            break;
-
-                        case PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR:
-                            Log.e(TAG, "prepareDrm: Provisioning was required but the request " +
-                                    "was denied by the server.");
-                            break;
-
-                        case PREPARE_DRM_STATUS_PREPARATION_ERROR:
-                        default:
-                            Log.e(TAG, "prepareDrm: Post-provisioning preparation failed.");
-                            break;
-                        }
-                    }
-                } catch (Exception e) {
-                    status = PREPARE_DRM_STATUS_PREPARATION_ERROR;
-                }
-
-                if (sendEvent) {
-                    final int prepareDrmStatus = status;
-                    sendDrmEvent(new DrmEventNotifier() {
-                        @Override
-                        public void notify(DrmEventCallback callback) {
-                            callback.onDrmPrepared(
-                                    MediaPlayer2Impl.this, getCurrentDataSource(), prepareDrmStatus);
-                        }
-                    });
-
-                    synchronized (mTaskLock) {
-                        mCurrentTask = null;
-                        processPendingTask_l();
-                    }
-                }
-            }
-        });
-    }
-
-    private void doPrepareDrm(@NonNull UUID uuid)
-            throws UnsupportedSchemeException, ResourceBusyException,
-                   NotProvisionedException {
-        Log.v(TAG, "prepareDrm: uuid: " + uuid + " mOnDrmConfigHelper: " + mOnDrmConfigHelper);
-
-        synchronized (mDrmLock) {
-            // only allowing if tied to a protected source; might relax for releasing offline keys
-            if (mDrmInfoImpl == null) {
-                final String msg = "prepareDrm(): Wrong usage: The player must be prepared and " +
-                        "DRM info be retrieved before this call.";
-                Log.e(TAG, msg);
-                throw new IllegalStateException(msg);
-            }
-
-            if (mActiveDrmScheme) {
-                final String msg = "prepareDrm(): Wrong usage: There is already " +
-                        "an active DRM scheme with " + mDrmUUID;
-                Log.e(TAG, msg);
-                throw new IllegalStateException(msg);
-            }
-
-            if (mPrepareDrmInProgress) {
-                final String msg = "prepareDrm(): Wrong usage: There is already " +
-                        "a pending prepareDrm call.";
-                Log.e(TAG, msg);
-                throw new IllegalStateException(msg);
-            }
-
-            if (mDrmProvisioningInProgress) {
-                final String msg = "prepareDrm(): Unexpectd: Provisioning is already in progress.";
-                Log.e(TAG, msg);
-                throw new IllegalStateException(msg);
-            }
-
-            // shouldn't need this; just for safeguard
-            cleanDrmObj();
-
-            mPrepareDrmInProgress = true;
-
-            try {
-                // only creating the DRM object to allow pre-openSession configuration
-                prepareDrm_createDrmStep(uuid);
-            } catch (Exception e) {
-                Log.w(TAG, "prepareDrm(): Exception ", e);
-                mPrepareDrmInProgress = false;
-                throw e;
-            }
-
-            mDrmConfigAllowed = true;
-        }  // synchronized
-
-        // call the callback outside the lock
-        if (mOnDrmConfigHelper != null)  {
-            mOnDrmConfigHelper.onDrmConfig(this, getCurrentDataSource());
-        }
-
-        synchronized (mDrmLock) {
-            mDrmConfigAllowed = false;
-            boolean earlyExit = false;
-
-            try {
-                prepareDrm_openSessionStep(uuid);
-
-                mDrmUUID = uuid;
-                mActiveDrmScheme = true;
-                mPrepareDrmInProgress = false;
-            } catch (IllegalStateException e) {
-                final String msg = "prepareDrm(): Wrong usage: The player must be " +
-                        "in the prepared state to call prepareDrm().";
-                Log.e(TAG, msg);
-                earlyExit = true;
-                mPrepareDrmInProgress = false;
-                throw new IllegalStateException(msg);
-            } catch (NotProvisionedException e) {
-                Log.w(TAG, "prepareDrm: NotProvisionedException", e);
-                throw e;
-            } catch (Exception e) {
-                Log.e(TAG, "prepareDrm: Exception " + e);
-                earlyExit = true;
-                mPrepareDrmInProgress = false;
-                throw e;
-            } finally {
-                if (earlyExit) {  // clean up object if didn't succeed
-                    cleanDrmObj();
-                }
-            }  // finally
-        }  // synchronized
-    }
-
-    @Override
-    public void releaseDrm()
-            throws NoDrmSchemeException {
-        synchronized (mDrmLock) {
-            Log.v(TAG, "releaseDrm:");
-
-            if (!mActiveDrmScheme) {
-                Log.e(TAG, "releaseDrm(): No active DRM scheme to release.");
-                throw new NoDrmSchemeExceptionImpl(
-                        "releaseDrm: No active DRM scheme to release.");
-            }
-
-            try {
-                // we don't have the player's state in this layer. The below call raises
-                // exception if we're in a non-stopped/prepared state.
-
-                // for cleaning native/mediaserver crypto object
-                _releaseDrm();
-
-                // for cleaning client-side MediaDrm object; only called if above has succeeded
-                cleanDrmObj();
-
-                mActiveDrmScheme = false;
-            } catch (IllegalStateException e) {
-                Log.w(TAG, "releaseDrm: Exception ", e);
-                throw new IllegalStateException(
-                        "releaseDrm: The player is not in a valid state.");
-            } catch (Exception e) {
-                Log.e(TAG, "releaseDrm: Exception ", e);
-            }
-        }  // synchronized
-    }
-
-    private native void _releaseDrm();
-
-    /**
-     * A key request/response exchange occurs between the app and a license server
-     * to obtain or release keys used to decrypt encrypted content.
-     * <p>
-     * getDrmKeyRequest() is used to obtain an opaque key request byte array that is
-     * delivered to the license server.  The opaque key request byte array is returned
-     * in KeyRequest.data.  The recommended URL to deliver the key request to is
-     * returned in KeyRequest.defaultUrl.
-     * <p>
-     * After the app has received the key request response from the server,
-     * it should deliver to the response to the DRM engine plugin using the method
-     * {@link #provideDrmKeyResponse}.
-     *
-     * @param keySetId is the key-set identifier of the offline keys being released when keyType is
-     * {@link MediaDrm#KEY_TYPE_RELEASE}. It should be set to null for other key requests, when
-     * keyType is {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}.
-     *
-     * @param initData is the container-specific initialization data when the keyType is
-     * {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}. Its meaning is
-     * interpreted based on the mime type provided in the mimeType parameter.  It could
-     * contain, for example, the content ID, key ID or other data obtained from the content
-     * metadata that is required in generating the key request.
-     * When the keyType is {@link MediaDrm#KEY_TYPE_RELEASE}, it should be set to null.
-     *
-     * @param mimeType identifies the mime type of the content
-     *
-     * @param keyType specifies the type of the request. The request may be to acquire
-     * keys for streaming, {@link MediaDrm#KEY_TYPE_STREAMING}, or for offline content
-     * {@link MediaDrm#KEY_TYPE_OFFLINE}, or to release previously acquired
-     * keys ({@link MediaDrm#KEY_TYPE_RELEASE}), which are identified by a keySetId.
-     *
-     * @param optionalParameters are included in the key request message to
-     * allow a client application to provide additional message parameters to the server.
-     * This may be {@code null} if no additional parameters are to be sent.
-     *
-     * @throws NoDrmSchemeException if there is no active DRM session
-     */
-    @Override
-    @NonNull
-    public MediaDrm.KeyRequest getDrmKeyRequest(@Nullable byte[] keySetId, @Nullable byte[] initData,
-            @Nullable String mimeType, @MediaDrm.KeyType int keyType,
-            @Nullable Map<String, String> optionalParameters)
-            throws NoDrmSchemeException
-    {
-        Log.v(TAG, "getDrmKeyRequest: " +
-                " keySetId: " + keySetId + " initData:" + initData + " mimeType: " + mimeType +
-                " keyType: " + keyType + " optionalParameters: " + optionalParameters);
-
-        synchronized (mDrmLock) {
-            if (!mActiveDrmScheme) {
-                Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException");
-                throw new NoDrmSchemeExceptionImpl(
-                        "getDrmKeyRequest: Has to set a DRM scheme first.");
-            }
-
-            try {
-                byte[] scope = (keyType != MediaDrm.KEY_TYPE_RELEASE) ?
-                        mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE
-                        keySetId;       // keySetId for KEY_TYPE_RELEASE
-
-                HashMap<String, String> hmapOptionalParameters =
-                                                (optionalParameters != null) ?
-                                                new HashMap<String, String>(optionalParameters) :
-                                                null;
-
-                MediaDrm.KeyRequest request = mDrmObj.getKeyRequest(scope, initData, mimeType,
-                                                              keyType, hmapOptionalParameters);
-                Log.v(TAG, "getDrmKeyRequest:   --> request: " + request);
-
-                return request;
-
-            } catch (NotProvisionedException e) {
-                Log.w(TAG, "getDrmKeyRequest NotProvisionedException: " +
-                        "Unexpected. Shouldn't have reached here.");
-                throw new IllegalStateException("getDrmKeyRequest: Unexpected provisioning error.");
-            } catch (Exception e) {
-                Log.w(TAG, "getDrmKeyRequest Exception " + e);
-                throw e;
-            }
-
-        }   // synchronized
-    }
-
-
-    /**
-     * A key response is received from the license server by the app, then it is
-     * provided to the DRM engine plugin using provideDrmKeyResponse. When the
-     * response is for an offline key request, a key-set identifier is returned that
-     * can be used to later restore the keys to a new session with the method
-     * {@link # restoreDrmKeys}.
-     * When the response is for a streaming or release request, null is returned.
-     *
-     * @param keySetId When the response is for a release request, keySetId identifies
-     * the saved key associated with the release request (i.e., the same keySetId
-     * passed to the earlier {@ link #getDrmKeyRequest} call. It MUST be null when the
-     * response is for either streaming or offline key requests.
-     *
-     * @param response the byte array response from the server
-     *
-     * @throws NoDrmSchemeException if there is no active DRM session
-     * @throws DeniedByServerException if the response indicates that the
-     * server rejected the request
-     */
-    @Override
-    public byte[] provideDrmKeyResponse(@Nullable byte[] keySetId, @NonNull byte[] response)
-            throws NoDrmSchemeException, DeniedByServerException
-    {
-        Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId + " response: " + response);
-
-        synchronized (mDrmLock) {
-
-            if (!mActiveDrmScheme) {
-                Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException");
-                throw new NoDrmSchemeExceptionImpl(
-                        "getDrmKeyRequest: Has to set a DRM scheme first.");
-            }
-
-            try {
-                byte[] scope = (keySetId == null) ?
-                                mDrmSessionId :     // sessionId for KEY_TYPE_STREAMING/OFFLINE
-                                keySetId;           // keySetId for KEY_TYPE_RELEASE
-
-                byte[] keySetResult = mDrmObj.provideKeyResponse(scope, response);
-
-                Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId + " response: " + response
-                        + " --> " + keySetResult);
-
-
-                return keySetResult;
-
-            } catch (NotProvisionedException e) {
-                Log.w(TAG, "provideDrmKeyResponse NotProvisionedException: " +
-                        "Unexpected. Shouldn't have reached here.");
-                throw new IllegalStateException("provideDrmKeyResponse: " +
-                        "Unexpected provisioning error.");
-            } catch (Exception e) {
-                Log.w(TAG, "provideDrmKeyResponse Exception " + e);
-                throw e;
-            }
-        }   // synchronized
-    }
-
-    @Override
-    public void restoreDrmKeys(@NonNull byte[] keySetId)
-            throws NoDrmSchemeException {
-        Log.v(TAG, "restoreDrmKeys: keySetId: " + keySetId);
-
-        synchronized (mDrmLock) {
-            if (!mActiveDrmScheme) {
-                Log.w(TAG, "restoreDrmKeys NoDrmSchemeException");
-                throw new NoDrmSchemeExceptionImpl(
-                        "restoreDrmKeys: Has to set a DRM scheme first.");
-            }
-
-            try {
-                mDrmObj.restoreKeys(mDrmSessionId, keySetId);
-            } catch (Exception e) {
-                Log.w(TAG, "restoreKeys Exception " + e);
-                throw e;
-            }
-        }  // synchronized
-    }
-
-    /**
-     * Read a DRM engine plugin String property value, given the property name string.
-     * <p>
-     * @param propertyName the property name
-     *
-     * Standard fields names are:
-     * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION},
-     * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS}
-     */
-    @Override
-    @NonNull
-    public String getDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName)
-            throws NoDrmSchemeException
-    {
-        Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName);
-
-        String value;
-        synchronized (mDrmLock) {
-
-            if (!mActiveDrmScheme && !mDrmConfigAllowed) {
-                Log.w(TAG, "getDrmPropertyString NoDrmSchemeException");
-                throw new NoDrmSchemeExceptionImpl(
-                        "getDrmPropertyString: Has to prepareDrm() first.");
-            }
-
-            try {
-                value = mDrmObj.getPropertyString(propertyName);
-            } catch (Exception e) {
-                Log.w(TAG, "getDrmPropertyString Exception " + e);
-                throw e;
-            }
-        }   // synchronized
-
-        Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName + " --> value: " + value);
-
-        return value;
-    }
-
-
-    /**
-     * Set a DRM engine plugin String property value.
-     * <p>
-     * @param propertyName the property name
-     * @param value the property value
-     *
-     * Standard fields names are:
-     * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION},
-     * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS}
-     */
-    @Override
-    public void setDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName,
-                                     @NonNull String value)
-            throws NoDrmSchemeException
-    {
-        Log.v(TAG, "setDrmPropertyString: propertyName: " + propertyName + " value: " + value);
-
-        synchronized (mDrmLock) {
-
-            if ( !mActiveDrmScheme && !mDrmConfigAllowed ) {
-                Log.w(TAG, "setDrmPropertyString NoDrmSchemeException");
-                throw new NoDrmSchemeExceptionImpl(
-                        "setDrmPropertyString: Has to prepareDrm() first.");
-            }
-
-            try {
-                mDrmObj.setPropertyString(propertyName, value);
-            } catch ( Exception e ) {
-                Log.w(TAG, "setDrmPropertyString Exception " + e);
-                throw e;
-            }
-        }   // synchronized
-    }
-
-    /**
-     * Encapsulates the DRM properties of the source.
-     */
-    public static final class DrmInfoImpl extends DrmInfo {
-        private Map<UUID, byte[]> mapPssh;
-        private UUID[] supportedSchemes;
-
-        /**
-         * Returns the PSSH info of the data source for each supported DRM scheme.
-         */
-        @Override
-        public Map<UUID, byte[]> getPssh() {
-            return mapPssh;
-        }
-
-        /**
-         * Returns the intersection of the data source and the device DRM schemes.
-         * It effectively identifies the subset of the source's DRM schemes which
-         * are supported by the device too.
-         */
-        @Override
-        public List<UUID> getSupportedSchemes() {
-            return Arrays.asList(supportedSchemes);
-        }
-
-        private DrmInfoImpl(Map<UUID, byte[]> Pssh, UUID[] SupportedSchemes) {
-            mapPssh = Pssh;
-            supportedSchemes = SupportedSchemes;
-        }
-
-        private DrmInfoImpl(PlayerMessage msg) {
-            Log.v(TAG, "DrmInfoImpl(" + msg + ")");
-
-            Iterator<Value> in = msg.getValuesList().iterator();
-            byte[] pssh = in.next().getBytesValue().toByteArray();
-
-            Log.v(TAG, "DrmInfoImpl() PSSH: " + arrToHex(pssh));
-            mapPssh = parsePSSH(pssh, pssh.length);
-            Log.v(TAG, "DrmInfoImpl() PSSH: " + mapPssh);
-
-            int supportedDRMsCount = in.next().getInt32Value();
-            supportedSchemes = new UUID[supportedDRMsCount];
-            for (int i = 0; i < supportedDRMsCount; i++) {
-                byte[] uuid = new byte[16];
-                in.next().getBytesValue().copyTo(uuid, 0);
-
-                supportedSchemes[i] = bytesToUUID(uuid);
-
-                Log.v(TAG, "DrmInfoImpl() supportedScheme[" + i + "]: " +
-                      supportedSchemes[i]);
-            }
-
-            Log.v(TAG, "DrmInfoImpl() psshsize: " + pssh.length +
-                  " supportedDRMsCount: " + supportedDRMsCount);
-        }
-
-        private DrmInfoImpl makeCopy() {
-            return new DrmInfoImpl(this.mapPssh, this.supportedSchemes);
-        }
-
-        private String arrToHex(byte[] bytes) {
-            String out = "0x";
-            for (int i = 0; i < bytes.length; i++) {
-                out += String.format("%02x", bytes[i]);
-            }
-
-            return out;
-        }
-
-        private UUID bytesToUUID(byte[] uuid) {
-            long msb = 0, lsb = 0;
-            for (int i = 0; i < 8; i++) {
-                msb |= ( ((long)uuid[i]   & 0xff) << (8 * (7 - i)) );
-                lsb |= ( ((long)uuid[i+8] & 0xff) << (8 * (7 - i)) );
-            }
-
-            return new UUID(msb, lsb);
-        }
-
-        private Map<UUID, byte[]> parsePSSH(byte[] pssh, int psshsize) {
-            Map<UUID, byte[]> result = new HashMap<UUID, byte[]>();
-
-            final int UUID_SIZE = 16;
-            final int DATALEN_SIZE = 4;
-
-            int len = psshsize;
-            int numentries = 0;
-            int i = 0;
-
-            while (len > 0) {
-                if (len < UUID_SIZE) {
-                    Log.w(TAG, String.format("parsePSSH: len is too short to parse " +
-                                             "UUID: (%d < 16) pssh: %d", len, psshsize));
-                    return null;
-                }
-
-                byte[] subset = Arrays.copyOfRange(pssh, i, i + UUID_SIZE);
-                UUID uuid = bytesToUUID(subset);
-                i += UUID_SIZE;
-                len -= UUID_SIZE;
-
-                // get data length
-                if (len < 4) {
-                    Log.w(TAG, String.format("parsePSSH: len is too short to parse " +
-                                             "datalen: (%d < 4) pssh: %d", len, psshsize));
-                    return null;
-                }
-
-                subset = Arrays.copyOfRange(pssh, i, i+DATALEN_SIZE);
-                int datalen = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) ?
-                    ((subset[3] & 0xff) << 24) | ((subset[2] & 0xff) << 16) |
-                    ((subset[1] & 0xff) <<  8) |  (subset[0] & 0xff)          :
-                    ((subset[0] & 0xff) << 24) | ((subset[1] & 0xff) << 16) |
-                    ((subset[2] & 0xff) <<  8) |  (subset[3] & 0xff) ;
-                i += DATALEN_SIZE;
-                len -= DATALEN_SIZE;
-
-                if (len < datalen) {
-                    Log.w(TAG, String.format("parsePSSH: len is too short to parse " +
-                                             "data: (%d < %d) pssh: %d", len, datalen, psshsize));
-                    return null;
-                }
-
-                byte[] data = Arrays.copyOfRange(pssh, i, i+datalen);
-
-                // skip the data
-                i += datalen;
-                len -= datalen;
-
-                Log.v(TAG, String.format("parsePSSH[%d]: <%s, %s> pssh: %d",
-                                         numentries, uuid, arrToHex(data), psshsize));
-                numentries++;
-                result.put(uuid, data);
-            }
-
-            return result;
-        }
-
-    };  // DrmInfoImpl
-
-    /**
-     * Thrown when a DRM method is called before preparing a DRM scheme through prepareDrm().
-     * Extends MediaDrm.MediaDrmException
-     */
-    public static final class NoDrmSchemeExceptionImpl extends NoDrmSchemeException {
-        public NoDrmSchemeExceptionImpl(String detailMessage) {
-            super(detailMessage);
-        }
-    }
-
-    private native void _prepareDrm(@NonNull byte[] uuid, @NonNull byte[] drmSessionId);
-
-    // Modular DRM helpers
-
-    private void prepareDrm_createDrmStep(@NonNull UUID uuid)
-            throws UnsupportedSchemeException {
-        Log.v(TAG, "prepareDrm_createDrmStep: UUID: " + uuid);
-
-        try {
-            mDrmObj = new MediaDrm(uuid);
-            Log.v(TAG, "prepareDrm_createDrmStep: Created mDrmObj=" + mDrmObj);
-        } catch (Exception e) { // UnsupportedSchemeException
-            Log.e(TAG, "prepareDrm_createDrmStep: MediaDrm failed with " + e);
-            throw e;
-        }
-    }
-
-    private void prepareDrm_openSessionStep(@NonNull UUID uuid)
-            throws NotProvisionedException, ResourceBusyException {
-        Log.v(TAG, "prepareDrm_openSessionStep: uuid: " + uuid);
-
-        // TODO: don't need an open session for a future specialKeyReleaseDrm mode but we should do
-        // it anyway so it raises provisioning error if needed. We'd rather handle provisioning
-        // at prepareDrm/openSession rather than getDrmKeyRequest/provideDrmKeyResponse
-        try {
-            mDrmSessionId = mDrmObj.openSession();
-            Log.v(TAG, "prepareDrm_openSessionStep: mDrmSessionId=" + mDrmSessionId);
-
-            // Sending it down to native/mediaserver to create the crypto object
-            // This call could simply fail due to bad player state, e.g., after play().
-            _prepareDrm(getByteArrayFromUUID(uuid), mDrmSessionId);
-            Log.v(TAG, "prepareDrm_openSessionStep: _prepareDrm/Crypto succeeded");
-
-        } catch (Exception e) { //ResourceBusyException, NotProvisionedException
-            Log.e(TAG, "prepareDrm_openSessionStep: open/crypto failed with " + e);
-            throw e;
-        }
-
-    }
-
-    // Instantiated from the native side
-    @SuppressWarnings("unused")
-    private static class StreamEventCallback extends AudioTrack.StreamEventCallback {
-        public long mJAudioTrackPtr;
-        public long mNativeCallbackPtr;
-        public long mUserDataPtr;
-
-        public StreamEventCallback(long jAudioTrackPtr, long nativeCallbackPtr, long userDataPtr) {
-            super();
-            mJAudioTrackPtr = jAudioTrackPtr;
-            mNativeCallbackPtr = nativeCallbackPtr;
-            mUserDataPtr = userDataPtr;
-        }
-
-        @Override
-        public void onTearDown(AudioTrack track) {
-            native_stream_event_onTearDown(mNativeCallbackPtr, mUserDataPtr);
-        }
-
-        @Override
-        public void onPresentationEnded(AudioTrack track) {
-            native_stream_event_onStreamPresentationEnd(mNativeCallbackPtr, mUserDataPtr);
-        }
-
-        @Override
-        public void onDataRequest(AudioTrack track, int size) {
-            native_stream_event_onStreamDataRequest(
-                    mJAudioTrackPtr, mNativeCallbackPtr, mUserDataPtr);
-        }
-    }
-
-    private class ProvisioningThread extends Thread {
-        public static final int TIMEOUT_MS = 60000;
-
-        private UUID uuid;
-        private String urlStr;
-        private Object drmLock;
-        private MediaPlayer2Impl mediaPlayer;
-        private int status;
-        public  int status() {
-            return status;
-        }
-
-        public ProvisioningThread initialize(MediaDrm.ProvisionRequest request,
-                                          UUID uuid, MediaPlayer2Impl mediaPlayer) {
-            // lock is held by the caller
-            drmLock = mediaPlayer.mDrmLock;
-            this.mediaPlayer = mediaPlayer;
-
-            urlStr = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData());
-            this.uuid = uuid;
-
-            status = PREPARE_DRM_STATUS_PREPARATION_ERROR;
-
-            Log.v(TAG, "HandleProvisioninig: Thread is initialised url: " + urlStr);
-            return this;
-        }
-
-        public void run() {
-
-            byte[] response = null;
-            boolean provisioningSucceeded = false;
-            try {
-                URL url = new URL(urlStr);
-                final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
-                try {
-                    connection.setRequestMethod("POST");
-                    connection.setDoOutput(false);
-                    connection.setDoInput(true);
-                    connection.setConnectTimeout(TIMEOUT_MS);
-                    connection.setReadTimeout(TIMEOUT_MS);
-
-                    connection.connect();
-                    response = readInputStreamFully(connection.getInputStream());
-
-                    Log.v(TAG, "HandleProvisioninig: Thread run: response " +
-                            response.length + " " + response);
-                } catch (Exception e) {
-                    status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR;
-                    Log.w(TAG, "HandleProvisioninig: Thread run: connect " + e + " url: " + url);
-                } finally {
-                    connection.disconnect();
-                }
-            } catch (Exception e)   {
-                status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR;
-                Log.w(TAG, "HandleProvisioninig: Thread run: openConnection " + e);
-            }
-
-            if (response != null) {
-                try {
-                    mDrmObj.provideProvisionResponse(response);
-                    Log.v(TAG, "HandleProvisioninig: Thread run: " +
-                            "provideProvisionResponse SUCCEEDED!");
-
-                    provisioningSucceeded = true;
-                } catch (Exception e) {
-                    status = PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR;
-                    Log.w(TAG, "HandleProvisioninig: Thread run: " +
-                            "provideProvisionResponse " + e);
-                }
-            }
-
-            boolean succeeded = false;
-
-            synchronized (drmLock) {
-                // continuing with prepareDrm
-                if (provisioningSucceeded) {
-                    succeeded = mediaPlayer.resumePrepareDrm(uuid);
-                    status = (succeeded) ?
-                            PREPARE_DRM_STATUS_SUCCESS :
-                            PREPARE_DRM_STATUS_PREPARATION_ERROR;
-                }
-                mediaPlayer.mDrmProvisioningInProgress = false;
-                mediaPlayer.mPrepareDrmInProgress = false;
-                if (!succeeded) {
-                    cleanDrmObj();  // cleaning up if it hasn't gone through while in the lock
-                }
-            }  // synchronized
-
-            // calling the callback outside the lock
-            sendDrmEvent(new DrmEventNotifier() {
-                @Override
-                public void notify(DrmEventCallback callback) {
-                    callback.onDrmPrepared(
-                            mediaPlayer, getCurrentDataSource(), status);
-                }
-            });
-
-            synchronized (mTaskLock) {
-                if (mCurrentTask != null
-                        && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE_DRM
-                        && mCurrentTask.mNeedToWaitForEventToComplete) {
-                    mCurrentTask = null;
-                    processPendingTask_l();
-                }
-            }
-        }
-
-        /**
-         * Returns a byte[] containing the remainder of 'in', closing it when done.
-         */
-        private byte[] readInputStreamFully(InputStream in) throws IOException {
-            try {
-                return readInputStreamFullyNoClose(in);
-            } finally {
-                in.close();
-            }
-        }
-
-        /**
-         * Returns a byte[] containing the remainder of 'in'.
-         */
-        private byte[] readInputStreamFullyNoClose(InputStream in) throws IOException {
-            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
-            byte[] buffer = new byte[1024];
-            int count;
-            while ((count = in.read(buffer)) != -1) {
-                bytes.write(buffer, 0, count);
-            }
-            return bytes.toByteArray();
-        }
-    }   // ProvisioningThread
-
-    private int HandleProvisioninig(UUID uuid) {
-        synchronized (mDrmLock) {
-            if (mDrmProvisioningInProgress) {
-                Log.e(TAG, "HandleProvisioninig: Unexpected mDrmProvisioningInProgress");
-                return PREPARE_DRM_STATUS_PREPARATION_ERROR;
-            }
-
-            MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest();
-            if (provReq == null) {
-                Log.e(TAG, "HandleProvisioninig: getProvisionRequest returned null.");
-                return PREPARE_DRM_STATUS_PREPARATION_ERROR;
-            }
-
-            Log.v(TAG, "HandleProvisioninig provReq " +
-                    " data: " + provReq.getData() + " url: " + provReq.getDefaultUrl());
-
-            // networking in a background thread
-            mDrmProvisioningInProgress = true;
-
-            mDrmProvisioningThread = new ProvisioningThread().initialize(provReq, uuid, this);
-            mDrmProvisioningThread.start();
-
-            return PREPARE_DRM_STATUS_SUCCESS;
-        }
-    }
-
-    private boolean resumePrepareDrm(UUID uuid) {
-        Log.v(TAG, "resumePrepareDrm: uuid: " + uuid);
-
-        // mDrmLock is guaranteed to be held
-        boolean success = false;
-        try {
-            // resuming
-            prepareDrm_openSessionStep(uuid);
-
-            mDrmUUID = uuid;
-            mActiveDrmScheme = true;
-
-            success = true;
-        } catch (Exception e) {
-            Log.w(TAG, "HandleProvisioninig: Thread run _prepareDrm resume failed with " + e);
-            // mDrmObj clean up is done by the caller
-        }
-
-        return success;
-    }
-
-    private void resetDrmState() {
-        synchronized (mDrmLock) {
-            Log.v(TAG, "resetDrmState: " +
-                    " mDrmInfoImpl=" + mDrmInfoImpl +
-                    " mDrmProvisioningThread=" + mDrmProvisioningThread +
-                    " mPrepareDrmInProgress=" + mPrepareDrmInProgress +
-                    " mActiveDrmScheme=" + mActiveDrmScheme);
-
-            mDrmInfoResolved = false;
-            mDrmInfoImpl = null;
-
-            if (mDrmProvisioningThread != null) {
-                // timeout; relying on HttpUrlConnection
-                try {
-                    mDrmProvisioningThread.join();
-                }
-                catch (InterruptedException e) {
-                    Log.w(TAG, "resetDrmState: ProvThread.join Exception " + e);
-                }
-                mDrmProvisioningThread = null;
-            }
-
-            mPrepareDrmInProgress = false;
-            mActiveDrmScheme = false;
-
-            cleanDrmObj();
-        }   // synchronized
-    }
-
-    private void cleanDrmObj() {
-        // the caller holds mDrmLock
-        Log.v(TAG, "cleanDrmObj: mDrmObj=" + mDrmObj + " mDrmSessionId=" + mDrmSessionId);
-
-        if (mDrmSessionId != null)    {
-            mDrmObj.closeSession(mDrmSessionId);
-            mDrmSessionId = null;
-        }
-        if (mDrmObj != null) {
-            mDrmObj.release();
-            mDrmObj = null;
-        }
-    }
-
-    private static final byte[] getByteArrayFromUUID(@NonNull UUID uuid) {
-        long msb = uuid.getMostSignificantBits();
-        long lsb = uuid.getLeastSignificantBits();
-
-        byte[] uuidBytes = new byte[16];
-        for (int i = 0; i < 8; ++i) {
-            uuidBytes[i] = (byte)(msb >>> (8 * (7 - i)));
-            uuidBytes[8 + i] = (byte)(lsb >>> (8 * (7 - i)));
-        }
-
-        return uuidBytes;
-    }
-
-    // Modular DRM end
-
-
-    private static class TimedTextUtil {
-        // These keys must be in sync with the keys in TextDescription2.h
-        private static final int KEY_START_TIME                     = 7; // int
-        private static final int KEY_STRUCT_TEXT_POS               = 14; // TextPos
-        private static final int KEY_STRUCT_TEXT                   = 16; // Text
-        private static final int KEY_GLOBAL_SETTING               = 101;
-        private static final int KEY_LOCAL_SETTING                = 102;
-
-        private static TimedText parsePlayerMessage(PlayerMessage playerMsg) {
-            if (playerMsg.getValuesCount() == 0) {
-                return null;
-            }
-
-            String textChars = null;
-            Rect textBounds = null;
-            Iterator<Value> in = playerMsg.getValuesList().iterator();
-            int type = in.next().getInt32Value();
-            if (type == KEY_LOCAL_SETTING) {
-                type = in.next().getInt32Value();
-                if (type != KEY_START_TIME) {
-                    return null;
-                }
-                int startTimeMs = in.next().getInt32Value();
-
-                type = in.next().getInt32Value();
-                if (type != KEY_STRUCT_TEXT) {
-                    return null;
-                }
-
-                byte[] text = in.next().getBytesValue().toByteArray();
-                if (text == null || text.length == 0) {
-                    textChars = null;
-                } else {
-                    textChars = new String(text);
-                }
-
-            } else if (type != KEY_GLOBAL_SETTING) {
-                Log.w(TAG, "Invalid timed text key found: " + type);
-                return null;
-            }
-            if (in.hasNext()) {
-                type = in.next().getInt32Value();
-                if (type == KEY_STRUCT_TEXT_POS) {
-                    int top = in.next().getInt32Value();
-                    int left = in.next().getInt32Value();
-                    int bottom = in.next().getInt32Value();
-                    int right = in.next().getInt32Value();
-                    textBounds = new Rect(left, top, right, bottom);
-                }
-            }
-            return new TimedText(textChars, textBounds);
-        }
-    }
-
-    private Object addTask(Task task) {
-        synchronized (mTaskLock) {
-            mPendingTasks.add(task);
-            processPendingTask_l();
-        }
-        return task;
-    }
-
-    @GuardedBy("mTaskLock")
-    private void processPendingTask_l() {
-        if (mCurrentTask != null) {
-            return;
-        }
-        if (!mPendingTasks.isEmpty()) {
-            Task task = mPendingTasks.remove(0);
-            mCurrentTask = task;
-            mTaskHandler.post(task);
-        }
-    }
-
-    private abstract class Task implements Runnable {
-        private final int mMediaCallType;
-        private final boolean mNeedToWaitForEventToComplete;
-        private DataSourceDesc mDSD;
-
-        public Task (int mediaCallType, boolean needToWaitForEventToComplete) {
-            mMediaCallType = mediaCallType;
-            mNeedToWaitForEventToComplete = needToWaitForEventToComplete;
-        }
-
-        abstract void process() throws IOException, NoDrmSchemeException;
-
-        @Override
-        public void run() {
-            int status = CALL_STATUS_NO_ERROR;
-            try {
-                if (mMediaCallType != CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED
-                        && getState() == PLAYER_STATE_ERROR) {
-                    status = CALL_STATUS_INVALID_OPERATION;
-                } else {
-                    if (mMediaCallType == CALL_COMPLETED_SEEK_TO) {
-                        synchronized (mTaskLock) {
-                            if (!mPendingTasks.isEmpty()) {
-                                Task nextTask = mPendingTasks.get(0);
-                                if (nextTask.mMediaCallType == mMediaCallType) {
-                                    throw new CommandSkippedException(
-                                            "consecutive seekTo is skipped except last one");
-                                }
-                            }
-                        }
-                    }
-                    process();
-                }
-            } catch (IllegalStateException e) {
-                status = CALL_STATUS_INVALID_OPERATION;
-            } catch (IllegalArgumentException e) {
-                status = CALL_STATUS_BAD_VALUE;
-            } catch (SecurityException e) {
-                status = CALL_STATUS_PERMISSION_DENIED;
-            } catch (IOException e) {
-                status = CALL_STATUS_ERROR_IO;
-            } catch (NoDrmSchemeException e) {
-                status = CALL_STATUS_NO_DRM_SCHEME;
-            } catch (CommandSkippedException e) {
-                status = CALL_STATUS_SKIPPED;
-            } catch (Exception e) {
-                status = CALL_STATUS_ERROR_UNKNOWN;
-            }
-            mDSD = getCurrentDataSource();
-
-            if (mMediaCallType != CALL_COMPLETED_SEEK_TO) {
-                synchronized (mTaskLock) {
-                    mIsPreviousCommandSeekTo = false;
-                }
-            }
-
-            // TODO: Make native implementations asynchronous and let them send notifications.
-            if (!mNeedToWaitForEventToComplete || status != CALL_STATUS_NO_ERROR) {
-
-                sendCompleteNotification(status);
-
-                synchronized (mTaskLock) {
-                    mCurrentTask = null;
-                    processPendingTask_l();
-                }
-            }
-        }
-
-        private void sendCompleteNotification(int status) {
-            // In {@link #notifyWhenCommandLabelReached} case, a separate callback
-            // {@link #onCommandLabelReached} is already called in {@code process()}.
-            // CALL_COMPLETED_PREPARE_DRM is sent via DrmEventCallback#onDrmPrepared
-            if (mMediaCallType == CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED
-                    || mMediaCallType == CALL_COMPLETED_PREPARE_DRM) {
-                return;
-            }
-            sendEvent(new EventNotifier() {
-                @Override
-                public void notify(EventCallback callback) {
-                    callback.onCallCompleted(
-                            MediaPlayer2Impl.this, mDSD, mMediaCallType, status);
-                }
-            });
-        }
-    };
-
-    private final class CommandSkippedException extends RuntimeException {
-        public CommandSkippedException(String detailMessage) {
-            super(detailMessage);
-        }
-    };
-
-    private final class SourceInfo {
-        final DataSourceDesc mDSD;
-        final long mId = mSrcIdGenerator.getAndIncrement();
-        AtomicInteger mBufferedPercentage = new AtomicInteger(0);
-
-        // m*AsNextSource (below) only applies to pending data sources in the playlist;
-        // the meanings of mCurrentSourceInfo.{mStateAsNextSource,mPlayPendingAsNextSource}
-        // are undefined.
-        int mStateAsNextSource = NEXT_SOURCE_STATE_INIT;
-        boolean mPlayPendingAsNextSource = false;
-
-        SourceInfo(DataSourceDesc dsd) {
-            this.mDSD = dsd;
-        }
-
-        @Override
-        public String toString() {
-            return String.format("%s(%d)", SourceInfo.class.getName(), mId);
-        }
-
-    }
-
-    private SourceInfo getSourceInfoById(long srcId) {
-        synchronized (mSrcLock) {
-            if (isCurrentSource(srcId)) {
-                return mCurrentSourceInfo;
-            }
-            if (isNextSource(srcId)) {
-                return mNextSourceInfos.peek();
-            }
-        }
-        return null;
-    }
-
-    private boolean isCurrentSource(long srcId) {
-        synchronized (mSrcLock) {
-            return mCurrentSourceInfo != null && mCurrentSourceInfo.mId == srcId;
-        }
-    }
-
-    private boolean isNextSource(long srcId) {
-        SourceInfo nextSourceInfo = mNextSourceInfos.peek();
-        return nextSourceInfo != null && nextSourceInfo.mId == srcId;
-    }
-
-}
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 900e3bb..b5e2213 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -90,6 +90,8 @@
      * int, android.content.Intent)}
      * @param resultData The resulting data from {@link android.app.Activity#onActivityResult(int,
      * int, android.content.Intent)}
+     * @throws IllegalStateException on pre-Q devices if a previously gotten MediaProjection
+     * from the same {@code resultData} has not yet been stopped
      */
     public MediaProjection getMediaProjection(int resultCode, @NonNull Intent resultData) {
         if (resultCode != Activity.RESULT_OK || resultData == null) {
diff --git a/media/java/android/media/update/MediaControlView2Provider.java b/media/java/android/media/update/MediaControlView2Provider.java
deleted file mode 100644
index 8e69653..0000000
--- a/media/java/android/media/update/MediaControlView2Provider.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 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 android.media.update;
-
-import android.media.SessionToken2;
-import android.media.session.MediaController;
-import android.util.AttributeSet;
-import android.widget.MediaControlView2;
-
-/**
- * Interface for connecting the public API to an updatable implementation.
- *
- * Each instance object is connected to one corresponding updatable object which implements the
- * runtime behavior of that class. There should a corresponding provider method for all public
- * methods.
- *
- * All methods behave as per their namesake in the public API.
- *
- * @see android.widget.MediaControlView2
- *
- * @hide
- */
-// TODO: @SystemApi
-public interface MediaControlView2Provider extends ViewGroupProvider {
-    void initialize(AttributeSet attrs, int defStyleAttr, int defStyleRes);
-
-    void setMediaSessionToken_impl(SessionToken2 token);
-    void setOnFullScreenListener_impl(MediaControlView2.OnFullScreenListener l);
-    /**
-     * @hide TODO: remove
-     */
-    void setController_impl(MediaController controller);
-    /**
-     * @hide
-     */
-    void setButtonVisibility_impl(int button, int visibility);
-    void requestPlayButtonFocus_impl();
-}
diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java
index 8687b80..2bc17a9 100644
--- a/media/java/android/media/update/StaticProvider.java
+++ b/media/java/android/media/update/StaticProvider.java
@@ -16,7 +16,6 @@
 
 package android.media.update;
 
-import android.annotation.Nullable;
 import android.app.Notification;
 import android.content.Context;
 import android.media.MediaBrowser2;
@@ -48,9 +47,6 @@
 import android.media.update.MediaSessionService2Provider.MediaNotificationProvider;
 import android.os.Bundle;
 import android.os.IInterface;
-import android.util.AttributeSet;
-import android.widget.MediaControlView2;
-import android.widget.VideoView2;
 
 import java.util.concurrent.Executor;
 
@@ -62,13 +58,6 @@
  * @hide
  */
 public interface StaticProvider {
-    MediaControlView2Provider createMediaControlView2(MediaControlView2 instance,
-            ViewGroupProvider superProvider, ViewGroupProvider privateProvider,
-            @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes);
-    VideoView2Provider createVideoView2(VideoView2 instance,
-            ViewGroupProvider superProvider, ViewGroupProvider privateProvider,
-            @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes);
-
     CommandProvider createMediaSession2Command(SessionCommand2 instance,
             int commandCode, String action, Bundle extra);
     SessionCommand2 fromBundle_MediaSession2Command(Bundle bundle);
diff --git a/media/java/android/media/update/VideoView2Provider.java b/media/java/android/media/update/VideoView2Provider.java
deleted file mode 100644
index 27b436f..0000000
--- a/media/java/android/media/update/VideoView2Provider.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * 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.update;
-
-import android.annotation.SystemApi;
-import android.media.AudioAttributes;
-import android.media.DataSourceDesc;
-import android.media.MediaItem2;
-import android.media.MediaMetadata2;
-import android.media.MediaPlayerBase;
-import android.media.SessionToken2;
-import android.media.session.MediaController;
-import android.media.session.PlaybackState;
-import android.media.session.MediaSession;
-import android.net.Uri;
-import android.util.AttributeSet;
-import android.widget.MediaControlView2;
-import android.widget.VideoView2;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Executor;
-
-/**
- * Interface for connecting the public API to an updatable implementation.
- *
- * Each instance object is connected to one corresponding updatable object which implements the
- * runtime behavior of that class. There should a corresponding provider method for all public
- * methods.
- *
- * All methods behave as per their namesake in the public API.
- *
- * @see android.widget.VideoView2
- *
- * @hide
- */
-// TODO @SystemApi
-public interface VideoView2Provider extends ViewGroupProvider {
-    void initialize(AttributeSet attrs, int defStyleAttr, int defStyleRes);
-
-    void setMediaControlView2_impl(MediaControlView2 mediaControlView, long intervalMs);
-    void setMediaMetadata_impl(MediaMetadata2 metadata);
-    /**
-     * @hide TODO: remove
-     */
-    MediaController getMediaController_impl();
-    SessionToken2 getMediaSessionToken_impl();
-    MediaControlView2 getMediaControlView2_impl();
-    MediaMetadata2 getMediaMetadata_impl();
-    void setSubtitleEnabled_impl(boolean enable);
-    boolean isSubtitleEnabled_impl();
-    // TODO: remove setSpeed_impl once MediaController2 is ready.
-    void setSpeed_impl(float speed);
-    void setAudioFocusRequest_impl(int focusGain);
-    void setAudioAttributes_impl(AudioAttributes attributes);
-    void setVideoPath_impl(String path);
-    /**
-     * @hide TODO: remove
-     */
-    void setVideoUri_impl(Uri uri);
-    /**
-     * @hide TODO: remove
-     */
-    void setVideoUri_impl(Uri uri, Map<String, String> headers);
-    void setMediaItem_impl(MediaItem2 mediaItem);
-    void setDataSource_impl(DataSourceDesc dsd);
-    void setViewType_impl(int viewType);
-    int getViewType_impl();
-    /**
-     * @hide TODO: remove
-     */
-    void setCustomActions_impl(List<PlaybackState.CustomAction> actionList,
-            Executor executor, VideoView2.OnCustomActionListener listener);
-    /**
-     * @hide
-     */
-    @VisibleForTesting
-    void setOnViewTypeChangedListener_impl(VideoView2.OnViewTypeChangedListener l);
-    /**
-     * @hide TODO: remove
-     */
-    void setFullScreenRequestListener_impl(VideoView2.OnFullScreenRequestListener l);
-}
diff --git a/media/java/android/media/update/ViewGroupHelper.java b/media/java/android/media/update/ViewGroupHelper.java
deleted file mode 100644
index 6b4f15d..0000000
--- a/media/java/android/media/update/ViewGroupHelper.java
+++ /dev/null
@@ -1,369 +0,0 @@
-/*
- * 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.update;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * Helper class for connecting the public API to an updatable implementation.
- *
- * @see ViewGroupProvider
- *
- * @hide
- */
-public abstract class ViewGroupHelper<T extends ViewGroupProvider> extends ViewGroup {
-    /** @hide */
-    final public T mProvider;
-
-    /** @hide */
-    public ViewGroupHelper(ProviderCreator<T> creator,
-            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-
-        mProvider = creator.createProvider(this, new SuperProvider(),
-                new PrivateProvider());
-    }
-
-    /** @hide */
-    // TODO @SystemApi
-    public T getProvider() {
-        return mProvider;
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        mProvider.onAttachedToWindow_impl();
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        mProvider.onDetachedFromWindow_impl();
-    }
-
-    @Override
-    public CharSequence getAccessibilityClassName() {
-        return mProvider.getAccessibilityClassName_impl();
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        return mProvider.onTouchEvent_impl(ev);
-    }
-
-    @Override
-    public boolean onTrackballEvent(MotionEvent ev) {
-        return mProvider.onTrackballEvent_impl(ev);
-    }
-
-    @Override
-    public void onFinishInflate() {
-        mProvider.onFinishInflate_impl();
-    }
-
-    @Override
-    public void setEnabled(boolean enabled) {
-        mProvider.setEnabled_impl(enabled);
-    }
-
-    @Override
-    public void onVisibilityAggregated(boolean isVisible) {
-        mProvider.onVisibilityAggregated_impl(isVisible);
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        mProvider.onLayout_impl(changed, left, top, right, bottom);
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        mProvider.onMeasure_impl(widthMeasureSpec, heightMeasureSpec);
-    }
-
-    @Override
-    protected int getSuggestedMinimumWidth() {
-        return mProvider.getSuggestedMinimumWidth_impl();
-    }
-
-    @Override
-    protected int getSuggestedMinimumHeight() {
-        return mProvider.getSuggestedMinimumHeight_impl();
-    }
-
-    // setMeasuredDimension is final
-
-    @Override
-    public boolean dispatchTouchEvent(MotionEvent ev) {
-        return mProvider.dispatchTouchEvent_impl(ev);
-    }
-
-    @Override
-    protected boolean checkLayoutParams(LayoutParams p) {
-        return mProvider.checkLayoutParams_impl(p);
-    }
-
-    @Override
-    protected LayoutParams generateDefaultLayoutParams() {
-        return mProvider.generateDefaultLayoutParams_impl();
-    }
-
-    @Override
-    public LayoutParams generateLayoutParams(AttributeSet attrs) {
-        return mProvider.generateLayoutParams_impl(attrs);
-    }
-
-    @Override
-    protected LayoutParams generateLayoutParams(LayoutParams lp) {
-        return mProvider.generateLayoutParams_impl(lp);
-    }
-
-    @Override
-    public boolean shouldDelayChildPressedState() {
-        return mProvider.shouldDelayChildPressedState_impl();
-    }
-
-    @Override
-    protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
-            int parentHeightMeasureSpec, int heightUsed) {
-        mProvider.measureChildWithMargins_impl(child,
-                parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);
-    }
-
-    /** @hide */
-    public class SuperProvider implements ViewGroupProvider {
-        @Override
-        public CharSequence getAccessibilityClassName_impl() {
-            return ViewGroupHelper.super.getAccessibilityClassName();
-        }
-
-        @Override
-        public boolean onTouchEvent_impl(MotionEvent ev) {
-            return ViewGroupHelper.super.onTouchEvent(ev);
-        }
-
-        @Override
-        public boolean onTrackballEvent_impl(MotionEvent ev) {
-            return ViewGroupHelper.super.onTrackballEvent(ev);
-        }
-
-        @Override
-        public void onFinishInflate_impl() {
-            ViewGroupHelper.super.onFinishInflate();
-        }
-
-        @Override
-        public void setEnabled_impl(boolean enabled) {
-            ViewGroupHelper.super.setEnabled(enabled);
-        }
-
-        @Override
-        public void onAttachedToWindow_impl() {
-            ViewGroupHelper.super.onAttachedToWindow();
-        }
-
-        @Override
-        public void onDetachedFromWindow_impl() {
-            ViewGroupHelper.super.onDetachedFromWindow();
-        }
-
-        @Override
-        public void onVisibilityAggregated_impl(boolean isVisible) {
-            ViewGroupHelper.super.onVisibilityAggregated(isVisible);
-        }
-
-        @Override
-        public void onLayout_impl(boolean changed, int left, int top, int right, int bottom) {
-            // abstract method; no super
-        }
-
-        @Override
-        public void onMeasure_impl(int widthMeasureSpec, int heightMeasureSpec) {
-            ViewGroupHelper.super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        }
-
-        @Override
-        public int getSuggestedMinimumWidth_impl() {
-            return ViewGroupHelper.super.getSuggestedMinimumWidth();
-        }
-
-        @Override
-        public int getSuggestedMinimumHeight_impl() {
-            return ViewGroupHelper.super.getSuggestedMinimumHeight();
-        }
-
-        @Override
-        public void setMeasuredDimension_impl(int measuredWidth, int measuredHeight) {
-            ViewGroupHelper.super.setMeasuredDimension(measuredWidth, measuredHeight);
-        }
-
-        @Override
-        public boolean dispatchTouchEvent_impl(MotionEvent ev) {
-            return ViewGroupHelper.super.dispatchTouchEvent(ev);
-        }
-
-        @Override
-        public boolean checkLayoutParams_impl(LayoutParams p) {
-            return ViewGroupHelper.super.checkLayoutParams(p);
-        }
-
-        @Override
-        public LayoutParams generateDefaultLayoutParams_impl() {
-            return ViewGroupHelper.super.generateDefaultLayoutParams();
-        }
-
-        @Override
-        public LayoutParams generateLayoutParams_impl(AttributeSet attrs) {
-            return ViewGroupHelper.super.generateLayoutParams(attrs);
-        }
-
-        @Override
-        public LayoutParams generateLayoutParams_impl(LayoutParams lp) {
-            return ViewGroupHelper.super.generateLayoutParams(lp);
-        }
-
-        @Override
-        public boolean shouldDelayChildPressedState_impl() {
-            return ViewGroupHelper.super.shouldDelayChildPressedState();
-        }
-
-        @Override
-        public void measureChildWithMargins_impl(View child,
-                int parentWidthMeasureSpec, int widthUsed,
-                int parentHeightMeasureSpec, int heightUsed) {
-            ViewGroupHelper.super.measureChildWithMargins(child,
-                    parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);
-        }
-    }
-
-    /** @hide */
-    public class PrivateProvider implements ViewGroupProvider {
-        @Override
-        public CharSequence getAccessibilityClassName_impl() {
-            return ViewGroupHelper.this.getAccessibilityClassName();
-        }
-
-        @Override
-        public boolean onTouchEvent_impl(MotionEvent ev) {
-            return ViewGroupHelper.this.onTouchEvent(ev);
-        }
-
-        @Override
-        public boolean onTrackballEvent_impl(MotionEvent ev) {
-            return ViewGroupHelper.this.onTrackballEvent(ev);
-        }
-
-        @Override
-        public void onFinishInflate_impl() {
-            ViewGroupHelper.this.onFinishInflate();
-        }
-
-        @Override
-        public void setEnabled_impl(boolean enabled) {
-            ViewGroupHelper.this.setEnabled(enabled);
-        }
-
-        @Override
-        public void onAttachedToWindow_impl() {
-            ViewGroupHelper.this.onAttachedToWindow();
-        }
-
-        @Override
-        public void onDetachedFromWindow_impl() {
-            ViewGroupHelper.this.onDetachedFromWindow();
-        }
-
-        @Override
-        public void onVisibilityAggregated_impl(boolean isVisible) {
-            ViewGroupHelper.this.onVisibilityAggregated(isVisible);
-        }
-
-        @Override
-        public void onLayout_impl(boolean changed, int left, int top, int right, int bottom) {
-            ViewGroupHelper.this.onLayout(changed, left, top, right, bottom);
-        }
-
-        @Override
-        public void onMeasure_impl(int widthMeasureSpec, int heightMeasureSpec) {
-            ViewGroupHelper.this.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        }
-
-        @Override
-        public int getSuggestedMinimumWidth_impl() {
-            return ViewGroupHelper.this.getSuggestedMinimumWidth();
-        }
-
-        @Override
-        public int getSuggestedMinimumHeight_impl() {
-            return ViewGroupHelper.this.getSuggestedMinimumHeight();
-        }
-
-        @Override
-        public void setMeasuredDimension_impl(int measuredWidth, int measuredHeight) {
-            ViewGroupHelper.this.setMeasuredDimension(measuredWidth, measuredHeight);
-        }
-
-        @Override
-        public boolean dispatchTouchEvent_impl(MotionEvent ev) {
-            return ViewGroupHelper.this.dispatchTouchEvent(ev);
-        }
-
-        @Override
-        public boolean checkLayoutParams_impl(LayoutParams p) {
-            return ViewGroupHelper.this.checkLayoutParams(p);
-        }
-
-        @Override
-        public LayoutParams generateDefaultLayoutParams_impl() {
-            return ViewGroupHelper.this.generateDefaultLayoutParams();
-        }
-
-        @Override
-        public LayoutParams generateLayoutParams_impl(AttributeSet attrs) {
-            return ViewGroupHelper.this.generateLayoutParams(attrs);
-        }
-
-        @Override
-        public LayoutParams generateLayoutParams_impl(LayoutParams lp) {
-            return ViewGroupHelper.this.generateLayoutParams(lp);
-        }
-
-        @Override
-        public boolean shouldDelayChildPressedState_impl() {
-            return ViewGroupHelper.this.shouldDelayChildPressedState();
-        }
-
-        @Override
-        public void measureChildWithMargins_impl(View child,
-                int parentWidthMeasureSpec, int widthUsed,
-                int parentHeightMeasureSpec, int heightUsed) {
-            ViewGroupHelper.this.measureChildWithMargins(child,
-                    parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);
-        }
-    }
-
-        /** @hide */
-    @FunctionalInterface
-    public interface ProviderCreator<T extends ViewGroupProvider> {
-        T createProvider(ViewGroupHelper<T> instance, ViewGroupProvider superProvider,
-                ViewGroupProvider privateProvider);
-    }
-}
diff --git a/media/java/android/media/update/ViewGroupProvider.java b/media/java/android/media/update/ViewGroupProvider.java
deleted file mode 100644
index 67e8cea..0000000
--- a/media/java/android/media/update/ViewGroupProvider.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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.update;
-
-import android.annotation.SystemApi;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup.LayoutParams;
-
-/**
- * Interface for connecting the public API to an updatable implementation.
- *
- * Each instance object is connected to one corresponding updatable object which implements the
- * runtime behavior of that class. There should a corresponding provider method for all public
- * methods.
- *
- * All methods behave as per their namesake in the public API.
- *
- * @see android.view.View
- *
- * @hide
- */
-// TODO @SystemApi
-public interface ViewGroupProvider {
-    // View methods
-    void onAttachedToWindow_impl();
-    void onDetachedFromWindow_impl();
-    CharSequence getAccessibilityClassName_impl();
-    boolean onTouchEvent_impl(MotionEvent ev);
-    boolean onTrackballEvent_impl(MotionEvent ev);
-    void onFinishInflate_impl();
-    void setEnabled_impl(boolean enabled);
-    void onVisibilityAggregated_impl(boolean isVisible);
-    void onLayout_impl(boolean changed, int left, int top, int right, int bottom);
-    void onMeasure_impl(int widthMeasureSpec, int heightMeasureSpec);
-    int getSuggestedMinimumWidth_impl();
-    int getSuggestedMinimumHeight_impl();
-    void setMeasuredDimension_impl(int measuredWidth, int measuredHeight);
-    boolean dispatchTouchEvent_impl(MotionEvent ev);
-
-    // ViewGroup methods
-    boolean checkLayoutParams_impl(LayoutParams p);
-    LayoutParams generateDefaultLayoutParams_impl();
-    LayoutParams generateLayoutParams_impl(AttributeSet attrs);
-    LayoutParams generateLayoutParams_impl(LayoutParams lp);
-    boolean shouldDelayChildPressedState_impl();
-    void measureChildWithMargins_impl(View child, int parentWidthMeasureSpec, int widthUsed,
-        int parentHeightMeasureSpec, int heightUsed);
-
-    // ViewManager methods
-    // ViewParent methods
-}
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 0f531c9..faf4301 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -97,7 +97,6 @@
     shared_libs: [
         "android.hardware.cas@1.0",  // for CasManager. VNDK???
         "android.hardware.cas.native@1.0",  // CasManager. VNDK???
-        "libaudioclient",  // for use of AudioTrack, AudioSystem. to be removed
         "libbinder",
         "libgui",  // for VideoFrameScheduler
         "libhidlallocatorutils",
diff --git a/media/jni/android_media_MediaPlayer2.cpp b/media/jni/android_media_MediaPlayer2.cpp
index c49e720..f7de2e7 100644
--- a/media/jni/android_media_MediaPlayer2.cpp
+++ b/media/jni/android_media_MediaPlayer2.cpp
@@ -175,7 +175,7 @@
     // that posts events to the application thread.
     jclass clazz = env->GetObjectClass(thiz);
     if (clazz == NULL) {
-        ALOGE("Can't find android/media/MediaPlayer2Impl");
+        ALOGE("Can't find android/media/MediaPlayer2");
         jniThrowException(env, "java/lang/Exception", NULL);
         return;
     }
@@ -488,7 +488,7 @@
     env->SetLongField(thiz, fields.surface_texture, (jlong)anw);
 
     // This will fail if the media player has not been initialized yet. This
-    // can be the case if setDisplay() on MediaPlayer2Impl.java has been called
+    // can be the case if setDisplay() on MediaPlayer2.java has been called
     // before setDataSource(). The redundant call to setVideoSurfaceTexture()
     // in prepare/prepare covers for this case.
     mp->setVideoSurfaceTexture(new ANativeWindowWrapper(anw));
@@ -950,7 +950,7 @@
 {
     jclass clazz;
 
-    clazz = env->FindClass("android/media/MediaPlayer2Impl");
+    clazz = env->FindClass("android/media/MediaPlayer2");
     if (clazz == NULL) {
         return;
     }
@@ -1009,10 +1009,11 @@
 }
 
 static void
-android_media_MediaPlayer2_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
+android_media_MediaPlayer2_native_setup(JNIEnv *env, jobject thiz,
+        jint sessionId, jobject weak_this)
 {
     ALOGV("native_setup");
-    sp<MediaPlayer2> mp = MediaPlayer2::Create();
+    sp<MediaPlayer2> mp = MediaPlayer2::Create(sessionId);
     if (mp == NULL) {
         jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
         return;
@@ -1395,21 +1396,21 @@
     {"nativePlayNextDataSource", "(J)V",                        (void *)android_media_MediaPlayer2_playNextDataSource},
     {"native_setVideoSurface", "(Landroid/view/Surface;)V",     (void *)android_media_MediaPlayer2_setVideoSurface},
     {"getBufferingParams", "()Landroid/media/BufferingParams;", (void *)android_media_MediaPlayer2_getBufferingParams},
-    {"_setBufferingParams", "(Landroid/media/BufferingParams;)V", (void *)android_media_MediaPlayer2_setBufferingParams},
-    {"_prepare",            "()V",                              (void *)android_media_MediaPlayer2_prepare},
-    {"_start",              "()V",                              (void *)android_media_MediaPlayer2_start},
+    {"native_setBufferingParams", "(Landroid/media/BufferingParams;)V", (void *)android_media_MediaPlayer2_setBufferingParams},
+    {"native_prepare",      "()V",                              (void *)android_media_MediaPlayer2_prepare},
+    {"native_start",        "()V",                              (void *)android_media_MediaPlayer2_start},
     {"native_getState",     "()I",                              (void *)android_media_MediaPlayer2_getState},
     {"native_getMetrics",   "()Landroid/os/PersistableBundle;", (void *)android_media_MediaPlayer2_native_getMetrics},
-    {"_setPlaybackParams", "(Landroid/media/PlaybackParams;)V", (void *)android_media_MediaPlayer2_setPlaybackParams},
+    {"native_setPlaybackParams", "(Landroid/media/PlaybackParams;)V", (void *)android_media_MediaPlayer2_setPlaybackParams},
     {"getPlaybackParams", "()Landroid/media/PlaybackParams;",   (void *)android_media_MediaPlayer2_getPlaybackParams},
-    {"_setSyncParams",     "(Landroid/media/SyncParams;)V",     (void *)android_media_MediaPlayer2_setSyncParams},
+    {"native_setSyncParams",     "(Landroid/media/SyncParams;)V",     (void *)android_media_MediaPlayer2_setSyncParams},
     {"getSyncParams",     "()Landroid/media/SyncParams;",       (void *)android_media_MediaPlayer2_getSyncParams},
-    {"_seekTo",             "(JI)V",                            (void *)android_media_MediaPlayer2_seekTo},
-    {"_pause",              "()V",                              (void *)android_media_MediaPlayer2_pause},
+    {"native_seekTo",       "(JI)V",                            (void *)android_media_MediaPlayer2_seekTo},
+    {"native_pause",        "()V",                              (void *)android_media_MediaPlayer2_pause},
     {"getCurrentPosition",  "()J",                              (void *)android_media_MediaPlayer2_getCurrentPosition},
     {"getDuration",         "()J",                              (void *)android_media_MediaPlayer2_getDuration},
-    {"_release",            "()V",                              (void *)android_media_MediaPlayer2_release},
-    {"_reset",              "()V",                              (void *)android_media_MediaPlayer2_reset},
+    {"native_release",      "()V",                              (void *)android_media_MediaPlayer2_release},
+    {"native_reset",        "()V",                              (void *)android_media_MediaPlayer2_reset},
     {"native_setAudioAttributes", "(Landroid/media/AudioAttributes;)Z", (void *)android_media_MediaPlayer2_setAudioAttributes},
     {"native_getAudioAttributes", "()Landroid/media/AudioAttributes;", (void *)android_media_MediaPlayer2_getAudioAttributes},
     {"setLooping",          "(Z)V",                             (void *)android_media_MediaPlayer2_setLooping},
@@ -1417,15 +1418,15 @@
     {"native_setVolume",    "(F)V",                             (void *)android_media_MediaPlayer2_setVolume},
     {"native_invoke",       "([B)[B",                           (void *)android_media_MediaPlayer2_invoke},
     {"native_init",         "()V",                              (void *)android_media_MediaPlayer2_native_init},
-    {"native_setup",        "(Ljava/lang/Object;)V",            (void *)android_media_MediaPlayer2_native_setup},
+    {"native_setup",        "(ILjava/lang/Object;)V",           (void *)android_media_MediaPlayer2_native_setup},
     {"native_finalize",     "()V",                              (void *)android_media_MediaPlayer2_native_finalize},
     {"getAudioSessionId",   "()I",                              (void *)android_media_MediaPlayer2_getAudioSessionId},
-    {"_setAudioSessionId",   "(I)V",                             (void *)android_media_MediaPlayer2_setAudioSessionId},
-    {"_setAuxEffectSendLevel", "(F)V",                          (void *)android_media_MediaPlayer2_setAuxEffectSendLevel},
-    {"_attachAuxEffect",     "(I)V",                             (void *)android_media_MediaPlayer2_attachAuxEffect},
+    {"native_setAudioSessionId", "(I)V",                        (void *)android_media_MediaPlayer2_setAudioSessionId},
+    {"native_setAuxEffectSendLevel", "(F)V",                    (void *)android_media_MediaPlayer2_setAuxEffectSendLevel},
+    {"native_attachAuxEffect", "(I)V",                          (void *)android_media_MediaPlayer2_attachAuxEffect},
     // Modular DRM
-    { "_prepareDrm", "([B[B)V",                                 (void *)android_media_MediaPlayer2_prepareDrm },
-    { "_releaseDrm", "()V",                                     (void *)android_media_MediaPlayer2_releaseDrm },
+    { "native_prepareDrm", "([B[B)V",                           (void *)android_media_MediaPlayer2_prepareDrm },
+    { "native_releaseDrm", "()V",                               (void *)android_media_MediaPlayer2_releaseDrm },
 
     // AudioRouting
     {"native_setPreferredDevice", "(Landroid/media/AudioDeviceInfo;)Z", (void *)android_media_MediaPlayer2_setPreferredDevice},
@@ -1441,9 +1442,9 @@
 };
 
 // This function only registers the native methods
-static int register_android_media_MediaPlayer2Impl(JNIEnv *env)
+static int register_android_media_MediaPlayer2(JNIEnv *env)
 {
-    return jniRegisterNativeMethods(env, "android/media/MediaPlayer2Impl", gMethods, NELEM(gMethods));
+    return jniRegisterNativeMethods(env, "android/media/MediaPlayer2", gMethods, NELEM(gMethods));
 }
 
 jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
@@ -1457,7 +1458,7 @@
     }
     assert(env != NULL);
 
-    if (register_android_media_MediaPlayer2Impl(env) < 0) {
+    if (register_android_media_MediaPlayer2(env) < 0) {
         ALOGE("ERROR: MediaPlayer2 native registration failed\n");
         goto bail;
     }
diff --git a/packages/SettingsLib/res/drawable/list_divider_dark.xml b/packages/SettingsLib/res/drawable/list_divider_dark.xml
deleted file mode 100644
index 5773d9e..0000000
--- a/packages/SettingsLib/res/drawable/list_divider_dark.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright (C) 2017 The Android Open Source Project
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-
-<shape
-    xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="#64000000" />
-    <size
-        android:height="1dp"
-        android:width="1dp" />
-</shape>
diff --git a/packages/SettingsLib/res/layout/preference_two_target_divider.xml b/packages/SettingsLib/res/layout/preference_two_target_divider.xml
index 60efed4..b81dd83 100644
--- a/packages/SettingsLib/res/layout/preference_two_target_divider.xml
+++ b/packages/SettingsLib/res/layout/preference_two_target_divider.xml
@@ -27,5 +27,5 @@
     <View
         android:layout_width="1dp"
         android:layout_height="match_parent"
-        android:background="@drawable/list_divider_dark" />
+        android:background="?android:attr/listDivider" />
 </LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values/colors.xml b/packages/SettingsLib/res/values/colors.xml
index 02b7ea6..66bbb3a 100644
--- a/packages/SettingsLib/res/values/colors.xml
+++ b/packages/SettingsLib/res/values/colors.xml
@@ -18,4 +18,5 @@
     <color name="disabled_text_color">#66000000</color> <!-- 38% black -->
 
     <color name="usage_graph_dots">@*android:color/tertiary_device_default_settings</color>
+    <color name="list_divider_color">#64000000</color>
 </resources>
diff --git a/packages/SystemUI/res/drawable/ic_home_button_outline.xml b/packages/SystemUI/res/drawable/ic_home_button_outline.xml
new file mode 100644
index 0000000..5bf345d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_home_button_outline.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="28dp"
+        android:height="10dp"
+        android:viewportWidth="28"
+        android:viewportHeight="10">
+    <path
+        android:pathData="M23,1H5C2.7909,1 1,2.7909 1,5C1,7.2091 2.7909,9 5,9H23C25.2091,9 27,7.2091 27,5C27,2.7909 25.2091,1 23,1ZM5,0C2.2386,0 0,2.2386 0,5C0,7.7614 2.2386,10 5,10H23C25.7614,10 28,7.7614 28,5C28,2.2386 25.7614,0 23,0H5Z"
+        android:fillColor="?attr/wallpaperTextColor"
+        android:fillType="evenOdd"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_notification_block.xml b/packages/SystemUI/res/drawable/ic_notification_block.xml
new file mode 100644
index 0000000..572e97b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_notification_block.xml
@@ -0,0 +1,25 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M12.0,2.0C6.48,2.0 2.0,6.48 2.0,12.0s4.48,10.0 10.0,10.0 10.0,-4.48 10.0,-10.0S17.52,2.0 12.0,2.0zM4.0,12.0c0.0,-4.42 3.58,-8.0 8.0,-8.0 1.85,0.0 3.5,0.63 4.9,1.69L5.69,16.9C4.63,15.55 4.0,13.85 4.0,12.0zm8.0,8.0c-1.85,0.0 -3.55,-0.63 -4.9,-1.69L18.31,7.1C19.37,8.45 20.0,10.15 20.0,12.0c0.0,4.42 -3.58,8.0 -8.0,8.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_notifications_alert.xml b/packages/SystemUI/res/drawable/ic_notifications_alert.xml
new file mode 100644
index 0000000..eb7b8ee
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_notifications_alert.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="24.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M7.58 4.08L6.15 2.65C3.75 4.48 2.17 7.3 2.03 10.5h2c.15-2.65 1.51-4.97 3.55-6.42zm12.39 6.42h2c-.15-3.2-1.73-6.02-4.12-7.85l-1.42 1.43c2.02 1.45 3.39 3.77 3.54 6.42zM18 11c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2v-5zm-6 11c.14 0 .27-.01.4-.04.65-.14 1.18-.58 1.44-1.18.1-.24.15-.5.15-.78h-4c.01 1.1.9 2 2.01 2z"
+        android:fillColor="#FF000000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_notifications_silence.xml b/packages/SystemUI/res/drawable/ic_notifications_silence.xml
new file mode 100644
index 0000000..ff136eb
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_notifications_silence.xml
@@ -0,0 +1,28 @@
+<!--
+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="24.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M0 0h24v24H0z"
+    />
+    <path
+        android:pathData="M20 18.69L7.84 6.14 5.27 3.49 4 4.76l2.8 2.8v.01c-.52.99-.8 2.16-.8 3.42v5l-2 2v1h13.73l2 2L21 19.72l-1-1.03zM12 22c1.11 0 2-.89 2-2h-4c0 1.11.89 2 2 2zm6-7.32V11c0-3.08-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68c-.15.03-.29.08-.42.12-.1.03-.2.07-.3.11h-.01c-.01 0-.01 0-.02.01-.23.09-.46.2-.68.31 0 0-.01 0-.01.01L18 14.68z"
+        android:fillColor="#FF000000"
+    />
+</vector>
diff --git a/packages/SystemUI/res/drawable/privacy_chip_bg.xml b/packages/SystemUI/res/drawable/privacy_chip_bg.xml
index 8247c27..36d0659 100644
--- a/packages/SystemUI/res/drawable/privacy_chip_bg.xml
+++ b/packages/SystemUI/res/drawable/privacy_chip_bg.xml
@@ -16,7 +16,7 @@
 -->
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="#bbbbbb" />
+    <solid android:color="#4a4a4a" />
     <padding android:padding="@dimen/ongoing_appops_chip_bg_padding" />
     <corners android:radius="@dimen/ongoing_appops_chip_bg_corner_radius" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
index ddefb6a..cbdd51b 100644
--- a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
@@ -18,11 +18,14 @@
 <com.android.systemui.privacy.OngoingPrivacyChip
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/privacy_chip"
-    android:layout_width="wrap_content"
     android:layout_height="match_parent"
-    android:layout_margin="@dimen/ongoing_appops_chip_margin"
+    android:layout_width="wrap_content"
+    android:layout_marginLeft="@dimen/ongoing_appops_chip_margin"
+    android:layout_marginRight="@dimen/ongoing_appops_chip_margin"
+    android:layout_marginTop="@dimen/ongoing_appops_top_chip_margin"
+    android:layout_marginBottom="@dimen/ongoing_appops_top_chip_margin"
     android:gravity="center_vertical|center_horizontal"
-    android:layout_gravity="center_vertical|end"
+    android:layout_gravity="center_vertical|start"
     android:orientation="horizontal"
     android:paddingStart="@dimen/ongoing_appops_chip_side_padding"
     android:paddingEnd="@dimen/ongoing_appops_chip_side_padding"
@@ -38,12 +41,17 @@
             />
 
         <TextView
-            android:id="@+id/app_name"
+            android:id="@+id/text_container"
             android:layout_height="match_parent"
             android:layout_width="wrap_content"
             android:singleLine="true"
             android:ellipsize="end"
+            android:lines="1"
             android:layout_gravity="center_vertical|end"
             android:gravity="center_vertical"
+            android:textAppearance="@style/TextAppearance.StatusBar.Clock"
+            android:textColor="@color/status_bar_clock_color"
+            android:layout_marginStart="@dimen/ongoing_appops_chip_icon_margin"
+            android:layout_marginEnd="@dimen/ongoing_appops_chip_icon_margin"
         />
 </com.android.systemui.privacy.OngoingPrivacyChip>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/ongoing_privacy_dialog_content.xml b/packages/SystemUI/res/layout/ongoing_privacy_dialog_content.xml
index b5e24a0..2f7d486 100644
--- a/packages/SystemUI/res/layout/ongoing_privacy_dialog_content.xml
+++ b/packages/SystemUI/res/layout/ongoing_privacy_dialog_content.xml
@@ -29,22 +29,30 @@
         android:orientation="vertical"
         android:padding="@dimen/ongoing_appops_dialog_content_padding">
 
-        <LinearLayout
-            android:id="@+id/icons_container"
+        <TextView
+            android:id="@+id/title"
             android:layout_width="match_parent"
-            android:layout_height="@dimen/ongoing_appops_dialog_icon_height"
-            android:orientation="horizontal"
+            android:layout_height="wrap_content"
             android:gravity="center"
-            android:importantForAccessibility="no"
+            android:textDirection="locale"
+            android:textAppearance="@style/TextAppearance.AppOpsDialog.Title"
+            android:textColor="@*android:color/text_color_primary"
+            android:paddingStart="@dimen/ongoing_appops_dialog_title_padding"
+            android:paddingEnd="@dimen/ongoing_appops_dialog_title_padding"
+            android:paddingBottom="@dimen/ongoing_appops_dialog_sep"
         />
 
         <LinearLayout
-            android:id="@+id/text_container"
+            android:id="@+id/items_container"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:orientation="vertical"
             android:gravity="start"
         />
+
+        <include android:id="@+id/overflow" layout="@layout/ongoing_privacy_dialog_item"
+                 android:visibility="gone" />
+
     </LinearLayout>
 
 </ScrollView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/ongoing_privacy_dialog_item.xml b/packages/SystemUI/res/layout/ongoing_privacy_dialog_item.xml
new file mode 100644
index 0000000..f05f7ba
--- /dev/null
+++ b/packages/SystemUI/res/layout/ongoing_privacy_dialog_item.xml
@@ -0,0 +1,53 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:fillViewport="true"
+    android:orientation="horizontal"
+    android:layout_marginTop="@dimen/ongoing_appops_dialog_text_margin"
+    android:focusable="true" >
+
+    <ImageView
+        android:id="@+id/app_icon"
+        android:layout_height="@dimen/ongoing_appops_dialog_icon_height"
+        android:layout_width="@dimen/ongoing_appops_dialog_icon_height"
+    />
+
+    <TextView
+        android:id="@+id/app_name"
+        android:layout_height="@dimen/ongoing_appops_dialog_icon_height"
+        android:layout_width="0dp"
+        android:layout_weight="1"
+        android:gravity="bottom|start"
+        android:textDirection="locale"
+        android:textAppearance="@style/TextAppearance.AppOpsDialog.Item"
+        android:textColor="@*android:color/text_color_primary"
+        android:paddingStart="@dimen/ongoing_appops_dialog_text_padding"
+        android:paddingEnd="@dimen/ongoing_appops_dialog_text_padding"
+
+    />
+
+    <LinearLayout
+        android:id="@+id/icons"
+        android:layout_height="@dimen/ongoing_appops_dialog_icon_height"
+        android:layout_width="wrap_content"
+        android:gravity="end"
+        android:visibility="gone"
+    />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
index e7f2c51..22b8d2f 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
@@ -59,7 +59,7 @@
         android:layout_height="match_parent"
         android:layout_weight="1"
         android:orientation="horizontal"
-        android:gravity="center_vertical|end">
+        android:gravity="center_vertical|end" >
 
     <include layout="@layout/ongoing_privacy_chip" />
 
@@ -67,6 +67,7 @@
         android:id="@+id/battery"
         android:layout_height="match_parent"
         android:layout_width="wrap_content"
-        android:gravity="center_vertical|end" />
+        android:gravity="center_vertical|end"
+        android:layout_gravity="center_vertical|end" />
     </LinearLayout>
 </LinearLayout>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index bb0c6f6..df858f0 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -34,4 +34,5 @@
     <bool name="quick_settings_wide">true</bool>
     <dimen name="qs_detail_margin_top">0dp</dimen>
     <dimen name="qs_paged_tile_layout_padding_bottom">0dp</dimen>
+    <dimen name="ongoing_appops_top_chip_margin">2dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index d4e6987..97f5f86 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -940,18 +940,34 @@
          that just start below the notch. -->
     <dimen name="display_cutout_touchable_region_size">12dp</dimen>
 
+    <!-- Padding below Ongoing App Ops dialog title -->
+    <dimen name="ongoing_appops_dialog_sep">16dp</dimen>
+    <!--Padding around text items in Ongoing App Ops dialog -->
+    <dimen name="ongoing_appops_dialog_text_padding">16dp</dimen>
     <!-- Height of icons in Ongoing App Ops dialog. Both App Op icon and application icon -->
-    <dimen name="ongoing_appops_dialog_icon_height">48dp</dimen>
+    <dimen name="ongoing_appops_dialog_icon_height">28dp</dimen>
     <!-- Margin between text lines in Ongoing App Ops dialog -->
     <dimen name="ongoing_appops_dialog_text_margin">15dp</dimen>
+    <!-- Side padding of title in Ongoing App Ops dialog -->
+    <dimen name="ongoing_appops_dialog_title_padding">10dp</dimen>
     <!-- Padding around Ongoing App Ops dialog content -->
     <dimen name="ongoing_appops_dialog_content_padding">24dp</dimen>
-    <!-- Margins around the Ongoing App Ops chip. In landscape, the side margins are 0 -->
+    <!-- Side margins around the Ongoing App Ops chip-->
     <dimen name="ongoing_appops_chip_margin">12dp</dimen>
+    <!-- Top and bottom margins around the Ongoing App Ops chip -->
+    <dimen name="ongoing_appops_top_chip_margin">12dp</dimen>
     <!-- Start and End padding for Ongoing App Ops chip -->
     <dimen name="ongoing_appops_chip_side_padding">6dp</dimen>
     <!-- Padding between background of Ongoing App Ops chip and content -->
-    <dimen name="ongoing_appops_chip_bg_padding">4dp</dimen>
+    <dimen name="ongoing_appops_chip_bg_padding">0dp</dimen>
+    <!-- Margin between icons of Ongoing App Ops chip -->
+    <dimen name="ongoing_appops_chip_icon_margin">4dp</dimen>
+    <!-- Icon size of Ongoing App Ops chip -->
+    <dimen name="ongoing_appops_chip_icon_size">18dp</dimen>
     <!-- Radius of Ongoing App Ops chip corners -->
     <dimen name="ongoing_appops_chip_bg_corner_radius">12dp</dimen>
+    <!-- Text size for Ongoing App Ops dialog title -->
+    <dimen name="ongoing_appops_dialog_title_size">24sp</dimen>
+    <!-- Text size for Ongoing App Ops dialog items -->
+    <dimen name="ongoing_appops_dialog_item_size">20sp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 50454fc..4a0bc9b 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2250,39 +2250,48 @@
          app for debugging. Will not be seen by users. [CHAR LIMIT=20] -->
     <string name="heap_dump_tile_name">Dump SysUI Heap</string>
 
+    <!-- Text on chip for multiple apps using a single app op [CHAR LIMIT=10] -->
+    <string name="ongoing_privacy_chip_multiple_apps"><xliff:g id="num_apps" example="3">%d</xliff:g> apps</string>
+
     <!-- Content description for ongoing privacy chip. Use with a single app [CHAR LIMIT=NONE]-->
     <string name="ongoing_privacy_chip_content_single_app"><xliff:g id="app" example="Example App">%1$s</xliff:g> is using your <xliff:g id="types_list" example="camera, location">%2$s</xliff:g>.</string>
 
     <!-- Content description for ongoing privacy chip. Use with multiple apps [CHAR LIMIT=NONE]-->
     <string name="ongoing_privacy_chip_content_multiple_apps">Applications are using your <xliff:g id="types_list" example="camera, location">%s</xliff:g>.</string>
 
-    <!-- Action on Ongoing Privacy Dialog to open application [CHAR LIMIT=10]-->
-    <string name="ongoing_privacy_dialog_open_app">Open app</string>
+    <!-- Content description for ongoing privacy chip. Use with multiple apps using same app op[CHAR LIMIT=NONE]-->
+    <string name="ongoing_privacy_chip_content_multiple_apps_single_op"><xliff:g id="num_apps" example="3">%1$d</xliff:g> applications are using your <xliff:g id="type" example="camera">%2$s</xliff:g>.</string>
 
     <!-- Action on Ongoing Privacy Dialog to dismiss [CHAR LIMIT=10]-->
     <string name="ongoing_privacy_dialog_cancel">Cancel</string>
 
-    <!-- Action on Ongoing Privacy Dialog to dismiss [CHAR LIMIT=10]-->
-    <string name="ongoing_privacy_dialog_okay">Okay</string>
+    <!-- Action on Ongoing Privacy Dialog to open privacy hub [CHAR LIMIT=15]-->
+    <string name="ongoing_privacy_dialog_open_settings">View details</string>
 
-    <!-- Action on Ongoing Privacy Dialog to open privacy hub [CHAR LIMIT=10]-->
-    <string name="ongoing_privacy_dialog_open_settings">Settings</string>
+    <!-- Text for item in Ongoing Privacy Dialog title when only one app is using app ops [CHAR LIMIT=NONE] -->
+    <string name="ongoing_privacy_dialog_single_app_title">App using your <xliff:g id="types_list" example="camera( and location)">%s</xliff:g></string>
 
-    <!-- Text for item in Ongoing Privacy Dialog when only one app is using a particular type of app op [CHAR LIMIT=NONE] -->
-    <string name="ongoing_privacy_dialog_app_item"><xliff:g id="app" example="Example App">%1$s</xliff:g> is using your <xliff:g id="type" example="camera">%2$s</xliff:g> for the last <xliff:g id="time" example="3">%3$d</xliff:g> min</string>
+    <!-- Text for item in Ongoing Privacy Dialog title when multiple apps is using app ops [CHAR LIMIT=NONE] -->
+    <string name="ongoing_privacy_dialog_multiple_apps_title">Apps using your <xliff:g id="types_list" example="camera( and location)">%s</xliff:g></string>
 
-    <!-- Text for item in Ongoing Privacy Dialog when only multiple apps are using a particular type of app op [CHAR LIMIT=NONE] -->
-    <string name="ongoing_privacy_dialog_apps_item"><xliff:g id="apps" example="Camera, Phone">%1$s</xliff:g> are using your <xliff:g id="type" example="camera">%2$s</xliff:g></string>
+    <!-- Separator for types. Include spaces before and after if needed [CHAR LIMIT=10] -->
+    <string name="ongoing_privacy_dialog_separator">,\u0020</string>
 
-    <!-- Text for Ongoing Privacy Dialog when a single app is using app ops [CHAR LIMIT=NONE] -->
-    <string name="ongoing_privacy_dialog_single_app"><xliff:g id="app" example="Example App">%1$s</xliff:g> is using your <xliff:g id="types_list" example="camera, location">%2$s</xliff:g></string>
+    <!-- Separator for types, before last type. Include spaces before and after if needed [CHAR LIMIT=10] -->
+    <string name="ongoing_privacy_dialog_last_separator">\u0020and\u0020</string>
 
-    <!-- Text for camera app op [CHAR LIMIT=12]-->
+    <!-- Text for camera app op [CHAR LIMIT=20]-->
     <string name="privacy_type_camera">camera</string>
 
-    <!-- Text for location app op [CHAR LIMIT=12]-->
+    <!-- Text for location app op [CHAR LIMIT=20]-->
     <string name="privacy_type_location">location</string>
 
-    <!-- Text for microphone app op [CHAR LIMIT=12]-->
+    <!-- Text for microphone app op [CHAR LIMIT=20]-->
     <string name="privacy_type_microphone">microphone</string>
+
+    <!-- Text for indicating extra apps using app ops [CHAR LIMIT=NONE] -->
+    <plurals name="ongoing_privacy_dialog_overflow_text">
+        <item quantity="one"><xliff:g id="num_apps" example="1">%d</xliff:g> other app</item>
+        <item quantity="other"><xliff:g id="num_apps" example="3">%d</xliff:g> other app</item>
+    </plurals>
 </resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 95fd86f..e9aa1b6 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -253,6 +253,18 @@
         <item name="android:textSize">@dimen/qs_carrier_info_text_size</item>
     </style>
 
+    <style name="TextAppearance.AppOpsDialog" />
+
+    <style name="TextAppearance.AppOpsDialog.Title">
+        <item name="android:textSize">@dimen/ongoing_appops_dialog_title_size</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+    </style>
+
+    <style name="TextAppearance.AppOpsDialog.Item">
+        <item name="android:textSize">@dimen/ongoing_appops_dialog_item_size</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+    </style>
+
     <style name="BaseBrightnessDialogContainer" parent="@style/Theme.SystemUI">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">wrap_content</item>
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index e3584cf..3666400 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -88,8 +88,6 @@
     private Runnable mWatchLongPress;
     private final long mLongPressTimeout;
 
-    protected boolean mSwipingInProgress;
-
     final private int[] mTmpPos = new int[2];
     private final int mFalsingThreshold;
     private boolean mTouchAboveFalsingThreshold;
@@ -130,10 +128,6 @@
         mDisableHwLayers = disableHwLayers;
     }
 
-    public boolean isSwipingInProgress() {
-        return mSwipingInProgress;
-    }
-
     private float getPos(MotionEvent ev) {
         return mSwipeDirection == X ? ev.getX() : ev.getY();
     }
@@ -325,7 +319,6 @@
                     if (Math.abs(delta) > mPagingTouchSlop
                             && Math.abs(delta) > Math.abs(deltaPerpendicular)) {
                         if (mCallback.canChildBeDragged(mCurrView)) {
-                            mSwipingInProgress = true;
                             mCallback.onBeginDrag(mCurrView);
                             mDragging = true;
                             mInitialTouchPos = getPos(ev);
@@ -445,7 +438,6 @@
                     wasRemoved = row.isRemoved();
                 }
                 if (!mCancelled || wasRemoved) {
-                    mSwipingInProgress = false;
                     mCallback.onChildDismissed(animView);
                 }
                 if (endAction != null) {
@@ -637,7 +629,6 @@
                                 !swipedFastEnough() /* useAccelerateInterpolator */);
                     } else {
                         // snappity
-                        mSwipingInProgress = false;
                         mCallback.onDragCancelled(mCurrView);
                         snapChild(mCurrView, 0 /* leftTarget */, velocity);
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index c61e10a..4557b4d 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -38,6 +38,7 @@
 
     private DozeMachine mDozeMachine;
     private DozeServicePlugin mDozePlugin;
+    private PluginManager mPluginManager;
 
     public DozeService() {
         setDebug(DEBUG);
@@ -53,14 +54,14 @@
             finish();
             return;
         }
-        Dependency.get(PluginManager.class).addPluginListener(this,
-                DozeServicePlugin.class, false /* Allow multiple */);
+        mPluginManager = Dependency.get(PluginManager.class);
+        mPluginManager.addPluginListener(this, DozeServicePlugin.class, false /* allowMultiple */);
         mDozeMachine = new DozeFactory().assembleMachine(this);
     }
 
     @Override
     public void onDestroy() {
-        Dependency.get(PluginManager.class).removePluginListener(this);
+        mPluginManager.removePluginListener(this);
         super.onDestroy();
         mDozeMachine = null;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
index fc1baef..d3715d0 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
@@ -15,7 +15,6 @@
 package com.android.systemui.privacy
 
 import android.content.Context
-import android.graphics.Color
 import android.util.AttributeSet
 import android.view.ViewGroup
 import android.widget.ImageView
@@ -30,7 +29,13 @@
     defStyleRes: Int = 0
 ) : LinearLayout(context, attrs, defStyleAttrs, defStyleRes) {
 
-    private lateinit var appName: TextView
+    private val iconMargin =
+            context.resources.getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_margin)
+    private val iconSize =
+            context.resources.getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_size)
+    val iconColor = context.resources.getColor(
+            R.color.status_bar_clock_color, context.theme)
+    private lateinit var text: TextView
     private lateinit var iconsContainer: LinearLayout
     var builder = PrivacyDialogBuilder(context, emptyList<PrivacyItem>())
     var privacyList = emptyList<PrivacyItem>()
@@ -43,7 +48,7 @@
     override fun onFinishInflate() {
         super.onFinishInflate()
 
-        appName = findViewById(R.id.app_name)
+        text = findViewById(R.id.text_container)
         iconsContainer = findViewById(R.id.icons_container)
     }
 
@@ -53,39 +58,52 @@
             iconsContainer.removeAllViews()
             dialogBuilder.generateIcons().forEach {
                 it.mutate()
-                it.setTint(Color.WHITE)
-                iconsContainer.addView(ImageView(context).apply {
+                it.setTint(iconColor)
+                val image = ImageView(context).apply {
                     setImageDrawable(it)
-                    maxHeight = this@OngoingPrivacyChip.height
-                })
+                    scaleType = ImageView.ScaleType.CENTER_INSIDE
+                }
+                iconsContainer.addView(image, iconSize, iconSize)
+                val lp = image.layoutParams as MarginLayoutParams
+                lp.marginStart = iconMargin
+                image.layoutParams = lp
             }
         }
 
-        if (privacyList.isEmpty()) {
-            return
-        } else {
+        if (!privacyList.isEmpty()) {
             generateContentDescription()
             setIcons(builder, iconsContainer)
-            appName.visibility = GONE
-            builder.app?.let {
-                appName.apply {
-                    setText(it.applicationName)
-                    setTextColor(Color.WHITE)
-                    visibility = VISIBLE
+            text.visibility = if (builder.types.size == 1) VISIBLE else GONE
+            if (builder.types.size == 1) {
+                if (builder.app != null) {
+                    text.setText(builder.app?.applicationName)
+                } else {
+                    text.text = context.getString(R.string.ongoing_privacy_chip_multiple_apps,
+                            builder.appsAndTypes.size)
                 }
             }
+        } else {
+            text.visibility = GONE
+            iconsContainer.removeAllViews()
         }
         requestLayout()
     }
 
     private fun generateContentDescription() {
-        val typesText = builder.generateTypesText()
-        if (builder.app != null) {
-            contentDescription = context.getString(R.string.ongoing_privacy_chip_content_single_app,
-                    builder.app?.applicationName, typesText)
-        } else {
+        val typesText = builder.joinTypes()
+        if (builder.types.size > 1) {
             contentDescription = context.getString(
                     R.string.ongoing_privacy_chip_content_multiple_apps, typesText)
+        } else {
+            if (builder.app != null) {
+                contentDescription =
+                        context.getString(R.string.ongoing_privacy_chip_content_single_app,
+                                builder.app?.applicationName, typesText)
+            } else {
+                contentDescription = context.getString(
+                        R.string.ongoing_privacy_chip_content_multiple_apps_single_op,
+                        builder.appsAndTypes.size, typesText)
+            }
         }
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
index 1d0e16e..f6a95af 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
@@ -18,10 +18,10 @@
 import android.app.Dialog
 import android.content.Context
 import android.content.DialogInterface
-import android.graphics.drawable.Drawable
+import android.content.Intent
+import android.content.res.ColorStateList
 import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.widget.ImageView
 import android.widget.LinearLayout
 import android.widget.TextView
@@ -34,29 +34,25 @@
     val dialogBuilder: PrivacyDialogBuilder
 ) {
 
-    val iconHeight = context.resources.getDimensionPixelSize(
+    val iconSize = context.resources.getDimensionPixelSize(
             R.dimen.ongoing_appops_dialog_icon_height)
-    val textMargin = context.resources.getDimensionPixelSize(
-            R.dimen.ongoing_appops_dialog_text_margin)
     val iconColor = context.resources.getColor(
             com.android.internal.R.color.text_color_primary, context.theme)
+    companion object {
+        private const val MAX_ITEMS = 10
+    }
 
     fun createDialog(): Dialog {
-        val builder = AlertDialog.Builder(context)
-                .setNeutralButton(R.string.ongoing_privacy_dialog_open_settings, null)
-        if (dialogBuilder.app != null) {
-            builder.setPositiveButton(R.string.ongoing_privacy_dialog_open_app,
+        val builder = AlertDialog.Builder(context).apply {
+            setNegativeButton(R.string.ongoing_privacy_dialog_cancel, null)
+            setPositiveButton(R.string.ongoing_privacy_dialog_open_settings,
                     object : DialogInterface.OnClickListener {
-                        val intent = context.packageManager
-                                .getLaunchIntentForPackage(dialogBuilder.app.packageName)
+                        val intent = Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE)
 
                         override fun onClick(dialog: DialogInterface?, which: Int) {
                             Dependency.get(ActivityStarter::class.java).startActivity(intent, false)
                         }
                     })
-            builder.setNegativeButton(R.string.ongoing_privacy_dialog_cancel, null)
-        } else {
-            builder.setPositiveButton(R.string.ongoing_privacy_dialog_okay, null)
         }
         builder.setView(getContentView())
         return builder.create()
@@ -66,44 +62,67 @@
         val layoutInflater = LayoutInflater.from(context)
         val contentView = layoutInflater.inflate(R.layout.ongoing_privacy_dialog_content, null)
 
-        val iconsContainer = contentView.findViewById(R.id.icons_container) as LinearLayout
-        val textContainer = contentView.findViewById(R.id.text_container) as LinearLayout
+        val title = contentView.findViewById(R.id.title) as TextView
+        val appsList = contentView.findViewById(R.id.items_container) as LinearLayout
 
-        addIcons(dialogBuilder, iconsContainer)
-        val lm = ViewGroup.MarginLayoutParams(
-                ViewGroup.MarginLayoutParams.WRAP_CONTENT,
-                ViewGroup.MarginLayoutParams.WRAP_CONTENT)
-        lm.topMargin = textMargin
-        val now = System.currentTimeMillis()
-        dialogBuilder.generateText(now).forEach {
-            val text = layoutInflater.inflate(R.layout.ongoing_privacy_text_item, null) as TextView
-            text.setText(it)
-            textContainer.addView(text, lm)
+        title.setText(dialogBuilder.getDialogTitle())
+
+        val numItems = dialogBuilder.appsAndTypes.size
+        for (i in 0..(numItems - 1)) {
+            if (i >= MAX_ITEMS) break
+            val item = dialogBuilder.appsAndTypes[i]
+            addAppItem(appsList, item.first, item.second, dialogBuilder.types.size > 1)
         }
+
+        if (numItems > MAX_ITEMS) {
+            val overflow = contentView.findViewById(R.id.overflow) as LinearLayout
+            overflow.visibility = View.VISIBLE
+            val overflowText = overflow.findViewById(R.id.app_name) as TextView
+            overflowText.text = context.resources.getQuantityString(
+                    R.plurals.ongoing_privacy_dialog_overflow_text,
+                    numItems - MAX_ITEMS,
+                    numItems - MAX_ITEMS
+            )
+            val overflowPlus = overflow.findViewById(R.id.app_icon) as ImageView
+            overflowPlus.apply {
+                imageTintList = ColorStateList.valueOf(iconColor)
+                setImageDrawable(context.getDrawable(R.drawable.plus))
+            }
+        }
+
         return contentView
     }
 
-    private fun addIcons(dialogBuilder: PrivacyDialogBuilder, iconsContainer: LinearLayout) {
+    private fun addAppItem(
+        itemList: LinearLayout,
+        app: PrivacyApplication,
+        types: List<PrivacyType>,
+        showIcons: Boolean = true
+    ) {
+        val layoutInflater = LayoutInflater.from(context)
+        val item = layoutInflater.inflate(R.layout.ongoing_privacy_dialog_item, itemList, false)
+        val appIcon = item.findViewById(R.id.app_icon) as ImageView
+        val appName = item.findViewById(R.id.app_name) as TextView
+        val icons = item.findViewById(R.id.icons) as LinearLayout
 
-        fun LinearLayout.addIcon(icon: Drawable) {
-            val image = ImageView(context).apply {
-                setImageDrawable(icon.apply {
-                    setBounds(0, 0, iconHeight, iconHeight)
-                    maxHeight = this@addIcon.height
-                })
-                adjustViewBounds = true
+        app.icon?.let {
+            appIcon.setImageDrawable(it)
+        }
+
+        appName.text = app.applicationName
+        if (showIcons) {
+            dialogBuilder.generateIconsForApp(types).forEach {
+                it.setBounds(0, 0, iconSize, iconSize)
+                val image = ImageView(context).apply {
+                    imageTintList = ColorStateList.valueOf(iconColor)
+                    setImageDrawable(it)
+                }
+                icons.addView(image, iconSize, LinearLayout.LayoutParams.WRAP_CONTENT)
             }
-            addView(image, LinearLayout.LayoutParams.WRAP_CONTENT,
-                    LinearLayout.LayoutParams.MATCH_PARENT)
+            icons.visibility = View.VISIBLE
+        } else {
+            icons.visibility = View.GONE
         }
-
-        dialogBuilder.generateIcons().forEach {
-            it.mutate()
-            it.setTint(iconColor)
-            iconsContainer.addIcon(it)
-        }
-        dialogBuilder.app.let {
-            it?.icon?.let { iconsContainer.addIcon(it) }
-        }
+        itemList.addView(item)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogBuilder.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogBuilder.kt
index 5ce4ee7..519df19 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogBuilder.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogBuilder.kt
@@ -15,59 +15,53 @@
 package com.android.systemui.privacy
 
 import android.content.Context
+import android.graphics.drawable.Drawable
 import com.android.systemui.R
-import java.lang.Math.max
 
 class PrivacyDialogBuilder(val context: Context, itemsList: List<PrivacyItem>) {
-    companion object {
-        val MILLIS_IN_MINUTE: Long = 1000 * 60
-    }
 
-    private val itemsByType: Map<PrivacyType, List<PrivacyItem>>
+    val appsAndTypes: List<Pair<PrivacyApplication, List<PrivacyType>>>
+    val types: List<PrivacyType>
     val app: PrivacyApplication?
+    private val separator = context.getString(R.string.ongoing_privacy_dialog_separator)
+    private val lastSeparator = context.getString(R.string.ongoing_privacy_dialog_last_separator)
 
     init {
-        itemsByType = itemsList.groupBy { it.privacyType }
-        val apps = itemsList.map { it.application }.distinct()
-        val singleApp = apps.size == 1
-        app = if (singleApp) apps.get(0) else null
+        appsAndTypes = itemsList.groupBy({ it.application }, { it.privacyType })
+                .toList()
+                .sortedWith(compareBy({ -it.second.size }, { it.first }))
+        types = itemsList.map { it.privacyType }.distinct().sorted()
+        val singleApp = appsAndTypes.size == 1
+        app = if (singleApp) appsAndTypes[0].first else null
     }
 
-    private fun buildTextForItem(type: PrivacyType, now: Long): String {
-        val items = itemsByType.getOrDefault(type, emptyList<PrivacyItem>())
-        return when (items.size) {
-            0 -> throw IllegalStateException("List cannot be empty")
-            1 -> {
-                val item = items.get(0)
-                val minutesUsed = max(((now - item.timeStarted) / MILLIS_IN_MINUTE).toInt(), 1)
-                context.getString(R.string.ongoing_privacy_dialog_app_item,
-                        item.application.applicationName, type.getName(context), minutesUsed)
-            }
-            else -> {
-                val apps = items.map { it.application.applicationName }.joinToString()
-                context.getString(R.string.ongoing_privacy_dialog_apps_item,
-                        apps, type.getName(context))
-            }
+    fun generateIconsForApp(types: List<PrivacyType>): List<Drawable> {
+        return types.sorted().map { it.getIcon(context) }
+    }
+
+    fun generateIcons() = types.map { it.getIcon(context) }
+
+    private fun <T> List<T>.joinWithAnd(): StringBuilder {
+        return subList(0, size - 1).joinTo(StringBuilder(), separator = separator).apply {
+            append(lastSeparator)
+            append(this@joinWithAnd.last())
         }
     }
 
-    private fun buildTextForApp(types: Set<PrivacyType>): List<String> {
-        app?.let {
-            val typesText = types.map { it.getName(context) }.sorted().joinToString()
-            return listOf(context.getString(R.string.ongoing_privacy_dialog_single_app,
-                    it.applicationName, typesText))
-        } ?: throw IllegalStateException("There has to be a single app")
+    fun joinTypes(): String {
+        return when (types.size) {
+            0 -> ""
+            1 -> types[0].getName(context)
+            else -> types.map { it.getName(context) }.joinWithAnd().toString()
+        }
     }
 
-    fun generateText(now: Long): List<String> {
-        if (app == null || itemsByType.keys.size == 1) {
-            return itemsByType.keys.map { buildTextForItem(it, now) }
+    fun getDialogTitle(): String {
+        if (app != null) {
+            return context.getString(R.string.ongoing_privacy_dialog_single_app_title, joinTypes())
         } else {
-            return buildTextForApp(itemsByType.keys)
+            return context.getString(R.string.ongoing_privacy_dialog_multiple_apps_title,
+                    joinTypes())
         }
     }
-
-    fun generateTypesText() = itemsByType.keys.map { it.getName(context) }.sorted().joinToString()
-
-    fun generateIcons() = itemsByType.keys.map { it.getIcon(context) }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
index 3d9aa0f..85e99f0 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
@@ -29,16 +29,21 @@
 
     fun getName(context: Context) = context.resources.getString(nameId)
 
-    fun getIcon(context: Context) = context.resources.getDrawable(iconId, null)
+    fun getIcon(context: Context) = context.resources.getDrawable(iconId, context.theme)
 }
 
 data class PrivacyItem(
     val privacyType: PrivacyType,
-    val application: PrivacyApplication,
-    val timeStarted: Long
+    val application: PrivacyApplication
 )
 
-data class PrivacyApplication(val packageName: String, val context: Context) {
+data class PrivacyApplication(val packageName: String, val context: Context)
+    : Comparable<PrivacyApplication> {
+
+    override fun compareTo(other: PrivacyApplication): Int {
+        return applicationName.compareTo(other.applicationName)
+    }
+
     var icon: Drawable? = null
     var applicationName: String
 
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index 5141e50..3fa3e8e 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -95,7 +95,7 @@
             else -> return null
         }
         val app = PrivacyApplication(appOpItem.packageName, context)
-        return PrivacyItem(type, app, appOpItem.timeStarted)
+        return PrivacyItem(type, app)
     }
 
     // Used by containing class to get notified of changes
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index e3f85d9..427f638 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -325,15 +325,10 @@
                 newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE;
         mBatteryMeterView.useWallpaperTextColor(shouldUseWallpaperTextColor);
         mClockView.useWallpaperTextColor(shouldUseWallpaperTextColor);
-
-        MarginLayoutParams lm = (MarginLayoutParams) mPrivacyChip.getLayoutParams();
-        int sideMargins = lm.leftMargin;
-        int topBottomMargins = (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
-                ? 0 : sideMargins;
-        lm.setMargins(sideMargins, topBottomMargins, sideMargins, topBottomMargins);
-        mPrivacyChip.setLayoutParams(lm);
     }
 
+
+
     @Override
     public void onRtlPropertiesChanged(int layoutDirection) {
         super.onRtlPropertiesChanged(layoutDirection);
@@ -378,6 +373,15 @@
 
         setLayoutParams(lp);
 
+        if (mPrivacyChip != null) {
+            MarginLayoutParams lm = (MarginLayoutParams) mPrivacyChip.getLayoutParams();
+            int sideMargins = lm.leftMargin;
+            int topBottomMargins = resources.getDimensionPixelSize(
+                    R.dimen.ongoing_appops_top_chip_margin);
+            lm.setMargins(sideMargins, topBottomMargins, sideMargins, topBottomMargins);
+            mPrivacyChip.setLayoutParams(lm);
+        }
+
         updateStatusIconAlphaAnimator();
         updateHeaderTextContainerAlphaAnimator();
     }
@@ -729,7 +733,8 @@
     public void setMargins(int sideMargins) {
         for (int i = 0; i < getChildCount(); i++) {
             View v = getChildAt(i);
-            if (v == mSystemIconsView || v == mQuickQsStatusIcons || v == mHeaderQsPanel) {
+            if (v == mSystemIconsView || v == mQuickQsStatusIcons || v == mHeaderQsPanel
+                    || v == mPrivacyChip) {
                 continue;
             }
             RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) v.getLayoutParams();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 97534ed..bed0c45 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -40,7 +40,6 @@
 import android.content.ClipDescription;
 import android.content.ComponentName;
 import android.content.ContentResolver;
-import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
@@ -64,7 +63,6 @@
 import android.provider.MediaStore;
 import android.text.TextUtils;
 import android.util.DisplayMetrics;
-import android.util.Log;
 import android.util.Slog;
 import android.view.Display;
 import android.view.LayoutInflater;
@@ -85,8 +83,9 @@
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.util.NotificationChannels;
 
-import java.io.File;
-import java.io.FileOutputStream;
+import libcore.io.IoUtils;
+
+import java.io.IOException;
 import java.io.OutputStream;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
@@ -121,16 +120,13 @@
 class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
     private static final String TAG = "SaveImageInBackgroundTask";
 
-    private static final String SCREENSHOTS_DIR_NAME = "Screenshots";
     private static final String SCREENSHOT_FILE_NAME_TEMPLATE = "Screenshot_%s.png";
     private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";
 
     private final SaveImageInBackgroundData mParams;
     private final NotificationManager mNotificationManager;
     private final Notification.Builder mNotificationBuilder, mPublicNotificationBuilder;
-    private final File mScreenshotDir;
     private final String mImageFileName;
-    private final String mImageFilePath;
     private final long mImageTime;
     private final BigPictureStyle mNotificationStyle;
     private final int mImageWidth;
@@ -146,10 +142,6 @@
         String imageDate = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(mImageTime));
         mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate);
 
-        mScreenshotDir = new File(Environment.getExternalStoragePublicDirectory(
-                Environment.DIRECTORY_PICTURES), SCREENSHOTS_DIR_NAME);
-        mImageFilePath = new File(mScreenshotDir, mImageFileName).getAbsolutePath();
-
         // Create the large notification icon
         mImageWidth = data.image.getWidth();
         mImageHeight = data.image.getHeight();
@@ -238,7 +230,7 @@
     }
 
     @Override
-    protected Void doInBackground(Void... params) {
+    protected Void doInBackground(Void... paramsUnused) {
         if (isCancelled()) {
             return null;
         }
@@ -252,36 +244,27 @@
         Resources r = context.getResources();
 
         try {
-            // Create screenshot directory if it doesn't exist
-            boolean madeDirs = mScreenshotDir.mkdirs();
-            if (madeDirs == false) {
-                Log.e(TAG, "Couldn't create screenshot directory: " + mScreenshotDir);
-            }
-
-            // media provider uses seconds for DATE_MODIFIED and DATE_ADDED, but milliseconds
-            // for DATE_TAKEN
-            long dateSeconds = mImageTime / 1000;
-
-            // Save
-            OutputStream out = new FileOutputStream(mImageFilePath);
-            image.compress(Bitmap.CompressFormat.PNG, 100, out);
-            out.flush();
-            out.close();
-
             // Save the screenshot to the MediaStore
-            ContentValues values = new ContentValues();
-            ContentResolver resolver = context.getContentResolver();
-            values.put(MediaStore.Images.ImageColumns.DATA, mImageFilePath);
-            values.put(MediaStore.Images.ImageColumns.TITLE, mImageFileName);
-            values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, mImageFileName);
-            values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, mImageTime);
-            values.put(MediaStore.Images.ImageColumns.DATE_ADDED, dateSeconds);
-            values.put(MediaStore.Images.ImageColumns.DATE_MODIFIED, dateSeconds);
-            values.put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/png");
-            values.put(MediaStore.Images.ImageColumns.WIDTH, mImageWidth);
-            values.put(MediaStore.Images.ImageColumns.HEIGHT, mImageHeight);
-            values.put(MediaStore.Images.ImageColumns.SIZE, new File(mImageFilePath).length());
-            Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+            final MediaStore.PendingParams params = new MediaStore.PendingParams(
+                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mImageFileName, "image/png");
+            params.setPrimaryDirectory(Environment.DIRECTORY_PICTURES);
+            params.setSecondaryDirectory(Environment.DIRECTORY_SCREENSHOTS);
+
+            final Uri uri = MediaStore.createPending(context, params);
+            final MediaStore.PendingSession session = MediaStore.openPending(context, uri);
+            try {
+                try (OutputStream out = session.openOutputStream()) {
+                    if (!image.compress(Bitmap.CompressFormat.PNG, 100, out)) {
+                        throw new IOException("Failed to compress");
+                    }
+                }
+                session.publish();
+            } catch (Exception e) {
+                session.abandon();
+                throw e;
+            } finally {
+                IoUtils.closeQuietly(session);
+            }
 
             // Note: Both the share and edit actions are proxied through ActionProxyReceiver in
             // order to do some common work like dismissing the keyguard and sending
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index 2bc4f02..c16b28f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -17,13 +17,16 @@
 package com.android.systemui.statusbar.notification.row;
 
 import static com.android.systemui.SwipeHelper.SWIPED_FAR_ENOUGH_SIZE_FRACTION;
+import static com.android.systemui.statusbar.notification.row.NotificationInfo.ACTION_BLOCK;
 import static com.android.systemui.statusbar.notification.row.NotificationInfo.ACTION_NONE;
+import static com.android.systemui.statusbar.notification.row.NotificationInfo.ACTION_TOGGLE_SILENT;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.Nullable;
 import android.app.Notification;
+import android.app.NotificationManager;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
@@ -41,6 +44,7 @@
 import com.android.systemui.R;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.AlphaOptimizedImageView;
+import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.row.NotificationGuts.GutsContent;
 import com.android.systemui.statusbar.notification.row.NotificationInfo.NotificationInfoAction;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
@@ -69,7 +73,7 @@
 
     private Context mContext;
     private FrameLayout mMenuContainer;
-    private MenuItem mInfoItem;
+    private NotificationInfoMenuItem mInfoItem;
     private MenuItem mAppOpsItem;
     private MenuItem mSnoozeItem;
     private ArrayList<MenuItem> mLeftMenuItems;
@@ -172,7 +176,9 @@
     @Override
     public void createMenu(ViewGroup parent, StatusBarNotification sbn) {
         mParent = (ExpandableNotificationRow) parent;
-        createMenuViews(true /* resetState */);
+        createMenuViews(true /* resetState */,
+                sbn != null && (sbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE)
+                        != 0);
     }
 
     @Override
@@ -216,7 +222,8 @@
             // Menu hasn't been created yet, no need to do anything.
             return;
         }
-        createMenuViews(!isMenuVisible() /* resetState */);
+        createMenuViews(!isMenuVisible() /* resetState */,
+                (sbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0);
     }
 
     @Override
@@ -231,30 +238,47 @@
         mParent.removeListener();
     }
 
-    private void createMenuViews(boolean resetState) {
+    private void createMenuViews(boolean resetState, final boolean isForeground) {
         final Resources res = mContext.getResources();
         mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size);
         mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height);
         mLeftMenuItems.clear();
         mRightMenuItems.clear();
         // Construct the menu items based on the notification
-        if (mParent != null && mParent.getStatusBarNotification() != null) {
-            int flags = mParent.getStatusBarNotification().getNotification().flags;
-            boolean isForeground = (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
-            if (!isForeground) {
-                // Only show snooze for non-foreground notifications
-                mSnoozeItem = createSnoozeItem(mContext);
-                mLeftMenuItems.add(mSnoozeItem);
-                mRightMenuItems.add(mSnoozeItem);
-            }
+        if (!isForeground) {
+            // Only show snooze for non-foreground notifications
+            mSnoozeItem = createSnoozeItem(mContext);
+            mLeftMenuItems.add(mSnoozeItem);
         }
         mInfoItem = createInfoItem(mContext);
-        mLeftMenuItems.add(mInfoItem);
-        mRightMenuItems.add(mInfoItem);
+        if (!NotificationUtils.useNewInterruptionModel(mContext)) {
+            mLeftMenuItems.add(mInfoItem);
+        }
 
         mAppOpsItem = createAppOpsItem(mContext);
         mLeftMenuItems.add(mAppOpsItem);
-        mRightMenuItems.add(mAppOpsItem);
+
+        if (NotificationUtils.useNewInterruptionModel(mContext)) {
+            if (!mParent.getIsNonblockable()) {
+                mRightMenuItems.add(createBlockItem(mContext, mInfoItem.getGutsView()));
+            }
+            // TODO(kprevas): this is duplicated logic
+            // but it's currently spread across NotificationGutsManager and NotificationInfo.
+            // Try to consolidate and reuse here.
+            boolean canToggleSilent = !mParent.getIsNonblockable()
+                    && !isForeground
+                    && mParent.getEntry().noisy;
+            if (canToggleSilent) {
+                int channelImportance = mParent.getEntry().channel.getImportance();
+                int effectiveImportance =
+                        channelImportance == NotificationManager.IMPORTANCE_UNSPECIFIED
+                                ? mParent.getEntry().importance : channelImportance;
+                mRightMenuItems.add(createToggleSilentItem(mContext, mInfoItem.getGutsView(),
+                        effectiveImportance < NotificationManager.IMPORTANCE_DEFAULT));
+            }
+        } else {
+            mRightMenuItems.addAll(mLeftMenuItems);
+        }
 
         populateMenuViews();
         if (resetState) {
@@ -597,7 +621,7 @@
         // TODO -- handle / allow custom menu items!
     }
 
-    public static MenuItem createSnoozeItem(Context context) {
+    static MenuItem createSnoozeItem(Context context) {
         Resources res = context.getResources();
         NotificationSnooze content = (NotificationSnooze) LayoutInflater.from(context)
                 .inflate(R.layout.notification_snooze, null, false);
@@ -607,17 +631,16 @@
         return snooze;
     }
 
-    public static MenuItem createInfoItem(Context context) {
+    static NotificationInfoMenuItem createInfoItem(Context context) {
         Resources res = context.getResources();
         String infoDescription = res.getString(R.string.notification_menu_gear_description);
         NotificationInfo infoContent = (NotificationInfo) LayoutInflater.from(context).inflate(
                 R.layout.notification_info, null, false);
-        MenuItem info = new NotificationInfoMenuItem(context, infoDescription, infoContent,
+        return new NotificationInfoMenuItem(context, infoDescription, infoContent,
                 R.drawable.ic_settings, ACTION_NONE);
-        return info;
     }
 
-    public static MenuItem createAppOpsItem(Context context) {
+    static MenuItem createAppOpsItem(Context context) {
         AppOpsInfo appOpsContent = (AppOpsInfo) LayoutInflater.from(context).inflate(
                 R.layout.app_ops_info, null, false);
         MenuItem info = new NotificationMenuItem(context, null, appOpsContent,
@@ -625,6 +648,29 @@
         return info;
     }
 
+    private static MenuItem createBlockItem(Context context, NotificationInfo gutsView) {
+        return new NotificationInfoMenuItem(
+                context,
+                context.getResources().getString(R.string.inline_stop_button),
+                gutsView,
+                R.drawable.ic_notification_block,
+                ACTION_BLOCK);
+    }
+
+    private static MenuItem createToggleSilentItem(Context context, NotificationInfo gutsView,
+            boolean isCurrentlySilent) {
+        return new NotificationInfoMenuItem(
+                context,
+                isCurrentlySilent
+                        ? context.getResources().getString(R.string.inline_silent_button_alert)
+                        : context.getResources().getString(R.string.inline_silent_button_silent),
+                gutsView,
+                isCurrentlySilent
+                        ? R.drawable.ic_notifications_alert
+                        : R.drawable.ic_notifications_silence,
+                ACTION_TOGGLE_SILENT);
+    }
+
     private void addMenuView(MenuItem item, ViewGroup parent) {
         View menuView = item.getMenuView();
         if (menuView != null) {
@@ -706,7 +752,8 @@
          * Add a new 'guts' panel. If iconResId < 0 it will not appear in the slow swipe menu
          * but can still be exposed via other affordances.
          */
-        public NotificationMenuItem(Context context, String s, GutsContent content, int iconResId) {
+        public NotificationMenuItem(Context context, String contentDescription, GutsContent content,
+                int iconResId) {
             Resources res = context.getResources();
             int padding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding);
             int tint = res.getColor(R.color.notification_gear_color);
@@ -719,7 +766,7 @@
                 iv.setAlpha(1f);
                 mMenuView = iv;
             }
-            mContentDescription = s;
+            mContentDescription = contentDescription;
             mGutsContent = content;
         }
 
@@ -746,11 +793,16 @@
         @NotificationInfoAction
         int mAction;
 
-        public NotificationInfoMenuItem(Context context, String s,
+        public NotificationInfoMenuItem(Context context, String contentDescription,
                 NotificationInfo content, int iconResId,
                 @NotificationInfoAction int action) {
-            super(context, s, content, iconResId);
+            super(context, contentDescription, content, iconResId);
             this.mAction = action;
         }
+
+        @Override
+        public NotificationInfo getGutsView() {
+            return (NotificationInfo) super.getGutsView();
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index a8ced7a..1be2afe7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -38,6 +38,7 @@
 import com.android.systemui.statusbar.ViewTransformationHelper;
 import com.android.systemui.statusbar.notification.CustomInterpolatorTransformation;
 import com.android.systemui.statusbar.notification.ImageTransformState;
+import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.TransformState;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 
@@ -69,7 +70,8 @@
     protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
         super(ctx, view, row);
         mShowExpandButtonAtEnd = ctx.getResources().getBoolean(
-                R.bool.config_showNotificationExpandButtonAtEnd);
+                R.bool.config_showNotificationExpandButtonAtEnd)
+                || NotificationUtils.useNewInterruptionModel(ctx);
         mTransformationHelper = new ViewTransformationHelper();
 
         // we want to avoid that the header clashes with the other text when transforming
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index a7329b0..ff31b261 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -401,6 +401,8 @@
      */
     private float mBackgroundXFactor = 1f;
 
+    private boolean mSwipingInProgress;
+
     private boolean mUsingLightTheme;
     private boolean mQsExpanded;
     private boolean mForwardScrollable;
@@ -3286,7 +3288,7 @@
                 || ev.getActionMasked() == MotionEvent.ACTION_UP;
         handleEmptySpaceClick(ev);
         boolean expandWantsIt = false;
-        boolean swipingInProgress = mSwipeHelper.isSwipingInProgress();
+        boolean swipingInProgress = mSwipingInProgress;
         if (mIsExpanded && !swipingInProgress && !mOnlyScrollingInThisMotion) {
             if (isCancelOrUp) {
                 mExpandHelper.onlyObserveMovements(false);
@@ -3341,7 +3343,7 @@
     @Override
     @ShadeViewRefactor(RefactorComponent.INPUT)
     public boolean onGenericMotionEvent(MotionEvent event) {
-        if (!isScrollingEnabled() || !mIsExpanded || mSwipeHelper.isSwipingInProgress() || mExpandingNotification
+        if (!isScrollingEnabled() || !mIsExpanded || mSwipingInProgress || mExpandingNotification
                 || mDisallowScrollingInThisMotion) {
             return false;
         }
@@ -3568,7 +3570,7 @@
         initDownStates(ev);
         handleEmptySpaceClick(ev);
         boolean expandWantsIt = false;
-        boolean swipingInProgress = mSwipeHelper.isSwipingInProgress();
+        boolean swipingInProgress = mSwipingInProgress;
         if (!swipingInProgress && !mOnlyScrollingInThisMotion) {
             expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
         }
@@ -3847,6 +3849,14 @@
         }
     }
 
+    @ShadeViewRefactor(RefactorComponent.INPUT)
+    private void setSwipingInProgress(boolean swiping) {
+        mSwipingInProgress = swiping;
+        if (swiping) {
+            requestDisallowInterceptTouchEvent(true);
+        }
+    }
+
     @Override
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void onWindowFocusChanged(boolean hasWindowFocus) {
@@ -5642,6 +5652,7 @@
 
         @Override
         public void onDragCancelled(View v) {
+            setSwipingInProgress(false);
             mFalsingManager.onNotificatonStopDismissing();
         }
 
@@ -5669,6 +5680,7 @@
          */
 
         public void handleChildViewDismissed(View view) {
+            setSwipingInProgress(false);
             if (mDismissAllInProgress) {
                 return;
             }
@@ -5737,6 +5749,7 @@
         @Override
         public void onBeginDrag(View v) {
             mFalsingManager.onNotificatonStartDismissing();
+            setSwipingInProgress(true);
             mAmbientState.onBeginDrag(v);
             updateContinuousShadowDrawing();
             if (mAnimationsEnabled && (mIsExpanded || !isPinnedHeadsUp(v))) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index 599da3b..f1d9549 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -165,15 +165,15 @@
 
         if (menuRow.isSnappedAndOnSameSide()) {
             // Menu was snapped to previously and we're on the same side
-            handleSwipeFromSnap(ev, animView, velocity, menuRow);
+            handleSwipeFromOpenState(ev, animView, velocity, menuRow);
         } else {
             // Menu has not been snapped, or was snapped previously but is now on
             // the opposite side.
-            handleSwipeFromNonSnap(ev, animView, velocity, menuRow);
+            handleSwipeFromClosedState(ev, animView, velocity, menuRow);
         }
     }
 
-    private void handleSwipeFromNonSnap(MotionEvent ev, View animView, float velocity,
+    private void handleSwipeFromClosedState(MotionEvent ev, View animView, float velocity,
             NotificationMenuRowPlugin menuRow) {
         boolean isDismissGesture = isDismissGesture(ev);
         final boolean gestureTowardsMenu = menuRow.isTowardsMenu(velocity);
@@ -183,10 +183,14 @@
         final boolean showMenuForSlowOnGoing = !menuRow.canBeDismissed()
                 && timeForGesture >= SWIPE_MENU_TIMING;
 
-        if (!isFalseGesture(ev)
-                && (swipedEnoughToShowMenu(menuRow)
-                && (!gestureFastEnough || showMenuForSlowOnGoing))
-                || (gestureTowardsMenu && !isDismissGesture)) {
+        boolean isNonDismissGestureTowardsMenu = gestureTowardsMenu && !isDismissGesture;
+        boolean isSlowSwipe = !gestureFastEnough || showMenuForSlowOnGoing;
+        boolean slowSwipedFarEnough = swipedEnoughToShowMenu(menuRow) && isSlowSwipe;
+        boolean isFastNonDismissGesture =
+                gestureFastEnough && !gestureTowardsMenu && !isDismissGesture;
+        boolean isMenuRevealingGestureAwayFromMenu = slowSwipedFarEnough || isFastNonDismissGesture;
+        if (isNonDismissGestureTowardsMenu
+                || (!isFalseGesture(ev) && isMenuRevealingGestureAwayFromMenu)) {
             // Menu has not been snapped to previously and this is menu revealing gesture
             snapOpen(animView, menuRow.getMenuSnapTarget(), velocity);
             menuRow.onSnapOpen();
@@ -199,7 +203,7 @@
         }
     }
 
-    private void handleSwipeFromSnap(MotionEvent ev, View animView, float velocity,
+    private void handleSwipeFromOpenState(MotionEvent ev, View animView, float velocity,
             NotificationMenuRowPlugin menuRow) {
         boolean isDismissGesture = isDismissGesture(ev);
 
@@ -227,7 +231,6 @@
         if (mCallback.isExpanded()) {
             // We don't want to quick-dismiss when it's a heads up as this might lead to closing
             // of the panel early.
-            mSwipingInProgress = false;
             mCallback.handleChildViewDismissed(view);
         }
         mCallback.onDismiss();
@@ -247,7 +250,6 @@
     @Override
     public void snapChild(final View animView, final float targetLeft, float velocity) {
         superSnapChild(animView, targetLeft, velocity);
-        mSwipingInProgress = false;
         mCallback.onDragCancelled(animView);
         if (targetLeft == 0) {
             handleMenuCoveredOrDismissed();
@@ -354,7 +356,6 @@
 
     public void onMenuShown(View animView) {
         setExposedMenuView(getTranslatingParentView());
-        mSwipingInProgress = false;
         mCallback.onDragCancelled(animView);
         Handler handler = getHandler();
 
@@ -422,4 +423,4 @@
 
         void onDismiss();
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index d5067b5..1be3975 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -22,6 +22,7 @@
 import android.graphics.PorterDuff;
 import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.Drawable;
+import android.os.SystemProperties;
 import android.util.AttributeSet;
 import android.view.accessibility.AccessibilityNodeInfo;
 
@@ -44,6 +45,8 @@
     private static final int STATE_FACE_UNLOCK = 2;
     private static final int STATE_FINGERPRINT = 3;
     private static final int STATE_FINGERPRINT_ERROR = 4;
+    private static final boolean HOLLOW_PILL = SystemProperties
+            .getBoolean("persist.sysui.hollow_pill", false);
 
     private int mLastState = 0;
     private boolean mLastDeviceInteractive;
@@ -221,6 +224,16 @@
                 throw new IllegalArgumentException();
         }
 
+        if (HOLLOW_PILL && deviceInteractive) {
+            switch (state) {
+                case STATE_FINGERPRINT:
+                case STATE_LOCK_OPEN:
+                case STATE_LOCKED:
+                case STATE_FACE_UNLOCK:
+                    iconRes = R.drawable.ic_home_button_outline;
+            }
+        }
+
         return mContext.getDrawable(iconRes);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index fa71df2..851e6d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -2787,7 +2787,9 @@
         }
 
         final float darkAmount = dozing && !mSemiAwake ? 1 : 0;
-        mStatusBarStateController.setDozeAmount(darkAmount, animate);
+        if (!mSemiAwake) {
+            mStatusBarStateController.setDozeAmount(darkAmount, animate);
+        }
         if (animate) {
             mNotificationStackScroller.notifyDarkAnimationStart(mDozing);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index ade063d..286eea0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.phone;
 
 import android.graphics.Color;
+import android.os.SystemProperties;
 import android.os.Trace;
 import android.util.MathUtils;
 
@@ -75,7 +76,12 @@
         public void prepare(ScrimState previousState) {
             mBlankScreen = mDisplayRequiresBlanking && previousState != ScrimState.AOD;
             mAnimationDuration = StackStateAnimator.ANIMATION_DURATION_WAKEUP;
-            mCurrentBehindAlpha = ScrimController.GRADIENT_SCRIM_DARK_KEYGUARD;
+            String opacity = SystemProperties.get("persist.sysui.aod2_scrim_opacity", "0.8");
+            try {
+                mCurrentBehindAlpha = Float.parseFloat(opacity);
+            } catch (RuntimeException e) {
+                mCurrentBehindAlpha = ScrimController.GRADIENT_SCRIM_DARK_KEYGUARD;
+            }
             mCurrentInFrontAlpha = 0;
             mCurrentInFrontTint = Color.BLACK;
             mCurrentBehindTint = Color.BLACK;
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 a6a9d74..1c4538e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -3264,6 +3264,10 @@
 
         mNotificationPanel.setDozing(mDozing, animate, mWakeUpTouchLocation,
                 mDozeServiceHost.wasPassivelyInterrupted());
+        if (mNotificationPanel.isSemiAwake()
+                && SystemProperties.getBoolean("persist.systemui.show_swipe_up", false)) {
+            mKeyguardIndicationController.showTransientIndication(R.string.keyguard_unlock);
+        }
         updateQsExpansionEnabled();
         Trace.endSection();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogBuilderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogBuilderTest.kt
index 7204d31..b23f667 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogBuilderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogBuilderTest.kt
@@ -27,55 +27,28 @@
 @SmallTest
 class PrivacyDialogBuilderTest : SysuiTestCase() {
 
-    companion object {
-        val MILLIS_IN_MINUTE: Long = 1000 * 60
-        val NOW = 4 * MILLIS_IN_MINUTE
-    }
-
     @Test
-    fun testGenerateText_multipleApps() {
+    fun testGenerateAppsList() {
         val bar2 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication(
-                "Bar", context), 2 * MILLIS_IN_MINUTE)
+                "Bar", context))
         val bar3 = PrivacyItem(Privacy.TYPE_LOCATION, PrivacyApplication(
-                "Bar", context), 3 * MILLIS_IN_MINUTE)
+                "Bar", context))
         val foo0 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication(
-                "Foo", context), 0)
+                "Foo", context))
         val baz1 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication(
-                "Baz", context), 1 * MILLIS_IN_MINUTE)
+                "Baz", context))
 
         val items = listOf(bar2, foo0, baz1, bar3)
 
         val textBuilder = PrivacyDialogBuilder(context, items)
 
-        val textList = textBuilder.generateText(NOW)
-        assertEquals(2, textList.size)
-        assertEquals("Bar, Foo, Baz are using your camera", textList[0])
-        assertEquals("Bar is using your location for the last 1 min", textList[1])
-    }
-
-    @Test
-    fun testGenerateText_singleApp() {
-        val bar2 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication(
-                "Bar", context), 0)
-        val bar1 = PrivacyItem(Privacy.TYPE_LOCATION, PrivacyApplication(
-                "Bar", context), 0)
-
-        val items = listOf(bar2, bar1)
-
-        val textBuilder = PrivacyDialogBuilder(context, items)
-        val textList = textBuilder.generateText(NOW)
-        assertEquals(1, textList.size)
-        assertEquals("Bar is using your camera, location", textList[0])
-    }
-
-    @Test
-    fun testGenerateText_singleApp_singleType() {
-        val bar2 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication(
-                "Bar", context), 2 * MILLIS_IN_MINUTE)
-        val items = listOf(bar2)
-        val textBuilder = PrivacyDialogBuilder(context, items)
-        val textList = textBuilder.generateText(NOW)
-        assertEquals(1, textList.size)
-        assertEquals("Bar is using your camera for the last 2 min", textList[0])
+        val list = textBuilder.appsAndTypes
+        assertEquals(3, list.size)
+        val appsList = list.map { it.first }
+        val typesList = list.map { it.second }
+        assertEquals(listOf("Bar", "Baz", "Foo"), appsList.map { it.packageName })
+        assertEquals(listOf(Privacy.TYPE_CAMERA, Privacy.TYPE_LOCATION), typesList[0])
+        assertEquals(listOf(Privacy.TYPE_CAMERA), typesList[1])
+        assertEquals(listOf(Privacy.TYPE_CAMERA), typesList[2])
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index dcd5946..db39373 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -32,8 +32,8 @@
 import android.os.Handler;
 import android.service.notification.StatusBarNotification;
 import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.testing.UiThreadTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.view.MotionEvent;
 import android.view.View;
 
@@ -44,7 +44,6 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -56,9 +55,8 @@
  * Tests for {@link NotificationSwipeHelper}.
  */
 @SmallTest
-@Ignore
-@RunWith(AndroidJUnit4.class)
-@UiThreadTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper()
 public class NotificationSwipeHelperTest extends SysuiTestCase {
 
     private NotificationSwipeHelper mSwipeHelper;
diff --git a/packages/services/PacProcessor/Android.mk b/packages/services/PacProcessor/Android.mk
index 75d0363..295b3d8 100644
--- a/packages/services/PacProcessor/Android.mk
+++ b/packages/services/PacProcessor/Android.mk
@@ -29,5 +29,3 @@
 LOCAL_JNI_SHARED_LIBRARIES := libjni_pacprocessor libpac
 
 include $(BUILD_PACKAGE)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/packages/services/PacProcessor/jni/Android.bp b/packages/services/PacProcessor/jni/Android.bp
new file mode 100644
index 0000000..2a94237
--- /dev/null
+++ b/packages/services/PacProcessor/jni/Android.bp
@@ -0,0 +1,40 @@
+//
+// Copyright (C) 2013 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.
+//
+
+cc_library_shared {
+    name: "libjni_pacprocessor",
+
+    srcs: [
+        "jni_init.cpp",
+        "com_android_pacprocessor_PacNative.cpp",
+    ],
+
+    shared_libs: [
+        "libandroidfw",
+        "libandroid_runtime",
+        "liblog",
+        "libutils",
+        "libnativehelper",
+        "libpac",
+    ],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wunused",
+        "-Wunreachable-code",
+    ],
+}
diff --git a/packages/services/PacProcessor/jni/Android.mk b/packages/services/PacProcessor/jni/Android.mk
deleted file mode 100644
index 254cbc2..0000000
--- a/packages/services/PacProcessor/jni/Android.mk
+++ /dev/null
@@ -1,41 +0,0 @@
-#
-# Copyright (C) 2013 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.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
-    jni_init.cpp \
-    com_android_pacprocessor_PacNative.cpp
-
-LOCAL_C_INCLUDES += \
-    external/chromium-libpac/src
-
-LOCAL_SHARED_LIBRARIES := \
-    libandroidfw \
-    libandroid_runtime \
-    liblog \
-    libutils \
-    libnativehelper \
-    libpac
-
-LOCAL_MODULE := libjni_pacprocessor
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 1ad83ec..7621ada 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -11,7 +11,7 @@
  * 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
+ * limitations under the License.
  */
 
 package com.android.server.backup;
@@ -104,7 +104,6 @@
 import com.android.server.backup.fullbackup.FullBackupEntry;
 import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
 import com.android.server.backup.internal.BackupHandler;
-import com.android.server.backup.keyvalue.BackupRequest;
 import com.android.server.backup.internal.ClearDataObserver;
 import com.android.server.backup.internal.OnTaskFinishedListener;
 import com.android.server.backup.internal.Operation;
@@ -112,6 +111,7 @@
 import com.android.server.backup.internal.ProvisionedObserver;
 import com.android.server.backup.internal.RunBackupReceiver;
 import com.android.server.backup.internal.RunInitializeReceiver;
+import com.android.server.backup.keyvalue.BackupRequest;
 import com.android.server.backup.params.AdbBackupParams;
 import com.android.server.backup.params.AdbParams;
 import com.android.server.backup.params.AdbRestoreParams;
@@ -159,17 +159,20 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicInteger;
 
+/** System service that performs backup/restore operations. */
 public class BackupManagerService {
-
     public static final String TAG = "BackupManagerService";
     public static final boolean DEBUG = true;
     public static final boolean MORE_DEBUG = false;
-    public static final boolean DEBUG_SCHEDULING = MORE_DEBUG || true;
+    public static final boolean DEBUG_SCHEDULING = true;
 
     // File containing backup-enabled state.  Contains a single byte;
     // nonzero == enabled.  File missing or contains a zero byte == disabled.
     private static final String BACKUP_ENABLE_FILE = "backup_enabled";
 
+    // Persistently track the need to do a full init.
+    private static final String INIT_SENTINEL_FILE_NAME = "_need_init_";
+
     // System-private key used for backing up an app's widget state.  Must
     // begin with U+FFxx by convention (we reserve all keys starting
     // with U+FF00 or higher for system use).
@@ -196,11 +199,16 @@
     public static final int BACKUP_METADATA_VERSION = 1;
     public static final int BACKUP_WIDGET_METADATA_TOKEN = 0x01FFED01;
 
-    private static final boolean COMPRESS_FULL_BACKUPS = true; // should be true in production
+    private static final int CURRENT_ANCESTRAL_RECORD_VERSION = 1;
+
+    // Round-robin queue for scheduling full backup passes.
+    private static final int SCHEDULE_FILE_VERSION = 1;
 
     public static final String SETTINGS_PACKAGE = "com.android.providers.settings";
     public static final String SHARED_BACKUP_AGENT_PACKAGE = "com.android.sharedstoragebackup";
-    private static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
+
+    // Pseudoname that we use for the Package Manager metadata "package".
+    public static final String PACKAGE_MANAGER_SENTINEL = "@pm@";
 
     // Retry interval for clear/init when the transport is unavailable
     private static final long TRANSPORT_RETRY_INTERVAL = 1 * AlarmManager.INTERVAL_HOUR;
@@ -210,8 +218,23 @@
     public static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED";
     public static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName";
 
-    // Time delay for initialization operations that can be delayed so as not to consume too much CPU
-    // on bring-up and increase time-to-UI.
+    // Bookkeeping of in-flight operations. The operation token is the index of the entry in the
+    // pending operations list.
+    public static final int OP_PENDING = 0;
+    private static final int OP_ACKNOWLEDGED = 1;
+    private static final int OP_TIMEOUT = -1;
+
+    // Waiting for backup agent to respond during backup operation.
+    public static final int OP_TYPE_BACKUP_WAIT = 0;
+
+    // Waiting for backup agent to respond during restore operation.
+    public static final int OP_TYPE_RESTORE_WAIT = 1;
+
+    // An entire backup operation spanning multiple packages.
+    public static final int OP_TYPE_BACKUP = 2;
+
+    // Time delay for initialization operations that can be delayed so as not to consume too much
+    // CPU on bring-up and increase time-to-UI.
     private static final long INITIALIZATION_DELAY_MILLIS = 3000;
 
     // Timeout interval for deciding that a bind or clear-data has taken too long
@@ -226,8 +249,62 @@
     private static final long BUSY_BACKOFF_MIN_MILLIS = 1000 * 60 * 60;  // one hour
     private static final int BUSY_BACKOFF_FUZZ = 1000 * 60 * 60 * 2;  // two hours
 
-    private BackupManagerConstants mConstants;
+    // The published binder is a singleton Trampoline object that calls through to the proper code.
+    // This indirection lets us turn down the heavy implementation object on the fly without
+    // disturbing binders that have been cached elsewhere in the system.
+    private static Trampoline sInstance;
+
+    static Trampoline getInstance() {
+        // Always constructed during system bring up, so no need to lazy-init.
+        return sInstance;
+    }
+
+    /** Helper to create the {@link BackupManagerService} instance. */
+    public static BackupManagerService create(
+            Context context,
+            Trampoline parent,
+            HandlerThread backupThread) {
+        // Set up our transport options and initialize the default transport
+        SystemConfig systemConfig = SystemConfig.getInstance();
+        Set<ComponentName> transportWhitelist = systemConfig.getBackupTransportWhitelist();
+        if (transportWhitelist == null) {
+            transportWhitelist = Collections.emptySet();
+        }
+
+        String transport =
+                Settings.Secure.getString(
+                        context.getContentResolver(), Settings.Secure.BACKUP_TRANSPORT);
+        if (TextUtils.isEmpty(transport)) {
+            transport = null;
+        }
+        if (DEBUG) {
+            Slog.v(TAG, "Starting with transport " + transport);
+        }
+        TransportManager transportManager =
+                new TransportManager(
+                        context,
+                        transportWhitelist,
+                        transport);
+
+        // If encrypted file systems is enabled or disabled, this call will return the
+        // correct directory.
+        File baseStateDir = new File(Environment.getDataDirectory(), "backup");
+
+        // This dir on /cache is managed directly in init.rc
+        File dataDir = new File(Environment.getDownloadCacheDirectory(), "backup_stage");
+
+        return new BackupManagerService(
+                context,
+                parent,
+                backupThread,
+                baseStateDir,
+                dataDir,
+                transportManager);
+    }
+
     private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
+    private final TransportManager mTransportManager;
+
     private Context mContext;
     private PackageManager mPackageManager;
     private IPackageManager mPackageManagerBinder;
@@ -235,30 +312,26 @@
     private PowerManager mPowerManager;
     private AlarmManager mAlarmManager;
     private IStorageManager mStorageManager;
+    private BackupManagerConstants mConstants;
+    private PowerManager.WakeLock mWakelock;
+    private BackupHandler mBackupHandler;
 
     private IBackupManager mBackupManagerBinder;
 
-    private final TransportManager mTransportManager;
-
     private boolean mEnabled;   // access to this is synchronized on 'this'
     private boolean mProvisioned;
     private boolean mAutoRestore;
-    private PowerManager.WakeLock mWakelock;
-    private BackupHandler mBackupHandler;
+
     private PendingIntent mRunBackupIntent;
     private PendingIntent mRunInitIntent;
-    private BroadcastReceiver mRunBackupReceiver;
-    private BroadcastReceiver mRunInitReceiver;
+
+    private final ArraySet<String> mPendingInits = new ArraySet<>();  // transport names
+
     // map UIDs to the set of participating packages under that UID
-    private final SparseArray<HashSet<String>> mBackupParticipants
-            = new SparseArray<>();
+    private final SparseArray<HashSet<String>> mBackupParticipants = new SparseArray<>();
 
     // Backups that we haven't started yet.  Keys are package names.
-    private HashMap<String, BackupRequest> mPendingBackups
-            = new HashMap<>();
-
-    // Pseudoname that we use for the Package Manager metadata "package"
-    public static final String PACKAGE_MANAGER_SENTINEL = "@pm@";
+    private HashMap<String, BackupRequest> mPendingBackups = new HashMap<>();
 
     // locking around the pending-backup management
     private final Object mQueueLock = new Object();
@@ -269,25 +342,32 @@
     // completed.
     private final Object mAgentConnectLock = new Object();
     private IBackupAgent mConnectedAgent;
-    private volatile boolean mBackupRunning;
     private volatile boolean mConnecting;
-    private volatile long mLastBackupPass;
 
-    // For debugging, we maintain a progress trace of operations during backup
-    public static final boolean DEBUG_BACKUP_TRACE = true;
-    private final List<String> mBackupTrace = new ArrayList<>();
+    private volatile boolean mBackupRunning;
+    private volatile long mLastBackupPass;
 
     // A similar synchronization mechanism around clearing apps' data for restore
     private final Object mClearDataLock = new Object();
     private volatile boolean mClearingData;
 
+    // Used by ADB.
     private final BackupPasswordManager mBackupPasswordManager;
+    private final SparseArray<AdbParams> mAdbBackupRestoreConfirmations = new SparseArray<>();
+    private final SecureRandom mRng = new SecureRandom();
 
     // Time when we post the transport registration operation
     private final long mRegisterTransportsRequestedTime;
 
+    @GuardedBy("mQueueLock")
+    private PerformFullTransportBackupTask mRunningFullBackupTask;
+
+    @GuardedBy("mQueueLock")
+    private ArrayList<FullBackupEntry> mFullBackupQueue;
+
     @GuardedBy("mPendingRestores")
     private boolean mIsRestoreInProgress;
+
     @GuardedBy("mPendingRestores")
     private final Queue<PerformUnifiedRestoreTask> mPendingRestores = new ArrayDeque<>();
 
@@ -296,17 +376,155 @@
     // Watch the device provisioning operation during setup
     private ContentObserver mProvisionedObserver;
 
-    // The published binder is actually to a singleton trampoline object that calls
-    // through to the proper code.  This indirection lets us turn down the heavy
-    // implementation object on the fly without disturbing binders that have been
-    // cached elsewhere in the system.
-    static Trampoline sInstance;
+    /**
+     * mCurrentOperations contains the list of currently active operations.
+     *
+     * If type of operation is OP_TYPE_WAIT, it are waiting for an ack or timeout.
+     * An operation wraps a BackupRestoreTask within it.
+     * It's the responsibility of this task to remove the operation from this array.
+     *
+     * A BackupRestore task gets notified of ack/timeout for the operation via
+     * BackupRestoreTask#handleCancel, BackupRestoreTask#operationComplete and notifyAll called
+     * on the mCurrentOpLock.
+     * {@link BackupManagerService#waitUntilOperationComplete(int)} is
+     * used in various places to 'wait' for notifyAll and detect change of pending state of an
+     * operation. So typically, an operation will be removed from this array by:
+     * - BackupRestoreTask#handleCancel and
+     * - BackupRestoreTask#operationComplete OR waitUntilOperationComplete. Do not remove at both
+     * these places because waitUntilOperationComplete relies on the operation being present to
+     * determine its completion status.
+     *
+     * If type of operation is OP_BACKUP, it is a task running backups. It provides a handle to
+     * cancel backup tasks.
+     */
+    @GuardedBy("mCurrentOpLock")
+    private final SparseArray<Operation> mCurrentOperations = new SparseArray<>();
+    private final Object mCurrentOpLock = new Object();
+    private final Random mTokenGenerator = new Random();
+    final AtomicInteger mNextToken = new AtomicInteger();
 
-    static Trampoline getInstance() {
-        // Always constructed during system bringup, so no need to lazy-init
-        return sInstance;
+    // Where we keep our journal files and other bookkeeping.
+    private File mBaseStateDir;
+    private File mDataDir;
+    private File mJournalDir;
+    @Nullable
+    private DataChangedJournal mJournal;
+    private File mFullBackupScheduleFile;
+
+    // Keep a log of all the apps we've ever backed up.
+    private ProcessedPackagesJournal mProcessedPackagesJournal;
+
+    private File mTokenFile;
+    private Set<String> mAncestralPackages = null;
+    private long mAncestralToken = 0;
+    private long mCurrentToken = 0;
+
+    @VisibleForTesting
+    public BackupManagerService(
+            Context context,
+            Trampoline parent,
+            HandlerThread backupThread,
+            File baseStateDir,
+            File dataDir,
+            TransportManager transportManager) {
+        mContext = context;
+        mPackageManager = context.getPackageManager();
+        mPackageManagerBinder = AppGlobals.getPackageManager();
+        mActivityManager = ActivityManager.getService();
+
+        mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+        mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        mStorageManager = IStorageManager.Stub.asInterface(ServiceManager.getService("mount"));
+
+        mBackupManagerBinder = Trampoline.asInterface(parent.asBinder());
+
+        mAgentTimeoutParameters = new
+                BackupAgentTimeoutParameters(Handler.getMain(), mContext.getContentResolver());
+        mAgentTimeoutParameters.start();
+
+        // spin up the backup/restore handler thread
+        mBackupHandler = new BackupHandler(this, backupThread.getLooper());
+
+        // Set up our bookkeeping
+        final ContentResolver resolver = context.getContentResolver();
+        mProvisioned = Settings.Global.getInt(resolver,
+                Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+        mAutoRestore = Settings.Secure.getInt(resolver,
+                Settings.Secure.BACKUP_AUTO_RESTORE, 1) != 0;
+
+        mProvisionedObserver = new ProvisionedObserver(this, mBackupHandler);
+        resolver.registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+                false, mProvisionedObserver);
+
+        mBaseStateDir = baseStateDir;
+        mBaseStateDir.mkdirs();
+        if (!SELinux.restorecon(mBaseStateDir)) {
+            Slog.e(TAG, "SELinux restorecon failed on " + mBaseStateDir);
+        }
+
+        mDataDir = dataDir;
+
+        mBackupPasswordManager = new BackupPasswordManager(mContext, mBaseStateDir, mRng);
+
+        // Alarm receivers for scheduled backups & initialization operations
+        BroadcastReceiver mRunBackupReceiver = new RunBackupReceiver(this);
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(RUN_BACKUP_ACTION);
+        context.registerReceiver(mRunBackupReceiver, filter,
+                android.Manifest.permission.BACKUP, null);
+
+        BroadcastReceiver mRunInitReceiver = new RunInitializeReceiver(this);
+        filter = new IntentFilter();
+        filter.addAction(RUN_INITIALIZE_ACTION);
+        context.registerReceiver(mRunInitReceiver, filter,
+                android.Manifest.permission.BACKUP, null);
+
+        Intent backupIntent = new Intent(RUN_BACKUP_ACTION);
+        backupIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        mRunBackupIntent = PendingIntent.getBroadcast(context, 0, backupIntent, 0);
+
+        Intent initIntent = new Intent(RUN_INITIALIZE_ACTION);
+        initIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        mRunInitIntent = PendingIntent.getBroadcast(context, 0, initIntent, 0);
+
+        // Set up the backup-request journaling
+        mJournalDir = new File(mBaseStateDir, "pending");
+        mJournalDir.mkdirs();   // creates mBaseStateDir along the way
+        mJournal = null;        // will be created on first use
+
+        mConstants = new BackupManagerConstants(mBackupHandler, mContext.getContentResolver());
+        // We are observing changes to the constants throughout the lifecycle of BMS. This is
+        // because we reference the constants in multiple areas of BMS, which otherwise would
+        // require frequent starting and stopping.
+        mConstants.start();
+
+        // Set up the various sorts of package tracking we do
+        mFullBackupScheduleFile = new File(mBaseStateDir, "fb-schedule");
+        initPackageTracking();
+
+        // Build our mapping of uid to backup client services.  This implicitly
+        // schedules a backup pass on the Package Manager metadata the first
+        // time anything needs to be backed up.
+        synchronized (mBackupParticipants) {
+            addPackageParticipantsLocked(null);
+        }
+
+        mTransportManager = transportManager;
+        mTransportManager.setOnTransportRegisteredListener(this::onTransportRegistered);
+        mRegisterTransportsRequestedTime = SystemClock.elapsedRealtime();
+        mBackupHandler.postDelayed(
+                mTransportManager::registerTransports, INITIALIZATION_DELAY_MILLIS);
+
+        // Now that we know about valid backup participants, parse any leftover journal files into
+        // the pending backup set
+        mBackupHandler.postDelayed(this::parseLeftoverJournals, INITIALIZATION_DELAY_MILLIS);
+
+        // Power management
+        mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*");
     }
 
+
     public BackupManagerConstants getConstants() {
         return mConstants;
     }
@@ -549,6 +767,7 @@
         return mPendingInits;
     }
 
+    /** Clear all pending transport initializations. */
     public void clearPendingInits() {
         mPendingInits.clear();
     }
@@ -562,28 +781,10 @@
         mRunningFullBackupTask = runningFullBackupTask;
     }
 
-    public static final class Lifecycle extends SystemService {
-
-        public Lifecycle(Context context) {
-            super(context);
-            sInstance = new Trampoline(context);
-        }
-
-        @Override
-        public void onStart() {
-            publishBinderService(Context.BACKUP_SERVICE, sInstance);
-        }
-
-        @Override
-        public void onUnlockUser(int userId) {
-            if (userId == UserHandle.USER_SYSTEM) {
-                sInstance.unlockSystemUser();
-            }
-        }
-    }
-
-    // Called through the trampoline from onUnlockUser(), then we buck the work
-    // off to the background thread to keep the unlock time down.
+    /**
+     * Called through Trampoline from {@link Lifecycle#onUnlockUser(int)}. We run the heavy work on
+     * a background thread to keep the unlock time down.
+     */
     public void unlockSystemUser() {
         // Migrate legacy setting
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup migrate");
@@ -618,89 +819,10 @@
         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
 
-    // Bookkeeping of in-flight operations for timeout etc. purposes.  The operation
-    // token is the index of the entry in the pending-operations list.
-    public static final int OP_PENDING = 0;
-    private static final int OP_ACKNOWLEDGED = 1;
-    private static final int OP_TIMEOUT = -1;
-
-    // Waiting for backup agent to respond during backup operation.
-    public static final int OP_TYPE_BACKUP_WAIT = 0;
-
-    // Waiting for backup agent to respond during restore operation.
-    public static final int OP_TYPE_RESTORE_WAIT = 1;
-
-    // An entire backup operation spanning multiple packages.
-    public static final int OP_TYPE_BACKUP = 2;
-
     /**
-     * mCurrentOperations contains the list of currently active operations.
-     *
-     * If type of operation is OP_TYPE_WAIT, it are waiting for an ack or timeout.
-     * An operation wraps a BackupRestoreTask within it.
-     * It's the responsibility of this task to remove the operation from this array.
-     *
-     * A BackupRestore task gets notified of ack/timeout for the operation via
-     * BackupRestoreTask#handleCancel, BackupRestoreTask#operationComplete and notifyAll called
-     * on the mCurrentOpLock.
-     * {@link BackupManagerService#waitUntilOperationComplete(int)} is
-     * used in various places to 'wait' for notifyAll and detect change of pending state of an
-     * operation. So typically, an operation will be removed from this array by:
-     * - BackupRestoreTask#handleCancel and
-     * - BackupRestoreTask#operationComplete OR waitUntilOperationComplete. Do not remove at both
-     * these places because waitUntilOperationComplete relies on the operation being present to
-     * determine its completion status.
-     *
-     * If type of operation is OP_BACKUP, it is a task running backups. It provides a handle to
-     * cancel backup tasks.
+     *  Utility: build a new random integer token. The low bits are the ordinal of the operation for
+     *  near-time uniqueness, and the upper bits are random for app-side unpredictability.
      */
-    @GuardedBy("mCurrentOpLock")
-    private final SparseArray<Operation> mCurrentOperations = new SparseArray<>();
-    private final Object mCurrentOpLock = new Object();
-    private final Random mTokenGenerator = new Random();
-    final AtomicInteger mNextToken = new AtomicInteger();
-
-    private final SparseArray<AdbParams> mAdbBackupRestoreConfirmations = new SparseArray<>();
-
-    // Where we keep our journal files and other bookkeeping
-    private File mBaseStateDir;
-    private File mDataDir;
-    private File mJournalDir;
-    @Nullable
-    private DataChangedJournal mJournal;
-
-    private final SecureRandom mRng = new SecureRandom();
-
-    // Keep a log of all the apps we've ever backed up, and what the dataset tokens are for both
-    // the current backup dataset and the ancestral dataset.
-    private ProcessedPackagesJournal mProcessedPackagesJournal;
-
-    private static final int CURRENT_ANCESTRAL_RECORD_VERSION = 1;
-    // increment when the schema changes
-    private File mTokenFile;
-    private Set<String> mAncestralPackages = null;
-    private long mAncestralToken = 0;
-    private long mCurrentToken = 0;
-
-    // Persistently track the need to do a full init
-    private static final String INIT_SENTINEL_FILE_NAME = "_need_init_";
-    private final ArraySet<String> mPendingInits = new ArraySet<>();  // transport names
-
-    // Round-robin queue for scheduling full backup passes
-    private static final int SCHEDULE_FILE_VERSION = 1; // current version of the schedule file
-
-    private File mFullBackupScheduleFile;
-    // If we're running a schedule-driven full backup, this is the task instance doing it
-
-    @GuardedBy("mQueueLock")
-    private PerformFullTransportBackupTask mRunningFullBackupTask;
-
-    @GuardedBy("mQueueLock")
-    private ArrayList<FullBackupEntry> mFullBackupQueue;
-
-    // Utility: build a new random integer token. The low bits are the ordinal of the
-    // operation for near-time uniqueness, and the upper bits are random for app-
-    // side unpredictability.
     public int generateRandomIntegerToken() {
         int token = mTokenGenerator.nextInt();
         if (token < 0) token = -token;
@@ -709,10 +831,9 @@
         return token;
     }
 
-    /*
-     * Construct a backup agent instance for the metadata pseudopackage.  This is a
-     * process-local non-lifecycle agent instance, so we manually set up the context
-     * topology for it.
+    /**
+     * Construct a backup agent instance for the metadata pseudopackage. This is a process-local
+     * non-lifecycle agent instance, so we manually set up the context topology for it.
      */
     public BackupAgent makeMetadataAgent() {
         PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(mPackageManager);
@@ -721,8 +842,8 @@
         return pmAgent;
     }
 
-    /*
-     * Same as above but with the explicit package-set configuration.
+    /**
+     * Same as {@link #makeMetadataAgent()} but with explicit package-set configuration.
      */
     public PackageManagerBackupAgent makeMetadataAgent(List<PackageInfo> packages) {
         PackageManagerBackupAgent pmAgent =
@@ -732,172 +853,6 @@
         return pmAgent;
     }
 
-    // ----- Debug-only backup operation trace -----
-    public void addBackupTrace(String s) {
-        if (DEBUG_BACKUP_TRACE) {
-            synchronized (mBackupTrace) {
-                mBackupTrace.add(s);
-            }
-        }
-    }
-
-    public void clearBackupTrace() {
-        if (DEBUG_BACKUP_TRACE) {
-            synchronized (mBackupTrace) {
-                mBackupTrace.clear();
-            }
-        }
-    }
-
-    // ----- Main service implementation -----
-
-    public static BackupManagerService create(
-            Context context,
-            Trampoline parent,
-            HandlerThread backupThread) {
-        // Set up our transport options and initialize the default transport
-        SystemConfig systemConfig = SystemConfig.getInstance();
-        Set<ComponentName> transportWhitelist = systemConfig.getBackupTransportWhitelist();
-        if (transportWhitelist == null) {
-            transportWhitelist = Collections.emptySet();
-        }
-
-        String transport =
-                Settings.Secure.getString(
-                        context.getContentResolver(), Settings.Secure.BACKUP_TRANSPORT);
-        if (TextUtils.isEmpty(transport)) {
-            transport = null;
-        }
-        if (DEBUG) {
-            Slog.v(TAG, "Starting with transport " + transport);
-        }
-        TransportManager transportManager =
-                new TransportManager(
-                        context,
-                        transportWhitelist,
-                        transport);
-
-        // If encrypted file systems is enabled or disabled, this call will return the
-        // correct directory.
-        File baseStateDir = new File(Environment.getDataDirectory(), "backup");
-
-        // This dir on /cache is managed directly in init.rc
-        File dataDir = new File(Environment.getDownloadCacheDirectory(), "backup_stage");
-
-        return new BackupManagerService(
-                context,
-                parent,
-                backupThread,
-                baseStateDir,
-                dataDir,
-                transportManager);
-    }
-
-    @VisibleForTesting
-    public BackupManagerService(
-            Context context,
-            Trampoline parent,
-            HandlerThread backupThread,
-            File baseStateDir,
-            File dataDir,
-            TransportManager transportManager) {
-        mContext = context;
-        mPackageManager = context.getPackageManager();
-        mPackageManagerBinder = AppGlobals.getPackageManager();
-        mActivityManager = ActivityManager.getService();
-
-        mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
-        mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-        mStorageManager = IStorageManager.Stub.asInterface(ServiceManager.getService("mount"));
-
-        mBackupManagerBinder = Trampoline.asInterface(parent.asBinder());
-
-        mAgentTimeoutParameters = new
-                BackupAgentTimeoutParameters(Handler.getMain(), mContext.getContentResolver());
-        mAgentTimeoutParameters.start();
-
-        // spin up the backup/restore handler thread
-        mBackupHandler = new BackupHandler(this, backupThread.getLooper());
-
-        // Set up our bookkeeping
-        final ContentResolver resolver = context.getContentResolver();
-        mProvisioned = Settings.Global.getInt(resolver,
-                Settings.Global.DEVICE_PROVISIONED, 0) != 0;
-        mAutoRestore = Settings.Secure.getInt(resolver,
-                Settings.Secure.BACKUP_AUTO_RESTORE, 1) != 0;
-
-        mProvisionedObserver = new ProvisionedObserver(this, mBackupHandler);
-        resolver.registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
-                false, mProvisionedObserver);
-
-        mBaseStateDir = baseStateDir;
-        mBaseStateDir.mkdirs();
-        if (!SELinux.restorecon(mBaseStateDir)) {
-            Slog.e(TAG, "SELinux restorecon failed on " + mBaseStateDir);
-        }
-
-        mDataDir = dataDir;
-
-        mBackupPasswordManager = new BackupPasswordManager(mContext, mBaseStateDir, mRng);
-
-        // Alarm receivers for scheduled backups & initialization operations
-        mRunBackupReceiver = new RunBackupReceiver(this);
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(RUN_BACKUP_ACTION);
-        context.registerReceiver(mRunBackupReceiver, filter,
-                android.Manifest.permission.BACKUP, null);
-
-        mRunInitReceiver = new RunInitializeReceiver(this);
-        filter = new IntentFilter();
-        filter.addAction(RUN_INITIALIZE_ACTION);
-        context.registerReceiver(mRunInitReceiver, filter,
-                android.Manifest.permission.BACKUP, null);
-
-        Intent backupIntent = new Intent(RUN_BACKUP_ACTION);
-        backupIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-        mRunBackupIntent = PendingIntent.getBroadcast(context, 0, backupIntent, 0);
-
-        Intent initIntent = new Intent(RUN_INITIALIZE_ACTION);
-        initIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-        mRunInitIntent = PendingIntent.getBroadcast(context, 0, initIntent, 0);
-
-        // Set up the backup-request journaling
-        mJournalDir = new File(mBaseStateDir, "pending");
-        mJournalDir.mkdirs();   // creates mBaseStateDir along the way
-        mJournal = null;        // will be created on first use
-
-        mConstants = new BackupManagerConstants(mBackupHandler, mContext.getContentResolver());
-        // We are observing changes to the constants throughout the lifecycle of BMS. This is
-        // because we reference the constants in multiple areas of BMS, which otherwise would
-        // require frequent starting and stopping.
-        mConstants.start();
-
-        // Set up the various sorts of package tracking we do
-        mFullBackupScheduleFile = new File(mBaseStateDir, "fb-schedule");
-        initPackageTracking();
-
-        // Build our mapping of uid to backup client services.  This implicitly
-        // schedules a backup pass on the Package Manager metadata the first
-        // time anything needs to be backed up.
-        synchronized (mBackupParticipants) {
-            addPackageParticipantsLocked(null);
-        }
-
-        mTransportManager = transportManager;
-        mTransportManager.setOnTransportRegisteredListener(this::onTransportRegistered);
-        mRegisterTransportsRequestedTime = SystemClock.elapsedRealtime();
-        mBackupHandler.postDelayed(
-                mTransportManager::registerTransports, INITIALIZATION_DELAY_MILLIS);
-
-        // Now that we know about valid backup participants, parse any leftover journal files into
-        // the pending backup set
-        mBackupHandler.postDelayed(this::parseLeftoverJournals, INITIALIZATION_DELAY_MILLIS);
-
-        // Power management
-        mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*");
-    }
-
     private void initPackageTracking() {
         if (MORE_DEBUG) Slog.v(TAG, "` tracking");
 
@@ -965,16 +920,16 @@
                     return null;
                 }
 
-                final int N = in.readInt();
-                schedule = new ArrayList<>(N);
+                final int numPackages = in.readInt();
+                schedule = new ArrayList<>(numPackages);
 
                 // HashSet instead of ArraySet specifically because we want the eventual
                 // lookups against O(hundreds) of entries to be as fast as possible, and
                 // we discard the set immediately after the scan so the extra memory
                 // overhead is transient.
-                HashSet<String> foundApps = new HashSet<>(N);
+                HashSet<String> foundApps = new HashSet<>(numPackages);
 
-                for (int i = 0; i < N; i++) {
+                for (int i = 0; i < numPackages; i++) {
                     String pkgName = in.readUTF();
                     long lastBackup = in.readLong();
                     foundApps.add(pkgName); // all apps that we've addressed already
@@ -1057,10 +1012,10 @@
                     //     [utf8] package name
                     //     [long] last backup time for this package
                     //     }
-                    int N = mFullBackupQueue.size();
-                    bufOut.writeInt(N);
+                    int numPackages = mFullBackupQueue.size();
+                    bufOut.writeInt(numPackages);
 
-                    for (int i = 0; i < N; i++) {
+                    for (int i = 0; i < numPackages; i++) {
                         FullBackupEntry entry = mFullBackupQueue.get(i);
                         bufOut.writeUTF(entry.packageName);
                         bufOut.writeLong(entry.lastBackup);
@@ -1100,21 +1055,24 @@
         }
     }
 
-    // Used for generating random salts or passwords
+    /** Used for generating random salts or passwords. */
     public byte[] randomBytes(int bits) {
         byte[] array = new byte[bits / 8];
         mRng.nextBytes(array);
         return array;
     }
 
+    /** For adb backup/restore. */
     public boolean setBackupPassword(String currentPw, String newPw) {
         return mBackupPasswordManager.setBackupPassword(currentPw, newPw);
     }
 
+    /** For adb backup/restore. */
     public boolean hasBackupPassword() {
         return mBackupPasswordManager.hasBackupPassword();
     }
 
+    /** For adb backup/restore. */
     public boolean backupPasswordMatches(String currentPw) {
         return mBackupPasswordManager.backupPasswordMatches(currentPw);
     }
@@ -1151,9 +1109,10 @@
         }
     }
 
-    // Reset all of our bookkeeping, in response to having been told that
-    // the backend data has been wiped [due to idle expiry, for example],
-    // so we must re-upload all saved settings.
+    /**
+     * Reset all of our bookkeeping because the backend data has been wiped (for example due to idle
+     * expiry), so we must re-upload all saved settings.
+     */
     public void resetBackupState(File stateFileDir) {
         synchronized (mQueueLock) {
             mProcessedPackagesJournal.reset();
@@ -1172,8 +1131,8 @@
 
         // Enqueue a new backup of every participant
         synchronized (mBackupParticipants) {
-            final int N = mBackupParticipants.size();
-            for (int i = 0; i < N; i++) {
+            final int numParticipants = mBackupParticipants.size();
+            for (int i = 0; i < numParticipants; i++) {
                 HashSet<String> participants = mBackupParticipants.valueAt(i);
                 if (participants != null) {
                     for (String packageName : participants) {
@@ -1217,10 +1176,10 @@
             boolean added = false;
             boolean changed = false;
             Bundle extras = intent.getExtras();
-            String pkgList[] = null;
-            if (Intent.ACTION_PACKAGE_ADDED.equals(action) ||
-                    Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
-                    Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
+            String[] pkgList = null;
+            if (Intent.ACTION_PACKAGE_ADDED.equals(action)
+                    || Intent.ACTION_PACKAGE_REMOVED.equals(action)
+                    || Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
                 Uri uri = intent.getData();
                 if (uri == null) {
                     return;
@@ -1413,8 +1372,8 @@
         // !!! TODO: cache this and regenerate only when necessary
         int flags = PackageManager.GET_SIGNING_CERTIFICATES;
         List<PackageInfo> packages = mPackageManager.getInstalledPackages(flags);
-        int N = packages.size();
-        for (int a = N - 1; a >= 0; a--) {
+        int numPackages = packages.size();
+        for (int a = numPackages - 1; a >= 0; a--) {
             PackageInfo pkg = packages.get(a);
             try {
                 ApplicationInfo app = pkg.applicationInfo;
@@ -1437,9 +1396,10 @@
         return packages;
     }
 
-    // Called from the backup tasks: record that the given app has been successfully
-    // backed up at least once.  This includes both key/value and full-data backups
-    // through the transport.
+    /**
+     * Called from the backup tasks: record that the given app has been successfully backed up at
+     * least once. This includes both key/value and full-data backups through the transport.
+     */
     public void logBackupComplete(String packageName) {
         if (packageName.equals(PACKAGE_MANAGER_SENTINEL)) return;
 
@@ -1447,8 +1407,8 @@
             final Intent notification = new Intent();
             notification.setAction(BACKUP_FINISHED_ACTION);
             notification.setPackage(receiver);
-            notification.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES |
-                    Intent.FLAG_RECEIVER_FOREGROUND);
+            notification.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES
+                    | Intent.FLAG_RECEIVER_FOREGROUND);
             notification.putExtra(BACKUP_FINISHED_PACKAGE_EXTRA, packageName);
             mContext.sendBroadcastAsUser(notification, UserHandle.OWNER);
         }
@@ -1456,9 +1416,10 @@
         mProcessedPackagesJournal.addPackage(packageName);
     }
 
-    // Persistently record the current and ancestral backup tokens as well
-    // as the set of packages with data [supposedly] available in the
-    // ancestral dataset.
+    /**
+     * Persistently record the current and ancestral backup tokens, as well as the set of packages
+     * with data available in the ancestral dataset.
+     */
     public void writeRestoreTokens() {
         try (RandomAccessFile af = new RandomAccessFile(mTokenFile, "rwd")) {
             // First, the version number of this record, for futureproofing
@@ -1512,7 +1473,7 @@
                     }
 
                     // if we timed out with no connect, abort and move on
-                    if (mConnecting == true) {
+                    if (mConnecting) {
                         Slog.w(TAG, "Timeout waiting for agent " + app);
                         mConnectedAgent = null;
                     }
@@ -1533,6 +1494,7 @@
         return agent;
     }
 
+    /** Unbind from a backup agent. */
     public void unbindAgent(ApplicationInfo app) {
         try {
             mActivityManager.unbindBackupAgent(app);
@@ -1541,11 +1503,13 @@
         }
     }
 
-    // clear an application's data, blocking until the operation completes or times out
-    // if keepSystemState is true, we intentionally do not also clear system state that
-    // would ordinarily also be cleared, because we aren't actually wiping the app back
-    // to empty; we're bringing it into the actual expected state related to the already-
-    // restored notification state etc.
+    /**
+     * Clear an application's data, blocking until the operation completes or times out. If {@code
+     * keepSystemState} is {@code true}, we intentionally do not clear system state that would
+     * ordinarily also be cleared, because we aren't actually wiping the app back to empty; we're
+     * bringing it into the actual expected state related to the already-restored notification state
+     * etc.
+     */
     public void clearApplicationDataSynchronous(String packageName, boolean keepSystemState) {
         // Don't wipe packages marked allowClearUserData=false
         try {
@@ -1567,7 +1531,8 @@
         synchronized (mClearDataLock) {
             mClearingData = true;
             try {
-                mActivityManager.clearApplicationUserData(packageName, keepSystemState, observer, 0);
+                mActivityManager.clearApplicationUserData(
+                        packageName, keepSystemState, observer, 0);
             } catch (RemoteException e) {
                 // can't happen because the activity manager is in this process
             }
@@ -1585,8 +1550,10 @@
         }
     }
 
-    // Get the restore-set token for the best-available restore set for this package:
-    // the active set if possible, else the ancestral one.  Returns zero if none available.
+    /**
+     * Get the restore-set token for the best-available restore set for this {@code packageName}:
+     * the active set if possible, else the ancestral one. Returns zero if none available.
+     */
     public long getAvailableRestoreToken(String packageName) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                 "getAvailableRestoreToken");
@@ -1604,10 +1571,19 @@
         return token;
     }
 
+    /**
+     * Requests a backup for the inputted {@code packages}.
+     *
+     * @see #requestBackup(String[], IBackupObserver, IBackupManagerMonitor, int).
+     */
     public int requestBackup(String[] packages, IBackupObserver observer, int flags) {
         return requestBackup(packages, observer, null, flags);
     }
 
+    /**
+     * Requests a backup for the inputted {@code packages} with a specified {@link
+     * IBackupManagerMonitor}.
+     */
     public int requestBackup(String[] packages, IBackupObserver observer,
             IBackupManagerMonitor monitor, int flags) {
         mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "requestBackup");
@@ -1681,8 +1657,8 @@
         EventLog.writeEvent(EventLogTags.BACKUP_REQUESTED, packages.length, kvBackupList.size(),
                 fullBackupList.size());
         if (MORE_DEBUG) {
-            Slog.i(TAG, "Backup requested for " + packages.length + " packages, of them: " +
-                    fullBackupList.size() + " full backups, " + kvBackupList.size()
+            Slog.i(TAG, "Backup requested for " + packages.length + " packages, of them: "
+                    + fullBackupList.size() + " full backups, " + kvBackupList.size()
                     + " k/v backups");
         }
 
@@ -1695,7 +1671,7 @@
         return BackupManager.SUCCESS;
     }
 
-    // Cancel all running backups.
+    /** Cancel all running backups. */
     public void cancelBackups() {
         mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "cancelBackups");
         if (MORE_DEBUG) {
@@ -1725,11 +1701,12 @@
         }
     }
 
+    /** Schedule a timeout message for the operation identified by {@code token}. */
     public void prepareOperationTimeout(int token, long interval, BackupRestoreTask callback,
             int operationType) {
         if (operationType != OP_TYPE_BACKUP_WAIT && operationType != OP_TYPE_RESTORE_WAIT) {
-            Slog.wtf(TAG, "prepareOperationTimeout() doesn't support operation " +
-                    Integer.toHexString(token) + " of type " + operationType);
+            Slog.wtf(TAG, "prepareOperationTimeout() doesn't support operation "
+                    + Integer.toHexString(token) + " of type " + operationType);
             return;
         }
         if (MORE_DEBUG) {
@@ -1752,12 +1729,16 @@
             case OP_TYPE_RESTORE_WAIT:
                 return MSG_RESTORE_OPERATION_TIMEOUT;
             default:
-                Slog.wtf(TAG, "getMessageIdForOperationType called on invalid operation type: " +
-                        operationType);
+                Slog.wtf(TAG, "getMessageIdForOperationType called on invalid operation type: "
+                        + operationType);
                 return -1;
         }
     }
 
+    /**
+     * Add an operation to the list of currently running operations. Used for cancellation,
+     * completion and timeout callbacks that act on the operation via the {@code token}.
+     */
     public void putOperation(int token, Operation operation) {
         if (MORE_DEBUG) {
             Slog.d(TAG, "Adding operation token=" + Integer.toHexString(token) + ", operation type="
@@ -1768,20 +1749,24 @@
         }
     }
 
+    /**
+     * Remove an operation from the list of currently running operations. An operation is removed
+     * when it is completed, cancelled, or timed out, and thus no longer running.
+     */
     public void removeOperation(int token) {
         if (MORE_DEBUG) {
             Slog.d(TAG, "Removing operation token=" + Integer.toHexString(token));
         }
         synchronized (mCurrentOpLock) {
             if (mCurrentOperations.get(token) == null) {
-                Slog.w(TAG, "Duplicate remove for operation. token=" +
-                        Integer.toHexString(token));
+                Slog.w(TAG, "Duplicate remove for operation. token="
+                        + Integer.toHexString(token));
             }
             mCurrentOperations.remove(token);
         }
     }
 
-    // synchronous waiter case
+    /** Block until we received an operation complete message (from the agent or cancellation). */
     public boolean waitUntilOperationComplete(int token) {
         if (MORE_DEBUG) {
             Slog.i(TAG, "Blocking until operation complete for "
@@ -1804,8 +1789,8 @@
                         // When the wait is notified we loop around and recheck the current state
                     } else {
                         if (MORE_DEBUG) {
-                            Slog.d(TAG, "Unblocked waiting for operation token=" +
-                                    Integer.toHexString(token));
+                            Slog.d(TAG, "Unblocked waiting for operation token="
+                                    + Integer.toHexString(token));
                         }
                         // No longer pending; we're done
                         finalState = op.state;
@@ -1826,6 +1811,7 @@
         return finalState == OP_ACKNOWLEDGED;
     }
 
+    /** Cancel the operation associated with {@code token}. */
     public void handleCancel(int token, boolean cancelAll) {
         // Notify any synchronous waiters
         Operation op = null;
@@ -1841,8 +1827,8 @@
             if (state == OP_ACKNOWLEDGED) {
                 // The operation finished cleanly, so we have nothing more to do.
                 if (DEBUG) {
-                    Slog.w(TAG, "Operation already got an ack." +
-                            "Should have been removed from mCurrentOperations.");
+                    Slog.w(TAG, "Operation already got an ack."
+                            + "Should have been removed from mCurrentOperations.");
                 }
                 op = null;
                 mCurrentOperations.delete(token);
@@ -1871,8 +1857,7 @@
         }
     }
 
-    // ----- Back up a set of applications via a worker thread -----
-
+    /** Returns {@code true} if a backup is currently running, else returns {@code false}. */
     public boolean isBackupOperationInProgress() {
         synchronized (mCurrentOpLock) {
             for (int i = 0; i < mCurrentOperations.size(); i++) {
@@ -1885,7 +1870,7 @@
         return false;
     }
 
-
+    /** Unbind the backup agent and kill the app if it's a non-system app. */
     public void tearDownAgentAndKill(ApplicationInfo app) {
         if (app == null) {
             // Null means the system package, so just quietly move on.  :)
@@ -1911,6 +1896,7 @@
         }
     }
 
+    /** For adb backup/restore. */
     public boolean deviceIsEncrypted() {
         try {
             return mStorageManager.getEncryptionState()
@@ -1961,8 +1947,8 @@
      */
     @GuardedBy("mQueueLock")
     private void dequeueFullBackupLocked(String packageName) {
-        final int N = mFullBackupQueue.size();
-        for (int i = N - 1; i >= 0; i--) {
+        final int numPackages = mFullBackupQueue.size();
+        for (int i = numPackages - 1; i >= 0; i--) {
             final FullBackupEntry e = mFullBackupQueue.get(i);
             if (packageName.equals(e.packageName)) {
                 mFullBackupQueue.remove(i);
@@ -2211,8 +2197,10 @@
         return true;
     }
 
-    // The job scheduler says our constraints don't hold any more,
-    // so tear down any ongoing backup task right away.
+    /**
+     * The job scheduler says our constraints don't hold anymore, so tear down any ongoing backup
+     * task right away.
+     */
     public void endFullBackup() {
         // offload the mRunningFullBackupTask.handleCancel() call to another thread,
         // as we might have to wait for mCancelLock
@@ -2236,7 +2224,7 @@
         new Thread(endFullBackupRunnable, "end-full-backup").start();
     }
 
-    // Used by both incremental and full restore
+    /** Used by both incremental and full restore to restore widget data. */
     public void restoreWidgetData(String packageName, byte[] widgetData) {
         // Apply the restored widget state and generate the ID update for the app
         // TODO: http://b/22388012
@@ -2250,6 +2238,7 @@
     // NEW UNIFIED RESTORE IMPLEMENTATION
     // *****************************
 
+    /** Schedule a backup pass for {@code packageName}. */
     public void dataChangedImpl(String packageName) {
         HashSet<String> targets = dataChangedTargets(packageName);
         dataChangedImpl(packageName, targets);
@@ -2319,6 +2308,7 @@
 
     // ----- IBackupManager binder interface -----
 
+    /** Sent from an app's backup agent to let the service know that there's new data to backup. */
     public void dataChanged(final String packageName) {
         final int callingUserHandle = UserHandle.getCallingUserId();
         if (callingUserHandle != UserHandle.USER_SYSTEM) {
@@ -2348,13 +2338,11 @@
         });
     }
 
-    // Run an initialize operation for the given transport
+    /** Run an initialize operation for the given transport. */
     public void initializeTransports(String[] transportNames, IBackupObserver observer) {
         mContext.enforceCallingPermission(android.Manifest.permission.BACKUP,
                 "initializeTransport");
-        if (MORE_DEBUG || true) {
-            Slog.v(TAG, "initializeTransport(): " + Arrays.asList(transportNames));
-        }
+        Slog.v(TAG, "initializeTransport(): " + Arrays.asList(transportNames));
 
         final long oldId = Binder.clearCallingIdentity();
         try {
@@ -2367,7 +2355,7 @@
         }
     }
 
-    // Clear the given package's backup data from the current transport
+    /** Clear the given package's backup data from the current transport. */
     public void clearBackupData(String transportName, String packageName) {
         if (DEBUG) Slog.v(TAG, "clearBackupData() of " + packageName + " on " + transportName);
         PackageInfo info;
@@ -2421,8 +2409,10 @@
         }
     }
 
-    // Run a backup pass immediately for any applications that have declared
-    // that they have pending updates.
+    /**
+     * Run a backup pass immediately for any applications that have declared that they have pending
+     * updates.
+     */
     public void backupNow() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "backupNow");
 
@@ -2453,17 +2443,18 @@
         }
     }
 
+    /** Returns {@code true} if the system user has gone through SUW. */
     public boolean deviceIsProvisioned() {
         final ContentResolver resolver = mContext.getContentResolver();
         return (Settings.Global.getInt(resolver, Settings.Global.DEVICE_PROVISIONED, 0) != 0);
     }
 
-    // Run a backup pass for the given packages, writing the resulting data stream
-    // to the supplied file descriptor.  This method is synchronous and does not return
-    // to the caller until the backup has been completed.
-    //
-    // This is the variant used by 'adb backup'; it requires on-screen confirmation
-    // by the user because it can be used to offload data over untrusted USB.
+    /**
+     * Used by 'adb backup' to run a backup pass for packages supplied via the command line, writing
+     * the resulting data stream to the supplied {@code fd}. This method is synchronous and does not
+     * return to the caller until the backup has been completed. It requires on-screen confirmation
+     * by the user.
+     */
     public void adbBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
             boolean includeShared, boolean doWidgets, boolean doAllApps, boolean includeSystem,
             boolean compress, boolean doKeyValue, String[] pkgList) {
@@ -2541,6 +2532,7 @@
         }
     }
 
+    /** Run a full backup pass for the given packages. Used by 'adb shell bmgr'. */
     public void fullTransportBackup(String[] pkgNames) {
         mContext.enforceCallingPermission(android.Manifest.permission.BACKUP,
                 "fullTransportBackup");
@@ -2600,6 +2592,10 @@
         }
     }
 
+    /**
+     * Used by 'adb restore' to run a restore pass, blocking until completion. Requires user
+     * confirmation.
+     */
     public void adbRestore(ParcelFileDescriptor fd) {
         mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "adbRestore");
 
@@ -2683,7 +2679,7 @@
 
     private void waitForCompletion(AdbParams params) {
         synchronized (params.latch) {
-            while (params.latch.get() == false) {
+            while (!params.latch.get()) {
                 try {
                     params.latch.wait();
                 } catch (InterruptedException e) { /* never interrupted */ }
@@ -2691,6 +2687,7 @@
         }
     }
 
+    /** Called when adb backup/restore has completed. */
     public void signalAdbBackupRestoreCompletion(AdbParams params) {
         synchronized (params.latch) {
             params.latch.set(true);
@@ -2698,8 +2695,10 @@
         }
     }
 
-    // Confirm that the previously-requested full backup/restore operation can proceed.  This
-    // is used to require a user-facing disclosure about the operation.
+    /**
+     * Confirm that the previously-requested full backup/restore operation can proceed. This is used
+     * to require a user-facing disclosure about the operation.
+     */
     public void acknowledgeAdbBackupOrRestore(int token, boolean allow,
             String curPassword, String encPpassword, IFullBackupRestoreObserver observer) {
         if (DEBUG) {
@@ -2750,55 +2749,7 @@
         }
     }
 
-    private static boolean backupSettingMigrated(int userId) {
-        File base = new File(Environment.getDataDirectory(), "backup");
-        File enableFile = new File(base, BACKUP_ENABLE_FILE);
-        return enableFile.exists();
-    }
-
-    private static boolean readBackupEnableState(int userId) {
-        File base = new File(Environment.getDataDirectory(), "backup");
-        File enableFile = new File(base, BACKUP_ENABLE_FILE);
-        if (enableFile.exists()) {
-            try (FileInputStream fin = new FileInputStream(enableFile)) {
-                int state = fin.read();
-                return state != 0;
-            } catch (IOException e) {
-                // can't read the file; fall through to assume disabled
-                Slog.e(TAG, "Cannot read enable state; assuming disabled");
-            }
-        } else {
-            if (DEBUG) {
-                Slog.i(TAG, "isBackupEnabled() => false due to absent settings file");
-            }
-        }
-        return false;
-    }
-
-    private static void writeBackupEnableState(boolean enable, int userId) {
-        File base = new File(Environment.getDataDirectory(), "backup");
-        File enableFile = new File(base, BACKUP_ENABLE_FILE);
-        File stage = new File(base, BACKUP_ENABLE_FILE + "-stage");
-        try (FileOutputStream fout = new FileOutputStream(stage)) {
-            fout.write(enable ? 1 : 0);
-            fout.close();
-            stage.renameTo(enableFile);
-            // will be synced immediately by the try-with-resources call to close()
-        } catch (IOException | RuntimeException e) {
-            // Whoops; looks like we're doomed.  Roll everything out, disabled,
-            // including the legacy state.
-            Slog.e(TAG, "Unable to record backup enable state; reverting to disabled: "
-                    + e.getMessage());
-
-            ContentResolver resolver = sInstance.getContext().getContentResolver();
-            Settings.Secure.putStringForUser(resolver,
-                    Settings.Secure.BACKUP_ENABLED, null, userId);
-            enableFile.delete();
-            stage.delete();
-        }
-    }
-
-    // Enable/disable backups
+    /** User-configurable enabling/disabling of backups. */
     public void setBackupEnabled(boolean enable) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                 "setBackupEnabled");
@@ -2865,7 +2816,7 @@
         }
     }
 
-    // Enable/disable automatic restore of app data at install time
+    /** Enable/disable automatic restore of app data at install time. */
     public void setAutoRestore(boolean doAutoRestore) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                 "setAutoRestore");
@@ -2884,7 +2835,7 @@
         }
     }
 
-    // Mark the backup service as having been provisioned
+    /** Mark the backup service as having been provisioned. */
     public void setBackupProvisioned(boolean available) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                 "setBackupProvisioned");
@@ -2893,14 +2844,14 @@
          */
     }
 
-    // Report whether the backup mechanism is currently enabled
+    /** Report whether the backup mechanism is currently enabled. */
     public boolean isBackupEnabled() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                 "isBackupEnabled");
         return mEnabled;    // no need to synchronize just to read it
     }
 
-    // Report the name of the currently active transport
+    /** Report the name of the currently active transport. */
     public String getCurrentTransport() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                 "getCurrentTransport");
@@ -2927,7 +2878,7 @@
         }
     }
 
-    // Report all known, available backup transports
+    /** Report all known, available backup transports by name. */
     public String[] listAllTransports() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                 "listAllTransports");
@@ -2935,12 +2886,14 @@
         return mTransportManager.getRegisteredTransportNames();
     }
 
+    /** Report all known, available backup transports by component. */
     public ComponentName[] listAllTransportComponents() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                 "listAllTransportComponents");
         return mTransportManager.getRegisteredTransportComponents();
     }
 
+    /** Report all system whitelisted transports. */
     public String[] getTransportWhitelist() {
         // No permission check, intentionally.
         Set<ComponentName> whitelistedComponents = mTransportManager.getTransportWhitelist();
@@ -3039,7 +2992,12 @@
         }
     }
 
-    /** Selects transport {@code transportName} and returns previous selected transport. */
+    /**
+     * Selects transport {@code transportName} and returns previously selected transport.
+     *
+     * @deprecated Use {@link #selectBackupTransportAsync(ComponentName,
+     * ISelectBackupTransportCallback)} instead.
+     */
     @Deprecated
     @Nullable
     public String selectBackupTransport(String transportName) {
@@ -3058,6 +3016,10 @@
         }
     }
 
+    /**
+     * Selects transport {@code transportComponent} asynchronously and notifies {@code listener}
+     * with the result upon completion.
+     */
     public void selectBackupTransportAsync(
             ComponentName transportComponent, ISelectBackupTransportCallback listener) {
         mContext.enforceCallingOrSelfPermission(
@@ -3126,9 +3088,11 @@
         }
     }
 
-    // Supply the configuration Intent for the given transport.  If the name is not one
-    // of the available transports, or if the transport does not supply any configuration
-    // UI, the method returns null.
+    /**
+     * Supply the configuration intent for the given transport. If the name is not one of the
+     * available transports, or if the transport does not supply any configuration UI, the method
+     * returns {@code null}.
+     */
     public Intent getConfigurationIntent(String transportName) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                 "getConfigurationIntent");
@@ -3169,7 +3133,7 @@
         }
     }
 
-    // Supply the manage-data intent for the given transport.
+    /** Supply the manage-data intent for the given transport. */
     public Intent getDataManagementIntent(String transportName) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                 "getDataManagementIntent");
@@ -3186,8 +3150,10 @@
         }
     }
 
-    // Supply the menu label for affordances that fire the manage-data intent
-    // for the given transport.
+    /**
+     * Supply the menu label for affordances that fire the manage-data intent for the given
+     * transport.
+     */
     public String getDataManagementLabel(String transportName) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                 "getDataManagementLabel");
@@ -3204,8 +3170,10 @@
         }
     }
 
-    // Callback: a requested backup agent has been instantiated.  This should only
-    // be called from the Activity Manager.
+    /**
+     * Callback: a requested backup agent has been instantiated. This should only be called from the
+     * {@link ActivityManager}.
+     */
     public void agentConnected(String packageName, IBinder agentBinder) {
         synchronized (mAgentConnectLock) {
             if (Binder.getCallingUid() == Process.SYSTEM_UID) {
@@ -3221,9 +3189,11 @@
         }
     }
 
-    // Callback: a backup agent has failed to come up, or has unexpectedly quit.
-    // If the agent failed to come up in the first place, the agentBinder argument
-    // will be null.  This should only be called from the Activity Manager.
+    /**
+     * Callback: a backup agent has failed to come up, or has unexpectedly quit. If the agent failed
+     * to come up in the first place, the agentBinder argument will be {@code null}. This should
+     * only be called from the {@link ActivityManager}.
+     */
     public void agentDisconnected(String packageName) {
         // TODO: handle backup being interrupted
         synchronized (mAgentConnectLock) {
@@ -3238,8 +3208,10 @@
         }
     }
 
-    // An application being installed will need a restore pass, then the Package Manager
-    // will need to be told when the restore is finished.
+    /**
+     * An application being installed will need a restore pass, then the {@link PackageManager} will
+     * need to be told when the restore is finished.
+     */
     public void restoreAtInstall(String packageName, int token) {
         if (Binder.getCallingUid() != Process.SYSTEM_UID) {
             Slog.w(TAG, "Non-system process uid=" + Binder.getCallingUid()
@@ -3283,8 +3255,8 @@
                 mWakelock.acquire();
 
                 OnTaskFinishedListener listener = caller -> {
-                        mTransportManager.disposeOfTransportClient(transportClient, caller);
-                        mWakelock.release();
+                    mTransportManager.disposeOfTransportClient(transportClient, caller);
+                    mWakelock.release();
                 };
 
                 if (MORE_DEBUG) {
@@ -3324,7 +3296,7 @@
         }
     }
 
-    // Hand off a restore session
+    /** Hand off a restore session. */
     public IRestoreSession beginRestoreSession(String packageName, String transport) {
         if (DEBUG) {
             Slog.v(TAG, "beginRestoreSession: pkg=" + packageName
@@ -3376,6 +3348,7 @@
         return mActiveRestoreSession;
     }
 
+    /** Clear the specified restore session. */
     public void clearRestoreSession(ActiveRestoreSession currentSession) {
         synchronized (this) {
             if (currentSession != mActiveRestoreSession) {
@@ -3388,8 +3361,10 @@
         }
     }
 
-    // Note that a currently-active backup agent has notified us that it has
-    // completed the given outstanding asynchronous backup/restore operation.
+    /**
+     * Note that a currently-active backup agent has notified us that it has completed the given
+     * outstanding asynchronous backup/restore operation.
+     */
     public void opComplete(int token, long result) {
         if (MORE_DEBUG) {
             Slog.v(TAG, "opComplete: " + Integer.toHexString(token) + " result=" + result);
@@ -3405,8 +3380,8 @@
                     mCurrentOperations.delete(token);
                 } else if (op.state == OP_ACKNOWLEDGED) {
                     if (DEBUG) {
-                        Slog.w(TAG, "Received duplicate ack for token=" +
-                                Integer.toHexString(token));
+                        Slog.w(TAG, "Received duplicate ack for token="
+                                + Integer.toHexString(token));
                     }
                     op = null;
                     mCurrentOperations.remove(token);
@@ -3427,6 +3402,7 @@
         }
     }
 
+    /** Checks if the package is eligible for backup. */
     public boolean isAppEligibleForBackup(String packageName) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.BACKUP, "isAppEligibleForBackup");
@@ -3448,6 +3424,7 @@
         }
     }
 
+    /** Returns the inputted packages that are eligible for backup. */
     public String[] filterAppsEligibleForBackup(String[] packages) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.BACKUP, "filterAppsEligibleForBackup");
@@ -3474,6 +3451,7 @@
         }
     }
 
+    /** Prints service state for 'dumpsys backup'. */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
 
@@ -3565,25 +3543,14 @@
                 pw.println("    " + s);
             }
 
-            if (DEBUG_BACKUP_TRACE) {
-                synchronized (mBackupTrace) {
-                    if (!mBackupTrace.isEmpty()) {
-                        pw.println("Most recent backup trace:");
-                        for (String s : mBackupTrace) {
-                            pw.println("   " + s);
-                        }
-                    }
-                }
-            }
-
             pw.print("Ancestral: ");
             pw.println(Long.toHexString(mAncestralToken));
             pw.print("Current:   ");
             pw.println(Long.toHexString(mCurrentToken));
 
-            int N = mBackupParticipants.size();
+            int numPackages = mBackupParticipants.size();
             pw.println("Participants:");
-            for (int i = 0; i < N; i++) {
+            for (int i = 0; i < numPackages; i++) {
                 int uid = mBackupParticipants.keyAt(i);
                 pw.print("  uid: ");
                 pw.println(uid);
@@ -3627,4 +3594,71 @@
         return mBackupManagerBinder;
     }
 
+    private static boolean backupSettingMigrated(int userId) {
+        File base = new File(Environment.getDataDirectory(), "backup");
+        File enableFile = new File(base, BACKUP_ENABLE_FILE);
+        return enableFile.exists();
+    }
+
+    private static boolean readBackupEnableState(int userId) {
+        File base = new File(Environment.getDataDirectory(), "backup");
+        File enableFile = new File(base, BACKUP_ENABLE_FILE);
+        if (enableFile.exists()) {
+            try (FileInputStream fin = new FileInputStream(enableFile)) {
+                int state = fin.read();
+                return state != 0;
+            } catch (IOException e) {
+                // can't read the file; fall through to assume disabled
+                Slog.e(TAG, "Cannot read enable state; assuming disabled");
+            }
+        } else {
+            if (DEBUG) {
+                Slog.i(TAG, "isBackupEnabled() => false due to absent settings file");
+            }
+        }
+        return false;
+    }
+
+    private static void writeBackupEnableState(boolean enable, int userId) {
+        File base = new File(Environment.getDataDirectory(), "backup");
+        File enableFile = new File(base, BACKUP_ENABLE_FILE);
+        File stage = new File(base, BACKUP_ENABLE_FILE + "-stage");
+        try (FileOutputStream fout = new FileOutputStream(stage)) {
+            fout.write(enable ? 1 : 0);
+            fout.close();
+            stage.renameTo(enableFile);
+            // will be synced immediately by the try-with-resources call to close()
+        } catch (IOException | RuntimeException e) {
+            // Whoops; looks like we're doomed.  Roll everything out, disabled,
+            // including the legacy state.
+            Slog.e(TAG, "Unable to record backup enable state; reverting to disabled: "
+                    + e.getMessage());
+
+            ContentResolver resolver = sInstance.getContext().getContentResolver();
+            Settings.Secure.putStringForUser(resolver,
+                    Settings.Secure.BACKUP_ENABLED, null, userId);
+            enableFile.delete();
+            stage.delete();
+        }
+    }
+
+    /** Implementation to receive lifecycle event callbacks for system services. */
+    public static final class Lifecycle extends SystemService {
+        public Lifecycle(Context context) {
+            super(context);
+            sInstance = new Trampoline(context);
+        }
+
+        @Override
+        public void onStart() {
+            publishBinderService(Context.BACKUP_SERVICE, sInstance);
+        }
+
+        @Override
+        public void onUnlockUser(int userId) {
+            if (userId == UserHandle.USER_SYSTEM) {
+                sInstance.unlockSystemUser();
+            }
+        }
+    }
 }
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index e108026..755095e 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -45,9 +45,9 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.EventLogTags;
 import com.android.server.backup.BackupAgentTimeoutParameters;
+import com.android.server.backup.BackupManagerService;
 import com.android.server.backup.BackupRestoreTask;
 import com.android.server.backup.FullBackupJob;
-import com.android.server.backup.BackupManagerService;
 import com.android.server.backup.TransportManager;
 import com.android.server.backup.internal.OnTaskFinishedListener;
 import com.android.server.backup.internal.Operation;
@@ -599,7 +599,6 @@
                 cleanUpPipes(enginePipes);
                 if (currentPackage.applicationInfo != null) {
                     Slog.i(TAG, "Unbinding agent in " + packageName);
-                    backupManagerService.addBackupTrace("unbinding " + packageName);
                     try {
                         backupManagerService.getActivityManager().unbindBackupAgent(
                                 currentPackage.applicationInfo);
@@ -709,7 +708,6 @@
             try {
                 backupManagerService.prepareOperationTimeout(
                         mCurrentOpToken, fullBackupAgentTimeoutMillis, this, OP_TYPE_BACKUP_WAIT);
-                backupManagerService.addBackupTrace("preflighting");
                 if (MORE_DEBUG) {
                     Slog.d(TAG, "Preflighting full payload of " + pkg.packageName);
                 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index aa96082..f7efa70 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8568,7 +8568,8 @@
                 r != null ? (r.isInterestingToUserLocked()
                         ? StatsLog.APP_CRASH_OCCURRED__FOREGROUND_STATE__FOREGROUND
                         : StatsLog.APP_CRASH_OCCURRED__FOREGROUND_STATE__BACKGROUND)
-                        : StatsLog.APP_CRASH_OCCURRED__FOREGROUND_STATE__UNKNOWN
+                        : StatsLog.APP_CRASH_OCCURRED__FOREGROUND_STATE__UNKNOWN,
+                (r != null) ? r.getProcessClassEnum() : 0
         );
 
         final int relaunchReason = r == null ? RELAUNCH_REASON_NONE
@@ -8751,7 +8752,7 @@
                 processName, r == null ? -1 : r.info.flags, tag, crashInfo.exceptionMessage);
 
         StatsLog.write(StatsLog.WTF_OCCURRED, callingUid, tag, processName,
-                callingPid);
+                callingPid, (r != null) ? r.getProcessClassEnum() : 0);
 
         addErrorToDropBox("wtf", r, processName, null, null, null, tag, null, null, crashInfo);
 
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 745c1263..faf8561 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -42,6 +42,7 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.server.ServerProtoEnums;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.DebugUtils;
@@ -1224,6 +1225,17 @@
         return mWindowProcessController.getInputDispatchingTimeout();
     }
 
+    public int getProcessClassEnum() {
+        if (pid == MY_PID) {
+            return ServerProtoEnums.SYSTEM_SERVER;
+        }
+        if (info == null) {
+            return ServerProtoEnums.ERROR_SOURCE_UNKNOWN;
+        }
+        return (info.flags & ApplicationInfo.FLAG_SYSTEM) != 0 ? ServerProtoEnums.SYSTEM_APP :
+            ServerProtoEnums.DATA_APP;
+    }
+
     void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
             String parentShortComponentName, WindowProcessController parentProcess,
             boolean aboveSystem, String annotation) {
@@ -1380,7 +1392,9 @@
                         : StatsLog.ANROCCURRED__IS_INSTANT_APP__UNAVAILABLE,
                 isInterestingToUserLocked()
                         ? StatsLog.ANROCCURRED__FOREGROUND_STATE__FOREGROUND
-                        : StatsLog.ANROCCURRED__FOREGROUND_STATE__BACKGROUND);
+                        : StatsLog.ANROCCURRED__FOREGROUND_STATE__BACKGROUND,
+                getProcessClassEnum(),
+                (this.info != null) ? this.info.packageName : "");
         final ProcessRecord parentPr = parentProcess != null
                 ? (ProcessRecord) parentProcess.mOwner : null;
         mService.addErrorToDropBox("anr", this, processName, activityShortComponentName,
diff --git a/services/core/java/com/android/server/connectivity/LingerMonitor.java b/services/core/java/com/android/server/connectivity/LingerMonitor.java
index 0e727c5..929dfc4 100644
--- a/services/core/java/com/android/server/connectivity/LingerMonitor.java
+++ b/services/core/java/com/android/server/connectivity/LingerMonitor.java
@@ -90,8 +90,8 @@
         mNotifier = notifier;
         mDailyLimit = dailyLimit;
         mRateLimitMillis = rateLimitMillis;
-        // Ensure that (now - mFirstNotificationMillis) >= rateLimitMillis at first
-        mFirstNotificationMillis = -rateLimitMillis;
+        // Ensure that (now - mLastNotificationMillis) >= rateLimitMillis at first
+        mLastNotificationMillis = -rateLimitMillis;
     }
 
     private static HashMap<String, Integer> makeTransportToNameMap() {
diff --git a/services/core/java/com/android/server/location/ContextHubClientBroker.java b/services/core/java/com/android/server/location/ContextHubClientBroker.java
index 002d4e1..6612d02 100644
--- a/services/core/java/com/android/server/location/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/ContextHubClientBroker.java
@@ -40,6 +40,8 @@
  * notification callbacks. This class implements the IContextHubClient object, and the implemented
  * APIs must be thread-safe.
  *
+ * TODO: Consider refactoring this class via inheritance
+ *
  * @hide
  */
 public class ContextHubClientBroker extends IContextHubClient.Stub
@@ -92,7 +94,7 @@
     /*
      * The PendingIntent registered with this client.
      */
-    private final PendingIntentRequest mPendingIntentRequest = new PendingIntentRequest();
+    private final PendingIntentRequest mPendingIntentRequest;
 
     /*
      * Helper class to manage registered PendingIntent requests from the client.
@@ -130,41 +132,31 @@
         public void clear() {
             mPendingIntent = null;
         }
-
-        public boolean register(PendingIntent pendingIntent, long nanoAppId) {
-            boolean success = false;
-            if (hasPendingIntent()) {
-                Log.e(TAG, "Failed to register PendingIntent: registered PendingIntent exists");
-            } else {
-                mNanoAppId = nanoAppId;
-                mPendingIntent = pendingIntent;
-                success = true;
-            }
-
-            return success;
-        }
-
-        public boolean unregister(PendingIntent pendingIntent) {
-            boolean success = false;
-            if (!hasPendingIntent() || !mPendingIntent.equals(pendingIntent)) {
-                Log.e(TAG, "Failed to unregister PendingIntent: PendingIntent is not registered");
-            } else {
-                mPendingIntent = null;
-                success = true;
-            }
-
-            return success;
-        }
     }
 
     /* package */ ContextHubClientBroker(
             Context context, IContexthub contextHubProxy, ContextHubClientManager clientManager,
-            ContextHubInfo contextHubInfo, short hostEndPointId) {
+            ContextHubInfo contextHubInfo, short hostEndPointId,
+            IContextHubClientCallback callback) {
         mContext = context;
         mContextHubProxy = contextHubProxy;
         mClientManager = clientManager;
         mAttachedContextHubInfo = contextHubInfo;
         mHostEndPointId = hostEndPointId;
+        mCallbackInterface = callback;
+        mPendingIntentRequest = new PendingIntentRequest();
+    }
+
+    /* package */ ContextHubClientBroker(
+            Context context, IContexthub contextHubProxy, ContextHubClientManager clientManager,
+            ContextHubInfo contextHubInfo, short hostEndPointId, PendingIntent pendingIntent,
+            long nanoAppId) {
+        mContext = context;
+        mContextHubProxy = contextHubProxy;
+        mClientManager = clientManager;
+        mAttachedContextHubInfo = contextHubInfo;
+        mHostEndPointId = hostEndPointId;
+        mPendingIntentRequest = new PendingIntentRequest(pendingIntent, nanoAppId);
     }
 
     /**
@@ -179,11 +171,7 @@
         ContextHubServiceUtil.checkPermissions(mContext);
 
         int result;
-        IContextHubClientCallback callback = null;
-        synchronized (this) {
-            callback = mCallbackInterface;
-        }
-        if (callback != null) {
+        if (isRegistered()) {
             ContextHubMsg messageToNanoApp =
                     ContextHubServiceUtil.createHidlContextHubMessage(mHostEndPointId, message);
 
@@ -204,64 +192,16 @@
     }
 
     /**
-     * @param pendingIntent the intent to register
-     * @param nanoAppId     the ID of the nanoapp to send events for
-     * @return true on success, false otherwise
-     */
-    @Override
-    public boolean registerIntent(PendingIntent pendingIntent, long nanoAppId) {
-        ContextHubServiceUtil.checkPermissions(mContext);
-        if (mClientManager.isPendingIntentRegistered(pendingIntent)) {
-            Log.e(TAG, "Failed to register PendingIntent: already registered");
-            return false;
-        }
-
-        boolean success = false;
-        synchronized (this) {
-            if (mCallbackInterface == null) {
-                Log.e(TAG, "Failed to register PendingIntent: client connection is closed");
-            } else {
-                success = mPendingIntentRequest.register(pendingIntent, nanoAppId);
-            }
-        }
-
-        return success;
-    }
-
-    /**
-     * @param pendingIntent the intent to unregister
-     * @return true on success, false otherwise
-     */
-    @Override
-    public boolean unregisterIntent(PendingIntent pendingIntent) {
-        ContextHubServiceUtil.checkPermissions(mContext);
-
-        boolean success = false;
-        synchronized (this) {
-            success = mPendingIntentRequest.unregister(pendingIntent);
-            if (mCallbackInterface == null) {
-                close();
-            }
-        }
-
-        return success;
-    }
-
-    /**
      * Closes the connection for this client with the service.
+     *
+     * If the client has a PendingIntent registered, this method also unregisters it.
      */
     @Override
     public void close() {
         synchronized (this) {
-            if (mCallbackInterface != null) {
-                mCallbackInterface.asBinder().unlinkToDeath(this, 0 /* flags */);
-                mCallbackInterface = null;
-            }
-            if (!mPendingIntentRequest.hasPendingIntent() && mRegistered) {
-                mClientManager.unregisterClient(mHostEndPointId);
-                mRegistered = false;
-            }
+            mPendingIntentRequest.clear();
         }
+        onClientExit();
     }
 
     /**
@@ -269,38 +209,7 @@
      */
     @Override
     public void binderDied() {
-        close();
-    }
-
-    /**
-     * Sets the callback interface for this client, only if the callback is currently unregistered.
-     *
-     * Also attaches a death recipient to a ContextHubClientBroker object. If unsuccessful, the
-     * connection is closed.
-     *
-     * @param callback the callback interface
-     * @return true if the callback was successfully set, false otherwise
-     *
-     * @throws IllegalStateException if the client has already been registered to a callback
-     */
-    /* package */
-    synchronized boolean setCallback(IContextHubClientCallback callback) {
-        boolean success = false;
-        if (mCallbackInterface != null) {
-            throw new IllegalStateException("Client is already registered with a callback");
-        } else {
-            mCallbackInterface = callback;
-            try {
-                mCallbackInterface.asBinder().linkToDeath(this, 0 /* flags */);
-                success = true;
-            } catch (RemoteException e) {
-                // The client process has died, so we close the connection.
-                Log.e(TAG, "Failed to attach death recipient to client");
-                close();
-            }
-        }
-
-        return success;
+        onClientExit();
     }
 
     /**
@@ -375,15 +284,30 @@
     }
 
     /**
-     * @param intent the PendingIntent to compare to
+     * @param intent    the PendingIntent to compare to
+     * @param nanoAppId the ID of the nanoapp of the PendingIntent to compare to
      * @return true if the given PendingIntent is currently registered, false otherwise
      */
-    /* package */ boolean hasPendingIntent(PendingIntent intent) {
+    /* package */ boolean hasPendingIntent(PendingIntent intent, long nanoAppId) {
         PendingIntent pendingIntent = null;
+        long intentNanoAppId;
         synchronized (this) {
             pendingIntent = mPendingIntentRequest.getPendingIntent();
+            intentNanoAppId = mPendingIntentRequest.getNanoAppId();
         }
-        return (pendingIntent != null) && pendingIntent.equals(intent);
+        return (pendingIntent != null) && pendingIntent.equals(intent)
+                && intentNanoAppId == nanoAppId;
+    }
+
+    /**
+     * Attaches the death recipient to the callback interface object, if any.
+     *
+     * @throws RemoteException if the client process already died
+     */
+    /* package */ void attachDeathRecipient() throws RemoteException {
+        if (mCallbackInterface != null) {
+            mCallbackInterface.asBinder().linkToDeath(this, 0 /* flags */);
+        }
     }
 
     /**
@@ -446,11 +370,29 @@
                 // The PendingIntent is no longer valid
                 Log.w(TAG, "PendingIntent has been canceled, unregistering from client"
                         + " (host endpoint ID " + mHostEndPointId + ")");
-                mPendingIntentRequest.clear();
-                if (mCallbackInterface == null) {
-                    close();
-                }
+                close();
             }
         }
     }
+
+    /**
+     * @return true if the client is still registered with the service, false otherwise
+     */
+    private synchronized boolean isRegistered() {
+        return mRegistered;
+    }
+
+    /**
+     * Invoked when a client exits either explicitly or by binder death.
+     */
+    private synchronized void onClientExit() {
+        if (mCallbackInterface != null) {
+            mCallbackInterface.asBinder().unlinkToDeath(this, 0 /* flags */);
+            mCallbackInterface = null;
+        }
+        if (!mPendingIntentRequest.hasPendingIntent() && mRegistered) {
+            mClientManager.unregisterClient(mHostEndPointId);
+            mRegistered = false;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/location/ContextHubClientManager.java b/services/core/java/com/android/server/location/ContextHubClientManager.java
index fe93a1a..7293440 100644
--- a/services/core/java/com/android/server/location/ContextHubClientManager.java
+++ b/services/core/java/com/android/server/location/ContextHubClientManager.java
@@ -24,6 +24,7 @@
 import android.hardware.location.IContextHubClient;
 import android.hardware.location.IContextHubClientCallback;
 import android.hardware.location.NanoAppMessage;
+import android.os.RemoteException;
 import android.util.Log;
 
 import java.util.concurrent.ConcurrentHashMap;
@@ -68,7 +69,7 @@
     /*
      * The next host endpoint ID to start iterating for the next available host endpoint ID.
      */
-    private int mNextHostEndpointId = 0;
+    private int mNextHostEndPointId = 0;
 
     /* package */ ContextHubClientManager(
             Context context, IContexthub contextHubProxy) {
@@ -88,9 +89,22 @@
      */
     /* package */ IContextHubClient registerClient(
             IContextHubClientCallback clientCallback, ContextHubInfo contextHubInfo) {
-        ContextHubClientBroker broker = createNewClientBroker(contextHubInfo);
-        if (!broker.setCallback(clientCallback)) {
-            return null; // Client process has died, so we return null
+        ContextHubClientBroker broker;
+        synchronized (this) {
+            short hostEndPointId = getHostEndPointId();
+            broker = new ContextHubClientBroker(
+                    mContext, mContextHubProxy, this /* clientManager */, contextHubInfo,
+                    hostEndPointId, clientCallback);
+            mHostEndPointIdToClientMap.put(hostEndPointId, broker);
+        }
+
+        try {
+            broker.attachDeathRecipient();
+        } catch (RemoteException e) {
+            // The client process has died, so we close the connection and return null
+            Log.e(TAG, "Failed to attach death recipient to client");
+            broker.close();
+            return null;
         }
 
         Log.d(TAG, "Registered client with host endpoint ID " + broker.getHostEndPointId());
@@ -98,32 +112,36 @@
     }
 
     /**
-     * Binds a existing and registered client with a new callback interface, provided a previously
-     * registered PendingIntent.
+     * Registers a new client with the service.
      *
-     * @param pendingIntent  a previously registered PendingIntent for a registered client
-     * @param clientCallback the callback interface of the client to bind to
-     * @param contextHubId   the ID of the hub this client is attached to
+     * @param pendingIntent  the callback interface of the client to register
+     * @param contextHubInfo the object describing the hub this client is attached to
+     * @param nanoAppId      the ID of the nanoapp to receive Intent events for
      *
      * @return the client interface
      *
-     * @throws IllegalArgumentException if no matching client is found
-     * @throws IllegalStateException    if the client has already been registered to a callback
+     * @throws IllegalArgumentException the PendingIntent was already registered for a different
+     *                                  ContextHubClient
+     * @throws IllegalStateException    if there were too many registered clients at the service
      */
-    /* package */ IContextHubClient bindClient(
-            PendingIntent pendingIntent, IContextHubClientCallback clientCallback,
-            int contextHubId) {
-        ContextHubClientBroker broker = getClientBroker(pendingIntent, contextHubId);
-        if (broker == null) {
-            throw new IllegalArgumentException("Could not find client of Context Hub (ID = "
-                    + contextHubId + ") with PendingIntent");
+    /* package */ IContextHubClient registerClient(
+            ContextHubInfo contextHubInfo, PendingIntent pendingIntent, long nanoAppId) {
+        ContextHubClientBroker broker;
+        String registerString = "Regenerated";
+        synchronized (this) {
+            broker = getClientBroker(contextHubInfo.getId(), pendingIntent, nanoAppId);
+
+            if (broker == null) {
+                short hostEndPointId = getHostEndPointId();
+                broker = new ContextHubClientBroker(
+                        mContext, mContextHubProxy, this /* clientManager */, contextHubInfo,
+                        hostEndPointId, pendingIntent, nanoAppId);
+                mHostEndPointIdToClientMap.put(hostEndPointId, broker);
+                registerString = "Registered";
+            }
         }
 
-        if (!broker.setCallback(clientCallback)) {
-            return null; // Client process has died, so we return null
-        }
-
-        Log.d(TAG, "Re-registered client with host endpoint ID " + broker.getHostEndPointId());
+        Log.d(TAG, registerString + " client with host endpoint ID " + broker.getHostEndPointId());
         return IContextHubClient.Stub.asInterface(broker);
     }
 
@@ -203,50 +221,28 @@
     }
 
     /**
-     * @param pendingIntent the PendingIntent to check
-     * @return true if the given PendingIntent is registered by a client, false otherwise
-     */
-    /* package */ boolean isPendingIntentRegistered(PendingIntent pendingIntent) {
-        for (ContextHubClientBroker broker : mHostEndPointIdToClientMap.values()) {
-            if (broker.hasPendingIntent(pendingIntent)) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Creates a new ContextHubClientBroker object for a client and registers it with the client
-     * manager.
+     * Returns an available host endpoint ID.
      *
-     * @param contextHubInfo the object describing the hub this client is attached to
-     *
-     * @return the ContextHubClientBroker object
+     * @returns an available host endpoint ID
      *
      * @throws IllegalStateException if max number of clients have already registered
      */
-    private synchronized ContextHubClientBroker createNewClientBroker(
-            ContextHubInfo contextHubInfo) {
+    private short getHostEndPointId() {
         if (mHostEndPointIdToClientMap.size() == MAX_CLIENT_ID + 1) {
             throw new IllegalStateException("Could not register client - max limit exceeded");
         }
 
-        ContextHubClientBroker broker = null;
-        int id = mNextHostEndpointId;
+        int id = mNextHostEndPointId;
         for (int i = 0; i <= MAX_CLIENT_ID; i++) {
             if (!mHostEndPointIdToClientMap.containsKey((short) id)) {
-                broker = new ContextHubClientBroker(
-                        mContext, mContextHubProxy, this, contextHubInfo, (short) id);
-                mHostEndPointIdToClientMap.put((short) id, broker);
-                mNextHostEndpointId = (id == MAX_CLIENT_ID) ? 0 : id + 1;
+                mNextHostEndPointId = (id == MAX_CLIENT_ID) ? 0 : id + 1;
                 break;
             }
 
             id = (id == MAX_CLIENT_ID) ? 0 : id + 1;
         }
 
-        return broker;
+        return (short) id;
     }
 
     /**
@@ -280,9 +276,10 @@
      * @param contextHubId  the ID of the Context Hub the client is attached to
      * @return the matching ContextHubClientBroker, null if not found
      */
-    private ContextHubClientBroker getClientBroker(PendingIntent pendingIntent, int contextHubId) {
+    private ContextHubClientBroker getClientBroker(
+            int contextHubId, PendingIntent pendingIntent, long nanoAppId) {
         for (ContextHubClientBroker broker : mHostEndPointIdToClientMap.values()) {
-            if (broker.hasPendingIntent(pendingIntent)
+            if (broker.hasPendingIntent(pendingIntent, nanoAppId)
                     && broker.getAttachedContextHubId() == contextHubId) {
                 return broker;
             }
diff --git a/services/core/java/com/android/server/location/ContextHubService.java b/services/core/java/com/android/server/location/ContextHubService.java
index 215e67c..52f1c6b 100644
--- a/services/core/java/com/android/server/location/ContextHubService.java
+++ b/services/core/java/com/android/server/location/ContextHubService.java
@@ -632,34 +632,26 @@
     }
 
     /**
-     * Recreates and binds a IContextHubClientCallback interface to an existing and registered
-     * client at the service for the specified Context Hub, provided a previously registered
-     * PendingIntent.
+     * Creates and registers a PendingIntent client at the service for the specified Context Hub.
      *
-     * @param pendingIntent  the PendingIntent previously registered for the client
-     * @param clientCallback the client interface to register with the service
-     * @param contextHubId   the ID of the hub this client is attached to
-     * @return the generated client interface, null if registration was unsuccessful
+     * @param contextHubId  the ID of the hub this client is attached to
+     * @param pendingIntent the PendingIntent associated with this client
+     * @param nanoAppId     the ID of the nanoapp PendingIntent events will be sent for
+     * @return the generated client interface
      *
-     * @throws IllegalArgumentException if contextHubId is not a valid ID
-     * @throws NullPointerException if clientCallback or pendingIntent is null
+     * @throws IllegalArgumentException if hubInfo does not represent a valid hub
+     * @throws IllegalStateException    if there were too many registered clients at the service
      */
     @Override
-    public IContextHubClient bindClient(
-            PendingIntent pendingIntent, IContextHubClientCallback clientCallback,
-            int contextHubId) throws RemoteException {
+    public IContextHubClient createPendingIntentClient(
+            int contextHubId, PendingIntent pendingIntent, long nanoAppId) throws RemoteException {
         checkPermissions();
         if (!isValidContextHubId(contextHubId)) {
             throw new IllegalArgumentException("Invalid context hub ID " + contextHubId);
         }
-        if (pendingIntent == null) {
-            throw new NullPointerException("Cannot create client with null pending intent");
-        }
-        if (clientCallback == null) {
-            throw new NullPointerException("Cannot create client with null callback");
-        }
 
-        return mClientManager.bindClient(pendingIntent, clientCallback, contextHubId);
+        ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId);
+        return mClientManager.registerClient(contextHubInfo, pendingIntent, nanoAppId);
     }
 
     /**
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index a6ea6b2..fccff57 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -396,8 +396,9 @@
             }
             synchronized (mLock) {
                 if (isValidMediaProjection(asBinder())) {
-                    throw new IllegalStateException(
-                            "Cannot start already started MediaProjection");
+                    Slog.w(TAG, "UID " + Binder.getCallingUid()
+                            + " attempted to start already started MediaProjection");
+                    return;
                 }
                 mCallback = callback;
                 registerCallback(mCallback);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8a0c416..e4e8010 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -12937,6 +12937,25 @@
         }
     }
 
+    @Override
+    public boolean canSuspendPackageForUser(String packageName, int userId) {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS,
+                "canSuspendPackageForUser");
+        final int callingUid = Binder.getCallingUid();
+        if (UserHandle.getUserId(callingUid) != userId) {
+            throw new SecurityException("Calling uid " + callingUid
+                    + " cannot query canSuspendPackageForUser for user " + userId);
+        }
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            synchronized (mPackages) {
+                return canSuspendPackageForUserLocked(packageName, userId);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
     @GuardedBy("mPackages")
     private boolean canSuspendPackageForUserLocked(String packageName, int userId) {
         if (isPackageDeviceAdmin(packageName, userId)) {
@@ -13000,7 +13019,7 @@
         }
 
         if (PLATFORM_PACKAGE_NAME.equals(packageName)) {
-            Slog.w(TAG, "Cannot suspend package: " + packageName);
+            Slog.w(TAG, "Cannot suspend the platform package: " + packageName);
             return false;
         }
 
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 580e4f4..3a74ab5 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -42,7 +42,6 @@
 import com.android.server.pm.PackageDexOptimizer;
 import com.android.server.pm.PackageManagerService;
 import com.android.server.pm.PackageManagerServiceUtils;
-import com.android.server.pm.PackageManagerServiceCompilerMapping;
 
 import java.io.File;
 import java.io.IOException;
diff --git a/services/core/java/com/android/server/pm/dex/DexoptOptions.java b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
index a7a7686..de3c9f2 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptOptions.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
@@ -18,8 +18,6 @@
 
 import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
 
-import android.annotation.Nullable;
-
 /**
  * Options used for dexopt invocations.
  */
diff --git a/services/core/java/com/android/server/pm/dex/DexoptUtils.java b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
index d2600b5..9a12a2f 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptUtils.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
@@ -24,8 +24,6 @@
 import com.android.server.pm.PackageDexOptimizer;
 
 import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 
 public final class DexoptUtils {
diff --git a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
index 86f7380..519a20d 100644
--- a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
+++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
@@ -35,13 +35,10 @@
 import java.io.Reader;
 import java.io.StringWriter;
 import java.io.Writer;
-import java.util.Arrays;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 97af045..282746a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2258,8 +2258,7 @@
     }
 
     /**
-     * @return whether the navigation bar can be hidden, e.g. the device has a
-     *         navigation bar and touch exploration is not enabled
+     * @return whether the navigation bar can be hidden, e.g. the device has a navigation bar
      */
     private boolean canHideNavigationBar() {
         return mDefaultDisplayPolicy.hasNavigationBar();
@@ -4305,8 +4304,7 @@
         final boolean screenDecor = (pfl & PRIVATE_FLAG_IS_SCREEN_DECOR) != 0;
 
         if (layoutInScreenAndInsetDecor && !screenDecor) {
-            if (canHideNavigationBar() &&
-                    (sysUiVis & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0) {
+            if ((sysUiVis & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0) {
                 outFrame.set(displayFrames.mUnrestricted);
             } else {
                 outFrame.set(displayFrames.mRestricted);
@@ -4965,8 +4963,7 @@
                         of.set(displayFrames.mOverscan);
                         df.set(displayFrames.mOverscan);
                         pf.set(displayFrames.mOverscan);
-                    } else if (canHideNavigationBar()
-                            && (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
+                    } else if ((sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
                             && (type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW
                             || type == TYPE_VOLUME_OVERLAY)) {
                         // Asking for layout as if the nav bar is hidden, lets the application
@@ -5062,8 +5059,7 @@
                     of.set(displayFrames.mOverscan);
                     df.set(displayFrames.mOverscan);
                     pf.set(displayFrames.mOverscan);
-                } else if (canHideNavigationBar()
-                        && (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
+                } else if ((sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
                         && (type == TYPE_STATUS_BAR
                             || type == TYPE_TOAST
                             || type == TYPE_DOCK_DIVIDER
diff --git a/services/core/java/com/android/server/timezone/RulesManagerService.java b/services/core/java/com/android/server/timezone/RulesManagerService.java
index 23c4a337..c4d2851 100644
--- a/services/core/java/com/android/server/timezone/RulesManagerService.java
+++ b/services/core/java/com/android/server/timezone/RulesManagerService.java
@@ -47,6 +47,7 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
 import libcore.icu.ICU;
+import libcore.timezone.TzDataSetVersion;
 import libcore.util.TimeZoneFinder;
 import libcore.util.ZoneInfoDB;
 
@@ -66,8 +67,8 @@
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     static final DistroFormatVersion DISTRO_FORMAT_VERSION_SUPPORTED =
             new DistroFormatVersion(
-                    DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
-                    DistroVersion.CURRENT_FORMAT_MINOR_VERSION);
+                    TzDataSetVersion.currentFormatMajorVersion(),
+                    TzDataSetVersion.currentFormatMinorVersion());
 
     public static class Lifecycle extends SystemService {
         public Lifecycle(Context context) {
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 37a65cd..082f521 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -121,6 +121,7 @@
 import android.app.ProfilerInfo;
 import android.app.ResultInfo;
 import android.app.WaitResult;
+import android.app.WindowConfiguration;
 import android.app.WindowConfiguration.ActivityType;
 import android.app.WindowConfiguration.WindowingMode;
 import android.app.servertransaction.ActivityLifecycleItem;
@@ -2405,7 +2406,7 @@
 
     <T extends ActivityStack> T getLaunchStack(@Nullable ActivityRecord r,
             @Nullable ActivityOptions options, @Nullable TaskRecord candidateTask, boolean onTop) {
-        return getLaunchStack(r, options, candidateTask, onTop, INVALID_DISPLAY);
+        return getLaunchStack(r, options, candidateTask, onTop, null /* launchParams */);
     }
 
     /**
@@ -2414,12 +2415,13 @@
      * @param r The activity we are trying to launch. Can be null.
      * @param options The activity options used to the launch. Can be null.
      * @param candidateTask The possible task the activity might be launched in. Can be null.
+     * @params launchParams The resolved launch params to use.
      *
      * @return The stack to use for the launch or INVALID_STACK_ID.
      */
     <T extends ActivityStack> T getLaunchStack(@Nullable ActivityRecord r,
             @Nullable ActivityOptions options, @Nullable TaskRecord candidateTask, boolean onTop,
-            int candidateDisplayId) {
+            @Nullable LaunchParamsController.LaunchParams launchParams) {
         int taskId = INVALID_TASK_ID;
         int displayId = INVALID_DISPLAY;
         //Rect bounds = null;
@@ -2427,9 +2429,6 @@
         // We give preference to the launch preference in activity options.
         if (options != null) {
             taskId = options.getLaunchTaskId();
-            displayId = options.getLaunchDisplayId();
-            // TODO: Need to work this into the equation...
-            //bounds = options.getLaunchBounds();
         }
 
         // First preference for stack goes to the task Id set in the activity options. Use the stack
@@ -2446,16 +2445,16 @@
         }
 
         final int activityType = resolveActivityType(r, options, candidateTask);
-        T stack = null;
+        T stack;
 
-        // Next preference for stack goes to the display Id set in the activity options or the
-        // candidate display.
-        if (displayId == INVALID_DISPLAY) {
-            displayId = candidateDisplayId;
+        // Next preference for stack goes to the display Id set the candidate display.
+        if (launchParams != null) {
+            displayId = launchParams.mPreferredDisplayId;
         }
         if (displayId != INVALID_DISPLAY && canLaunchOnDisplay(r, displayId)) {
             if (r != null) {
-                stack = (T) getValidLaunchStackOnDisplay(displayId, r, candidateTask, options);
+                stack = (T) getValidLaunchStackOnDisplay(displayId, r, candidateTask, options,
+                        launchParams);
                 if (stack != null) {
                     return stack;
                 }
@@ -2482,8 +2481,12 @@
         if (stack != null) {
             display = stack.getDisplay();
             if (display != null && canLaunchOnDisplay(r, display.mDisplayId)) {
-                final int windowingMode =
-                        display.resolveWindowingMode(r, options, candidateTask, activityType);
+                int windowingMode = launchParams != null ? launchParams.mWindowingMode
+                        : WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+                if (windowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
+                    windowingMode = display.resolveWindowingMode(r, options, candidateTask,
+                            activityType);
+                }
                 if (stack.isCompatible(windowingMode, activityType)) {
                     return stack;
                 }
@@ -2523,8 +2526,9 @@
      * @param candidateTask The possible task the activity might be put in.
      * @return Existing stack if there is a valid one, new dynamic stack if it is valid or null.
      */
-    ActivityStack getValidLaunchStackOnDisplay(int displayId, @NonNull ActivityRecord r,
-            @Nullable TaskRecord candidateTask, @Nullable ActivityOptions options) {
+    private ActivityStack getValidLaunchStackOnDisplay(int displayId, @NonNull ActivityRecord r,
+            @Nullable TaskRecord candidateTask, @Nullable ActivityOptions options,
+            @Nullable LaunchParamsController.LaunchParams launchParams) {
         final ActivityDisplay activityDisplay = getActivityDisplayOrCreateLocked(displayId);
         if (activityDisplay == null) {
             throw new IllegalArgumentException(
@@ -2552,8 +2556,18 @@
 
         // If there is no valid stack on the external display - check if new dynamic stack will do.
         if (displayId != DEFAULT_DISPLAY) {
+            final int windowingMode;
+            if (launchParams != null) {
+                // When launch params is not null, we always defer to its windowing mode. Sometimes
+                // it could be unspecified, which indicates it should inherit windowing mode from
+                // display.
+                windowingMode = launchParams.mWindowingMode;
+            } else {
+                windowingMode = options != null ? options.getLaunchWindowingMode()
+                        : r.getWindowingMode();
+            }
             return activityDisplay.createStack(
-                    options != null ? options.getLaunchWindowingMode() : r.getWindowingMode(),
+                    windowingMode,
                     options != null ? options.getLaunchActivityType() : r.getActivityType(),
                     true /*onTop*/);
         }
@@ -2563,8 +2577,10 @@
     }
 
     ActivityStack getValidLaunchStackOnDisplay(int displayId, @NonNull ActivityRecord r,
-            @Nullable ActivityOptions options) {
-        return getValidLaunchStackOnDisplay(displayId, r, null /* candidateTask */, options);
+            @Nullable ActivityOptions options,
+            @Nullable LaunchParamsController.LaunchParams launchParams) {
+        return getValidLaunchStackOnDisplay(displayId, r, null /* candidateTask */, options,
+                launchParams);
     }
 
     // TODO: Can probably be consolidated into getLaunchStack()...
@@ -2644,7 +2660,7 @@
                 continue;
             }
             final ActivityStack stack = getValidLaunchStackOnDisplay(display.mDisplayId, r,
-                    null /* options */);
+                    null /* options */, null /* launchParams */);
             if (stack != null) {
                 return stack;
             }
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 83db8de..d4c1bca 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2145,7 +2145,7 @@
             if (mTargetStack == null && targetDisplayId != sourceStack.mDisplayId) {
                 // Can't use target display, lets find a stack on the source display.
                 mTargetStack = mSupervisor.getValidLaunchStackOnDisplay(
-                        sourceStack.mDisplayId, mStartActivity, mOptions);
+                        sourceStack.mDisplayId, mStartActivity, mOptions, mLaunchParams);
             }
             if (mTargetStack == null) {
                 // There are no suitable stacks on the target and source display(s). Look on all
@@ -2368,7 +2368,8 @@
 
         if (mPreferredDisplayId != DEFAULT_DISPLAY) {
             // Try to put the activity in a stack on a secondary display.
-            stack = mSupervisor.getValidLaunchStackOnDisplay(mPreferredDisplayId, r, aOptions);
+            stack = mSupervisor.getValidLaunchStackOnDisplay(mPreferredDisplayId, r, aOptions,
+                    mLaunchParams);
             if (stack == null) {
                 // If source display is not suitable - look for topmost valid stack in the system.
                 if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
@@ -2432,9 +2433,12 @@
                  || mPreferredDisplayId != DEFAULT_DISPLAY) {
             // We don't pass in the default display id into the get launch stack call so it can do a
             // full resolution.
-            final int candidateDisplay =
+            mLaunchParams.mPreferredDisplayId =
                     mPreferredDisplayId != DEFAULT_DISPLAY ? mPreferredDisplayId : INVALID_DISPLAY;
-            return mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP, candidateDisplay);
+            final ActivityStack stack = mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP,
+                    mLaunchParams);
+            mLaunchParams.mPreferredDisplayId = mPreferredDisplayId;
+            return stack;
         }
         // Otherwise handle adjacent launch.
 
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 66063c40..31c0c7f 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -305,7 +305,7 @@
                 .setName(surface + " - animation-leash")
                 .setSize(width, height);
         final SurfaceControl leash = builder.build();
-        t.setWindowCrop(surface, width, height);
+        t.setWindowCrop(leash, width, height);
         if (!hidden) {
             t.show(leash);
         }
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 073601d..912cb7f 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -809,7 +809,7 @@
             updateBoundsForDisplayChanges();
         }
 
-        if (mAnimationBackgroundSurface != null) {
+        if (mAnimationBackgroundSurface == null) {
             mAnimationBackgroundSurface = makeChildSurface(null).setColorLayer(true)
                     .setName("animation background stackId=" + mStackId)
                     .build();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d7d3e71..c47b22f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -4402,22 +4402,24 @@
 
                         lastFocus = displayContent.mLastFocus;
                         newFocus = displayContent.mCurrentFocus;
-                        if (lastFocus == newFocus) {
-                            // Report focus to ViewRootImpl when top focused display changes.
-                            // Or, nothing to do for no window focus change.
-                            if (topFocusedDisplayChanged && newFocus != null) {
-                                if (DEBUG_FOCUS_LIGHT) {
-                                    Slog.d(TAG, "Reporting focus: " + newFocus
-                                            + " due to top focused display change.");
-                                }
-                                // See {@link IWindow#windowFocusChanged} to know why set
-                                // reportToClient as false.
-                                newFocus.reportFocusChangedSerialized(true, mInTouchMode,
-                                        false /* reportToClient */);
-                                notifyFocusChanged();
+                    }
+                    if (lastFocus == newFocus) {
+                        // Report focus to ViewRootImpl when top focused display changes.
+                        // Or, nothing to do for no window focus change.
+                        if (topFocusedDisplayChanged && newFocus != null) {
+                            if (DEBUG_FOCUS_LIGHT) {
+                                Slog.d(TAG, "Reporting focus: " + newFocus
+                                        + " due to top focused display change.");
                             }
-                            return;
+                            // See {@link IWindow#windowFocusChanged} to know why set
+                            // reportToClient as false.
+                            newFocus.reportFocusChangedSerialized(true, mInTouchMode,
+                                    false /* reportToClient */);
+                            notifyFocusChanged();
                         }
+                        return;
+                    }
+                    synchronized (mGlobalLock) {
                         displayContent.mLastFocus = newFocus;
                         if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Focus moving from " + lastFocus +
                                 " to " + newFocus + " displayId=" + displayContent.getDisplayId());
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 3bb8ce3..f8ac41f 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -804,8 +804,11 @@
 
         boolean disableSystemTextClassifier = SystemProperties.getBoolean(
                 "config.disable_systemtextclassifier", false);
+
+        //TODO(b/111276913): temporarily disabled until the manager is properly implemented to
+        // ignore events when disabled and buffer when enabled
         boolean disableIntelligence = SystemProperties.getBoolean(
-                "config.disable_intelligence", false);
+                "config.disable_intelligence", true);
         boolean disableNetworkTime = SystemProperties.getBoolean("config.disable_networktime",
                 false);
         boolean disableCameraService = SystemProperties.getBoolean("config.disable_cameraservice",
@@ -1140,6 +1143,8 @@
                 traceBeginAndSlog("StartIntelligenceService");
                 mSystemServiceManager.startService(INTELLIGENCE_MANAGER_SERVICE_CLASS);
                 traceEnd();
+            } else {
+                Slog.d(TAG, "IntelligenceService disabled");
             }
 
             // NOTE: ClipboardService indirectly depends on IntelligenceService
diff --git a/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java
index c4cb593..25d1cc7 100644
--- a/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java
+++ b/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java
@@ -11,7 +11,7 @@
  * 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
+ * limitations under the License.
  */
 
 package com.android.server.backup;
@@ -75,6 +75,7 @@
 import java.io.File;
 import java.util.List;
 
+/** Tests for the system service {@link BackupManagerService} that performs backup/restore. */
 @RunWith(RobolectricTestRunner.class)
 @Config(shadows = {ShadowAppBackupUtils.class})
 @Presubmit
@@ -94,6 +95,10 @@
     private String mTransportName;
     private ShadowPackageManager mShadowPackageManager;
 
+    /**
+     * Initialize state that {@link BackupManagerService} operations interact with. This includes
+     * setting up the transport, starting the backup thread, and creating backup data directories.
+     */
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -119,12 +124,20 @@
         mDataDir = new File(cacheDir, "data");
     }
 
+    /**
+     * Clean up and reset state that was created for testing {@link BackupManagerService}
+     * operations.
+     */
     @After
     public void tearDown() throws Exception {
         mBackupThread.quit();
         ShadowAppBackupUtils.reset();
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#MORE_DEBUG} is set to {@code false}. This is
+     * specifically to prevent overloading the logs in production.
+     */
     @Test
     public void testMoreDebug_isFalse() throws Exception {
         boolean moreDebug = BackupManagerService.MORE_DEBUG;
@@ -132,8 +145,10 @@
         assertThat(moreDebug).isFalse();
     }
 
-    /* Tests for destination string */
-
+    /**
+     * Test verifying that {@link BackupManagerService#getDestinationString(String)} returns the
+     * current destination string of inputted transport if the transport is registered.
+     */
     @Test
     public void testDestinationString() throws Exception {
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -146,6 +161,10 @@
         assertThat(destination).isEqualTo("destinationString");
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#getDestinationString(String)} returns {@code
+     * null} if the inputted transport is not registered.
+     */
     @Test
     public void testDestinationString_whenTransportNotRegistered() throws Exception {
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -158,6 +177,10 @@
         assertThat(destination).isNull();
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#getDestinationString(String)} throws a {@link
+     * SecurityException} if the caller does not have backup permission.
+     */
     @Test
     public void testDestinationString_withoutPermission() throws Exception {
         mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
@@ -170,8 +193,10 @@
                 () -> backupManagerService.getDestinationString(mTransportName));
     }
 
-    /* Tests for app eligibility */
-
+    /**
+     * Test verifying that {@link BackupManagerService#isAppEligibleForBackup(String)} returns
+     * {@code false} when the given app is not eligible for backup.
+     */
     @Test
     public void testIsAppEligibleForBackup_whenAppNotEligible() throws Exception {
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -183,6 +208,10 @@
         assertThat(result).isFalse();
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#isAppEligibleForBackup(String)} returns
+     * {@code true} when the given app is eligible for backup.
+     */
     @Test
     public void testIsAppEligibleForBackup_whenAppEligible() throws Exception {
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -197,6 +226,10 @@
                 .disposeOfTransportClient(eq(transportMock.transportClient), any());
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#isAppEligibleForBackup(String)} throws a
+     * {@link SecurityException} if the caller does not have backup permission.
+     */
     @Test
     public void testIsAppEligibleForBackup_withoutPermission() throws Exception {
         mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
@@ -209,6 +242,11 @@
                 () -> backupManagerService.isAppEligibleForBackup(PACKAGE_1));
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#filterAppsEligibleForBackup(String[])}
+     * returns an {@code array} of only apps that are eligible for backup from an {@array} of
+     * inputted apps.
+     */
     @Test
     public void testFilterAppsEligibleForBackup() throws Exception {
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -225,6 +263,10 @@
                 .disposeOfTransportClient(eq(transportMock.transportClient), any());
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#filterAppsEligibleForBackup(String[])}
+     * returns an empty {@code array} if no inputted apps are eligible for backup.
+     */
     @Test
     public void testFilterAppsEligibleForBackup_whenNoneIsEligible() throws Exception {
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -237,6 +279,10 @@
         assertThat(filtered).isEmpty();
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#filterAppsEligibleForBackup(String[])} throws
+     * a {@link SecurityException} if the caller does not have backup permission.
+     */
     @Test
     public void testFilterAppsEligibleForBackup_withoutPermission() throws Exception {
         mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
@@ -270,6 +316,11 @@
                 .thenReturn(mOldTransport.transportName);
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#selectBackupTransport(String)} successfully
+     * switches the current transport to the inputted transport, returns the name of the old
+     * transport, and disposes of the transport client after the operation.
+     */
     @Test
     public void testSelectBackupTransport() throws Exception {
         setUpForSelectTransport();
@@ -285,6 +336,10 @@
                 .disposeOfTransportClient(eq(mNewTransportMock.transportClient), any());
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#selectBackupTransport(String)} throws a
+     * {@link SecurityException} if the caller does not have backup permission.
+     */
     @Test
     public void testSelectBackupTransport_withoutPermission() throws Exception {
         setUpForSelectTransport();
@@ -296,6 +351,11 @@
                 () -> backupManagerService.selectBackupTransport(mNewTransport.transportName));
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#selectBackupTransportAsync(ComponentName,
+     * ISelectBackupTransportCallback)} successfully switches the current transport to the inputted
+     * transport and disposes of the transport client after the operation.
+     */
     @Test
     public void testSelectBackupTransportAsync() throws Exception {
         setUpForSelectTransport();
@@ -314,6 +374,12 @@
                 .disposeOfTransportClient(eq(mNewTransportMock.transportClient), any());
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#selectBackupTransportAsync(ComponentName,
+     * ISelectBackupTransportCallback)} does not switch the current transport to the inputted
+     * transport and notifies the inputted callback of failure when it fails to register the
+     * transport.
+     */
     @Test
     public void testSelectBackupTransportAsync_whenRegistrationFails() throws Exception {
         setUpForSelectTransport();
@@ -330,6 +396,11 @@
         verify(callback).onFailure(anyInt());
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#selectBackupTransportAsync(ComponentName,
+     * ISelectBackupTransportCallback)} does not switch the current transport to the inputted
+     * transport and notifies the inputted callback of failure when the transport gets unregistered.
+     */
     @Test
     public void testSelectBackupTransportAsync_whenTransportGetsUnregistered() throws Exception {
         setUpTransports(mTransportManager, mTransport.unregistered());
@@ -347,6 +418,11 @@
         verify(callback).onFailure(anyInt());
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#selectBackupTransportAsync(ComponentName,
+     * ISelectBackupTransportCallback)} throws a {@link SecurityException} if the caller does not
+     * have backup permission.
+     */
     @Test
     public void testSelectBackupTransportAsync_withoutPermission() throws Exception {
         setUpForSelectTransport();
@@ -366,8 +442,10 @@
                 mContext.getContentResolver(), Settings.Secure.BACKUP_TRANSPORT);
     }
 
-    /* Tests for transport attributes */
-
+    /**
+     * Test verifying that {@link BackupManagerService#getCurrentTransportComponent()} returns the
+     * {@link ComponentName} of the currently selected transport.
+     */
     @Test
     public void testGetCurrentTransportComponent() throws Exception {
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -380,6 +458,10 @@
         assertThat(transportComponent).isEqualTo(mTransport.getTransportComponent());
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#getCurrentTransportComponent()} returns
+     * {@code null} if there is no currently selected transport.
+     */
     @Test
     public void testGetCurrentTransportComponent_whenNoTransportSelected() throws Exception {
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -391,6 +473,10 @@
         assertThat(transportComponent).isNull();
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#getCurrentTransportComponent()} returns
+     * {@code null} if the currently selected transport is not registered.
+     */
     @Test
     public void testGetCurrentTransportComponent_whenTransportNotRegistered() throws Exception {
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -403,6 +489,10 @@
         assertThat(transportComponent).isNull();
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#getCurrentTransportComponent()} throws a
+     * {@link SecurityException} if the caller does not have backup permission.
+     */
     @Test
     public void testGetCurrentTransportComponent_withoutPermission() throws Exception {
         mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
@@ -428,9 +518,14 @@
         mTransportUid = mContext.getPackageManager().getPackageUid(transportPackage, 0);
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#updateTransportAttributes(int, ComponentName,
+     * String, Intent, String, Intent, String)} succeeds if the uid of the transport is same as the
+     * uid of the caller.
+     */
     @Test
     public void
-            testUpdateTransportAttributes_whenTransportUidEqualsToCallingUid_callsThroughToTransportManager()
+            testUpdateTransportAttributes_whenTransportUidEqualsCallingUid_callsTransportManager()
                     throws Exception {
         setUpForUpdateTransportAttributes();
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -457,6 +552,11 @@
                         eq("dataManagementLabel"));
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#updateTransportAttributes(int, ComponentName,
+     * String, Intent, String, Intent, String)} throws a {@link SecurityException} if the uid of the
+     * transport is not equal to the uid of the caller.
+     */
     @Test
     public void testUpdateTransportAttributes_whenTransportUidNotEqualToCallingUid_throwsException()
             throws Exception {
@@ -477,6 +577,11 @@
                                 "dataManagementLabel"));
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#updateTransportAttributes(int, ComponentName,
+     * String, Intent, String, Intent, String)} throws a {@link RuntimeException} if given a {@code
+     * null} transport component.
+     */
     @Test
     public void testUpdateTransportAttributes_whenTransportComponentNull_throwsException()
             throws Exception {
@@ -497,6 +602,11 @@
                                 "dataManagementLabel"));
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#updateTransportAttributes(int, ComponentName,
+     * String, Intent, String, Intent, String)} throws a {@link RuntimeException} if given a {@code
+     * null} transport name.
+     */
     @Test
     public void testUpdateTransportAttributes_whenNameNull_throwsException() throws Exception {
         setUpForUpdateTransportAttributes();
@@ -516,6 +626,11 @@
                                 "dataManagementLabel"));
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#updateTransportAttributes(int, ComponentName,
+     * String, Intent, String, Intent, String)} throws a {@link RuntimeException} if given a {@code
+     * null} destination string.
+     */
     @Test
     public void testUpdateTransportAttributes_whenCurrentDestinationStringNull_throwsException()
             throws Exception {
@@ -536,9 +651,14 @@
                                 "dataManagementLabel"));
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#updateTransportAttributes(int, ComponentName,
+     * String, Intent, String, Intent, String)} throws a {@link RuntimeException} if given either a
+     * {@code null} data management label or {@code null} data management intent, but not both.
+     */
     @Test
     public void
-            testUpdateTransportAttributes_whenDataManagementArgumentsNullityDontMatch_throwsException()
+            testUpdateTransportAttributes_whenDataManagementArgsNullityDontMatch_throwsException()
                     throws Exception {
         setUpForUpdateTransportAttributes();
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -569,6 +689,10 @@
                                 null));
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#updateTransportAttributes(int, ComponentName,
+     * String, Intent, String, Intent, String)} succeeds if the caller has backup permission.
+     */
     @Test
     public void testUpdateTransportAttributes_whenPermissionGranted_callsThroughToTransportManager()
             throws Exception {
@@ -597,6 +721,11 @@
                         eq("dataManagementLabel"));
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#updateTransportAttributes(int, ComponentName,
+     * String, Intent, String, Intent, String)} throws a {@link SecurityException} if the caller
+     * does not have backup permission.
+     */
     @Test
     public void testUpdateTransportAttributes_whenPermissionDenied_throwsSecurityException()
             throws Exception {
@@ -634,6 +763,10 @@
         ShadowKeyValueBackupTask.reset();
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#requestBackup(String[], IBackupObserver,
+     * int)} throws a {@link SecurityException} if the caller does not have backup permission.
+     */
     @Test
     public void testRequestBackup_whenPermissionDenied() throws Exception {
         mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
@@ -644,6 +777,10 @@
                 () -> backupManagerService.requestBackup(new String[] {PACKAGE_1}, mObserver, 0));
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#requestBackup(String[], IBackupObserver,
+     * int)} throws an {@link IllegalArgumentException} if passed {@null} for packages.
+     */
     @Test
     public void testRequestBackup_whenPackagesNull() throws Exception {
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -655,6 +792,11 @@
         verify(mObserver).backupFinished(BackupManager.ERROR_TRANSPORT_ABORTED);
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#requestBackup(String[], IBackupObserver,
+     * int)} throws an {@link IllegalArgumentException} if passed an empty {@code array} for
+     * packages.
+     */
     @Test
     public void testRequestBackup_whenPackagesEmpty() throws Exception {
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -666,6 +808,10 @@
         verify(mObserver).backupFinished(BackupManager.ERROR_TRANSPORT_ABORTED);
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#requestBackup(String[], IBackupObserver,
+     * int)} returns {@link BackupManager#ERROR_BACKUP_NOT_ALLOWED} if backup is disabled.
+     */
     @Test
     public void testRequestBackup_whenBackupDisabled() throws Exception {
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -678,6 +824,11 @@
         verify(mObserver).backupFinished(BackupManager.ERROR_BACKUP_NOT_ALLOWED);
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#requestBackup(String[], IBackupObserver,
+     * int)} returns {@link BackupManager#ERROR_BACKUP_NOT_ALLOWED} if the system user hasn't gone
+     * through SUW.
+     */
     @Test
     public void testRequestBackup_whenNotProvisioned() throws Exception {
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -690,6 +841,11 @@
         verify(mObserver).backupFinished(BackupManager.ERROR_BACKUP_NOT_ALLOWED);
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#requestBackup(String[], IBackupObserver,
+     * int)} returns {@link BackupManager#ERROR_TRANSPORT_ABORTED} if the current transport is not
+     * registered.
+     */
     @Test
     public void testRequestBackup_whenTransportNotRegistered() throws Exception {
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -704,6 +860,11 @@
         verify(mObserver).backupFinished(BackupManager.ERROR_TRANSPORT_ABORTED);
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#requestBackup(String[], IBackupObserver,
+     * int)} returns {@link BackupManager#SUCCESS} and notifies the observer of {@link
+     * BackupManager#ERROR_BACKUP_NOT_ALLOWED} if the specified app is not eligible for backup.
+     */
     @Test
     public void testRequestBackup_whenAppNotEligibleForBackup() throws Exception {
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -722,6 +883,11 @@
         tearDownForRequestBackup();
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#requestBackup(String[], IBackupObserver,
+     * int)} returns {@link BackupManager#SUCCESS} and updates bookkeeping if backup for a key value
+     * package succeeds.
+     */
     @Test
     @Config(shadows = ShadowKeyValueBackupTask.class)
     public void testRequestBackup_whenPackageIsKeyValue() throws Exception {
@@ -739,6 +905,11 @@
         tearDownForRequestBackup();
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#requestBackup(String[], IBackupObserver,
+     * int)} returns {@link BackupManager#SUCCESS} and updates bookkeeping if backup for a full
+     * backup package succeeds.
+     */
     @Test
     @Config(shadows = ShadowKeyValueBackupTask.class)
     public void testRequestBackup_whenPackageIsFullBackup() throws Exception {
@@ -757,6 +928,10 @@
         tearDownForRequestBackup();
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#backupNow()} clears the calling identity
+     * for scheduling a job and then restores the original calling identity after the operation.
+     */
     @Test
     @Config(shadows = {ShadowBinder.class, ShadowKeyValueBackupJob.class})
     public void testBackupNow_clearsCallingIdentityForJobScheduler() {
@@ -771,6 +946,10 @@
         assertThat(ShadowBinder.getCallingUid()).isEqualTo(1);
     }
 
+    /**
+     * Test verifying that {@link BackupManagerService#backupNow()} restores the original calling
+     * identity if an exception is thrown during execution.
+     */
     @Test
     @Config(shadows = {ShadowBinder.class, ShadowKeyValueBackupJobException.class})
     public void testBackupNow_whenExceptionThrown_restoresCallingIdentity() {
@@ -792,8 +971,11 @@
         return backupManagerService;
     }
 
-    /* Miscellaneous tests */
-
+    /**
+     * Test verifying that {@link BackupManagerService#BackupManagerService(Context, Trampoline,
+     * HandlerThread, File, File, TransportManager)} posts a transport registration task to the
+     * backup handler thread.
+     */
     @Test
     public void testConstructor_postRegisterTransports() {
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -804,6 +986,11 @@
         verify(mTransportManager).registerTransports();
     }
 
+    /**
+     * Test verifying that the {@link BackupManagerService#BackupManagerService(Context, Trampoline,
+     * HandlerThread, File, File, TransportManager)} does not directly register transports in its
+     * own thread.
+     */
     @Test
     public void testConstructor_doesNotRegisterTransportsSynchronously() {
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -842,6 +1029,10 @@
      */
     @Implements(KeyValueBackupJob.class)
     public static class ShadowKeyValueBackupJobException extends ShadowKeyValueBackupJob {
+        /**
+         * Implementation of {@link ShadowKeyValueBackupJob#schedule(Context, long,
+         * BackupManagerConstants)} that throws an {@link IllegalArgumentException}.
+         */
         public static void schedule(Context ctx, long delay, BackupManagerConstants constants) {
             ShadowKeyValueBackupJob.schedule(ctx, delay, constants);
             throw new IllegalArgumentException();
diff --git a/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java
index c252609..1b106dd 100644
--- a/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java
@@ -40,6 +40,7 @@
 import javax.annotation.Nullable;
 
 import libcore.io.IoUtils;
+import libcore.timezone.TzDataSetVersion;
 
 import static com.android.server.timezone.RulesManagerService.REQUIRED_QUERY_PERMISSION;
 import static com.android.server.timezone.RulesManagerService.REQUIRED_UPDATER_PERMISSION;
@@ -128,15 +129,15 @@
         configureDeviceSystemRulesVersion("2016a");
 
         DistroVersion stagedDistroVersion = new DistroVersion(
-                DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
-                DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+                TzDataSetVersion.currentFormatMajorVersion(),
+                TzDataSetVersion.currentFormatMinorVersion() - 1,
                 "2016c",
                 3);
         configureStagedInstall(stagedDistroVersion);
 
         DistroVersion installedDistroVersion = new DistroVersion(
-                DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
-                DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+                TzDataSetVersion.currentFormatMajorVersion(),
+                TzDataSetVersion.currentFormatMinorVersion() - 1,
                 "2016b",
                 4);
         configureInstalledDistroVersion(installedDistroVersion);
@@ -162,8 +163,8 @@
         configureNoStagedOperation();
 
         DistroVersion installedDistroVersion = new DistroVersion(
-                DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
-                DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+                TzDataSetVersion.currentFormatMajorVersion(),
+                TzDataSetVersion.currentFormatMinorVersion() - 1,
                 "2016b",
                 4);
         configureInstalledDistroVersion(installedDistroVersion);
@@ -187,8 +188,8 @@
         configureStagedUninstall();
 
         DistroVersion installedDistroVersion = new DistroVersion(
-                DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
-                DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+                TzDataSetVersion.currentFormatMajorVersion(),
+                TzDataSetVersion.currentFormatMinorVersion() - 1,
                 "2016b",
                 4);
         configureInstalledDistroVersion(installedDistroVersion);
@@ -231,8 +232,8 @@
         configureDeviceCannotReadStagedDistroOperation();
 
         DistroVersion installedDistroVersion = new DistroVersion(
-                DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
-                DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+                TzDataSetVersion.currentFormatMajorVersion(),
+                TzDataSetVersion.currentFormatMinorVersion() - 1,
                 "2016b",
                 4);
         configureInstalledDistroVersion(installedDistroVersion);
@@ -275,8 +276,8 @@
         configureDeviceSystemRulesVersion(systemRulesVersion);
 
         DistroVersion installedDistroVersion = new DistroVersion(
-                DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
-                DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+                TzDataSetVersion.currentFormatMajorVersion(),
+                TzDataSetVersion.currentFormatMinorVersion() - 1,
                 installedRulesVersion,
                 revision);
         configureInstalledDistroVersion(installedDistroVersion);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index f7d7ad6..88479ec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -346,7 +346,7 @@
             doReturn(stack).when(mService.mStackSupervisor)
                     .getLaunchStack(any(), any(), any(), anyBoolean());
             doReturn(stack).when(mService.mStackSupervisor)
-                    .getLaunchStack(any(), any(), any(), anyBoolean(), anyInt());
+                    .getLaunchStack(any(), any(), any(), anyBoolean(), any());
         }
 
         // Set up mock package manager internal and make sure no unmocked methods are called
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index b744770..c7ecdfa 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -2292,6 +2292,45 @@
     public static final String KEY_SUPPORT_EMERGENCY_DIALER_SHORTCUT_BOOL =
             "support_emergency_dialer_shortcut_bool";
 
+    /**
+     * Call forwarding uses USSD command without SS command.
+     * When {@code true}, the call forwarding query/set by ussd command and UI only display Call
+     * Forwarding when unanswered.
+     * When {@code false}, don't use USSD to query/set call forwarding.
+     * @hide
+     */
+    public static final String KEY_USE_CALL_FORWARDING_USSD_BOOL = "use_call_forwarding_ussd_bool";
+
+    /**
+     * This flag specifies whether to support for the caller id set command by ussd.
+     * When {@code true}, device shall sync caller id ussd result to ss command.
+     * When {@code false}, caller id don't support ussd command.
+     * @hide
+     */
+    public static final String KEY_USE_CALLER_ID_USSD_BOOL = "use_caller_id_ussd_bool";
+
+    /**
+     * Specifies the service class for call waiting service.
+     * Default value is
+     * {@link com.android.internal.telephony.CommandsInterface#SERVICE_CLASS_VOICE}.
+     * <p>
+     * See 27.007 +CCFC or +CLCK.
+     * The value set as below:
+     * {@link com.android.internal.telephony.CommandsInterface#SERVICE_CLASS_NONE}
+     * {@link com.android.internal.telephony.CommandsInterface#SERVICE_CLASS_VOICE}
+     * {@link com.android.internal.telephony.CommandsInterface#SERVICE_CLASS_DATA}
+     * {@link com.android.internal.telephony.CommandsInterface#SERVICE_CLASS_FAX}
+     * {@link com.android.internal.telephony.CommandsInterface#SERVICE_CLASS_SMS}
+     * {@link com.android.internal.telephony.CommandsInterface#SERVICE_CLASS_DATA_SYNC}
+     * {@link com.android.internal.telephony.CommandsInterface#SERVICE_CLASS_DATA_ASYNC}
+     * {@link com.android.internal.telephony.CommandsInterface#SERVICE_CLASS_PACKET}
+     * {@link com.android.internal.telephony.CommandsInterface#SERVICE_CLASS_PAD}
+     * {@link com.android.internal.telephony.CommandsInterface#SERVICE_CLASS_MAX}
+     * @hide
+     */
+    public static final String KEY_CALL_WAITING_SERVICE_CLASS_INT =
+            "call_waiting_service_class_int";
+
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
 
@@ -2648,6 +2687,9 @@
         sDefaults.putBoolean(KEY_CALL_WAITING_OVER_UT_WARNING_BOOL, false);
         sDefaults.putBoolean(KEY_SUPPORT_CLIR_NETWORK_DEFAULT_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_EMERGENCY_DIALER_SHORTCUT_BOOL, true);
+        sDefaults.putBoolean(KEY_USE_CALL_FORWARDING_USSD_BOOL, false);
+        sDefaults.putBoolean(KEY_USE_CALLER_ID_USSD_BOOL, false);
+        sDefaults.putInt(KEY_CALL_WAITING_SERVICE_CLASS_INT, 1 /* SERVICE_CLASS_VOICE */);
     }
 
     /**
diff --git a/telephony/java/android/telephony/CellSignalStrength.java b/telephony/java/android/telephony/CellSignalStrength.java
index 6090d5c..fd21d42 100644
--- a/telephony/java/android/telephony/CellSignalStrength.java
+++ b/telephony/java/android/telephony/CellSignalStrength.java
@@ -21,15 +21,20 @@
  */
 public abstract class CellSignalStrength {
 
-    public static final int SIGNAL_STRENGTH_NONE_OR_UNKNOWN = 0;
+    public static final int SIGNAL_STRENGTH_NONE_OR_UNKNOWN =
+            TelephonyProtoEnums.SIGNAL_STRENGTH_NONE_OR_UNKNOWN; // 0
 
-    public static final int SIGNAL_STRENGTH_POOR = 1;
+    public static final int SIGNAL_STRENGTH_POOR =
+            TelephonyProtoEnums.SIGNAL_STRENGTH_POOR; // 1
 
-    public static final int SIGNAL_STRENGTH_MODERATE = 2;
+    public static final int SIGNAL_STRENGTH_MODERATE =
+            TelephonyProtoEnums.SIGNAL_STRENGTH_MODERATE; // 2
 
-    public static final int SIGNAL_STRENGTH_GOOD = 3;
+    public static final int SIGNAL_STRENGTH_GOOD =
+            TelephonyProtoEnums.SIGNAL_STRENGTH_GOOD; // 3
 
-    public static final int SIGNAL_STRENGTH_GREAT = 4;
+    public static final int SIGNAL_STRENGTH_GREAT =
+            TelephonyProtoEnums.SIGNAL_STRENGTH_GREAT; // 4
 
     /** @hide */
     public static final int NUM_SIGNAL_STRENGTH_BINS = 5;
diff --git a/telephony/java/android/telephony/ICellInfoCallback.aidl b/telephony/java/android/telephony/ICellInfoCallback.aidl
new file mode 100644
index 0000000..7fb62682
--- /dev/null
+++ b/telephony/java/android/telephony/ICellInfoCallback.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+import android.telephony.CellInfo;
+
+import java.util.List;
+
+/**
+ * Callback to provide asynchronous CellInfo.
+ * @hide
+ */
+oneway interface ICellInfoCallback
+{
+    void onCellInfo(in List<CellInfo> state);
+}
diff --git a/telephony/java/android/telephony/NetworkRegistrationState.java b/telephony/java/android/telephony/NetworkRegistrationState.java
index 75e8eda..aee744f 100644
--- a/telephony/java/android/telephony/NetworkRegistrationState.java
+++ b/telephony/java/android/telephony/NetworkRegistrationState.java
@@ -70,6 +70,43 @@
     /** Registered on roaming network */
     public static final int REG_STATE_ROAMING               = 5;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "NR_STATUS_",
+            value = {NR_STATUS_NONE, NR_STATUS_RESTRICTED, NR_STATUS_NOT_RESTRICTED,
+                    NR_STATUS_CONNECTED})
+    public @interface NRStatus {}
+
+    /**
+     * The device isn't camped on an LTE cell or the LTE cell doesn't support E-UTRA-NR
+     * Dual Connectivity(EN-DC).
+     * @hide
+     */
+    public static final int NR_STATUS_NONE = -1;
+
+    /**
+     * The device is camped on an LTE cell that supports E-UTRA-NR Dual Connectivity(EN-DC) but
+     * either the use of dual connectivity with NR(DCNR) is restricted or NR is not supported by
+     * the selected PLMN.
+     * @hide
+     */
+    public static final int NR_STATUS_RESTRICTED = 1;
+
+    /**
+     * The device is camped on an LTE cell that supports E-UTRA-NR Dual Connectivity(EN-DC) and both
+     * the use of dual connectivity with NR(DCNR) is not restricted and NR is supported by the
+     * selected PLMN.
+     * @hide
+     */
+    public static final int NR_STATUS_NOT_RESTRICTED = 2;
+
+    /**
+     * The device is camped on an LTE cell that supports E-UTRA-NR Dual Connectivity(EN-DC) and
+     * also connected to at least one 5G cell as a secondary serving cell.
+     * @hide
+     */
+    public static final int NR_STATUS_CONNECTED = 3;
+
     /**
      * Supported service type
      * @hide
@@ -104,6 +141,9 @@
 
     private int mAccessNetworkTechnology;
 
+    @NRStatus
+    private int mNrStatus;
+
     private final int mRejectCause;
 
     private final boolean mEmergencyOnly;
@@ -154,6 +194,7 @@
         mAvailableServices = availableServices;
         mCellIdentity = cellIdentity;
         mEmergencyOnly = emergencyOnly;
+        mNrStatus = NR_STATUS_NONE;
     }
 
     /**
@@ -200,6 +241,7 @@
                 VoiceSpecificRegistrationStates.class.getClassLoader());
         mDataSpecificStates = source.readParcelable(
                 DataSpecificRegistrationStates.class.getClassLoader());
+        mNrStatus = source.readInt();
     }
 
     /**
@@ -213,6 +255,19 @@
     public @Domain int getDomain() { return mDomain; }
 
     /**
+     * @return the 5G NR connection status.
+     * @hide
+     */
+    public @NRStatus int getNrStatus() {
+        return mNrStatus;
+    }
+
+    /** @hide */
+    public void setNrStatus(@NRStatus int nrStatus) {
+        mNrStatus = nrStatus;
+    }
+
+    /**
      * @return The registration state.
      */
     public @RegState int getRegState() {
@@ -315,6 +370,19 @@
         return "Unknown reg state " + regState;
     }
 
+    private static String nrStatusToString(@NRStatus int nrStatus) {
+        switch (nrStatus) {
+            case NR_STATUS_RESTRICTED:
+                return "RESTRICTED";
+            case NR_STATUS_NOT_RESTRICTED:
+                return "NOT_RESTRICTED";
+            case NR_STATUS_CONNECTED:
+                return "CONNECTED";
+            default:
+                return "NONE";
+        }
+    }
+
     @Override
     public String toString() {
         return new StringBuilder("NetworkRegistrationState{")
@@ -330,6 +398,7 @@
                 .append(" cellIdentity=").append(mCellIdentity)
                 .append(" voiceSpecificStates=").append(mVoiceSpecificStates)
                 .append(" dataSpecificStates=").append(mDataSpecificStates)
+                .append(" nrStatus=").append(nrStatusToString(mNrStatus))
                 .append("}").toString();
     }
 
@@ -337,7 +406,7 @@
     public int hashCode() {
         return Objects.hash(mDomain, mTransportType, mRegState, mRoamingType,
                 mAccessNetworkTechnology, mRejectCause, mEmergencyOnly, mAvailableServices,
-                mCellIdentity, mVoiceSpecificStates, mDataSpecificStates);
+                mCellIdentity, mVoiceSpecificStates, mDataSpecificStates, mNrStatus);
     }
 
     @Override
@@ -359,7 +428,8 @@
                 && Arrays.equals(mAvailableServices, other.mAvailableServices)
                 && Objects.equals(mCellIdentity, other.mCellIdentity)
                 && Objects.equals(mVoiceSpecificStates, other.mVoiceSpecificStates)
-                && Objects.equals(mDataSpecificStates, other.mDataSpecificStates);
+                && Objects.equals(mDataSpecificStates, other.mDataSpecificStates)
+                && mNrStatus == other.mNrStatus;
     }
 
     @Override
@@ -375,6 +445,7 @@
         dest.writeParcelable(mCellIdentity, 0);
         dest.writeParcelable(mVoiceSpecificStates, 0);
         dest.writeParcelable(mDataSpecificStates, 0);
+        dest.writeInt(mNrStatus);
     }
 
     public static final Parcelable.Creator<NetworkRegistrationState> CREATOR =
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 0937b10..ab80e252 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -83,7 +83,45 @@
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef({DUPLEX_MODE_UNKNOWN, DUPLEX_MODE_FDD, DUPLEX_MODE_TDD})
+    @IntDef(prefix = "FREQUENCY_RANGE_",
+            value = {FREQUENCY_RANGE_UNKNOWN, FREQUENCY_RANGE_LOW, FREQUENCY_RANGE_MID,
+                    FREQUENCY_RANGE_HIGH, FREQUENCY_RANGE_MMWAVE})
+    public @interface FrequencyRange {}
+
+    /**
+     * Indicates frequency range is unknown.
+     * @hide
+     */
+    public static final int FREQUENCY_RANGE_UNKNOWN = -1;
+
+    /**
+     * Indicates the frequency range is below 1GHz.
+     * @hide
+     */
+    public static final int FREQUENCY_RANGE_LOW = 1;
+
+    /**
+     * Indicates the frequency range is between 1GHz to 3GHz.
+     * @hide
+     */
+    public static final int FREQUENCY_RANGE_MID = 2;
+
+    /**
+     * Indicates the frequency range is between 3GHz and 6GHz.
+     * @hide
+     */
+    public static final int FREQUENCY_RANGE_HIGH = 3;
+
+    /**
+     * Indicates the frequency range is above 6GHz (millimeter wave frequency).
+     * @hide
+     */
+    public static final int FREQUENCY_RANGE_MMWAVE = 4;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "DUPLEX_MODE_",
+            value = {DUPLEX_MODE_UNKNOWN, DUPLEX_MODE_FDD, DUPLEX_MODE_TDD})
     public @interface DuplexMode {}
 
     /**
@@ -283,6 +321,8 @@
     @UnsupportedAppUsage
     private boolean mIsUsingCarrierAggregation;
 
+    @FrequencyRange
+    private int mNrFrequencyRange;
     private int mChannelNumber;
     private int[] mCellBandwidths = new int[0];
 
@@ -375,6 +415,7 @@
         mLteEarfcnRsrpBoost = s.mLteEarfcnRsrpBoost;
         mNetworkRegistrationStates = s.mNetworkRegistrationStates == null ? null :
                 new ArrayList<>(s.mNetworkRegistrationStates);
+        mNrFrequencyRange = s.mNrFrequencyRange;
     }
 
     /**
@@ -406,6 +447,7 @@
         in.readList(mNetworkRegistrationStates, NetworkRegistrationState.class.getClassLoader());
         mChannelNumber = in.readInt();
         mCellBandwidths = in.createIntArray();
+        mNrFrequencyRange = in.readInt();
     }
 
     public void writeToParcel(Parcel out, int flags) {
@@ -433,6 +475,7 @@
         out.writeList(mNetworkRegistrationStates);
         out.writeInt(mChannelNumber);
         out.writeIntArray(mCellBandwidths);
+        out.writeInt(mNrFrequencyRange);
     }
 
     public int describeContents() {
@@ -792,7 +835,8 @@
                 mIsEmergencyOnly,
                 mIsUsingCarrierAggregation,
                 mLteEarfcnRsrpBoost,
-                mNetworkRegistrationStates);
+                mNetworkRegistrationStates,
+                mNrFrequencyRange);
     }
 
     @Override
@@ -823,7 +867,8 @@
                 && mIsUsingCarrierAggregation == s.mIsUsingCarrierAggregation)
                 && (mNetworkRegistrationStates == null ? s.mNetworkRegistrationStates == null :
                         s.mNetworkRegistrationStates != null &&
-                        mNetworkRegistrationStates.containsAll(s.mNetworkRegistrationStates));
+                        mNetworkRegistrationStates.containsAll(s.mNetworkRegistrationStates))
+                && mNrFrequencyRange == s.mNrFrequencyRange;
     }
 
     /**
@@ -958,6 +1003,7 @@
             .append(", mIsUsingCarrierAggregation=").append(mIsUsingCarrierAggregation)
             .append(", mLteEarfcnRsrpBoost=").append(mLteEarfcnRsrpBoost)
             .append(", mNetworkRegistrationStates=").append(mNetworkRegistrationStates)
+            .append(", mNrFrequencyRange=").append(mNrFrequencyRange)
             .append("}").toString();
     }
 
@@ -987,6 +1033,7 @@
         mIsUsingCarrierAggregation = false;
         mLteEarfcnRsrpBoost = 0;
         mNetworkRegistrationStates = new ArrayList<>();
+        mNrFrequencyRange = FREQUENCY_RANGE_UNKNOWN;
     }
 
     public void setStateOutOfService() {
@@ -1225,6 +1272,7 @@
         m.putInt("LteEarfcnRsrpBoost", mLteEarfcnRsrpBoost);
         m.putInt("ChannelNumber", mChannelNumber);
         m.putIntArray("CellBandwidths", mCellBandwidths);
+        m.putInt("mNrFrequencyRange", mNrFrequencyRange);
     }
 
     /** @hide */
@@ -1288,6 +1336,22 @@
         mIsUsingCarrierAggregation = ca;
     }
 
+    /**
+     * @return the frequency range of 5G NR.
+     * @hide
+     */
+    public @FrequencyRange int getNrFrequencyRange() {
+        return mNrFrequencyRange;
+    }
+
+    /**
+     * @param nrFrequencyRange the frequency range of 5G NR.
+     * @hide
+     */
+    public void setNrFrequencyRange(@FrequencyRange int nrFrequencyRange) {
+        mNrFrequencyRange = nrFrequencyRange;
+    }
+
     /** @hide */
     public int getLteEarfcnRsrpBoost() {
         return mLteEarfcnRsrpBoost;
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index bc832c3..240b8a9 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -17,12 +17,12 @@
 package android.telephony;
 
 import android.annotation.UnsupportedAppUsage;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.CarrierConfigManager;
 import android.util.Log;
-import android.content.res.Resources;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -37,25 +37,25 @@
     private static final boolean DBG = false;
 
     /** @hide */
-    @UnsupportedAppUsage
-    public static final int SIGNAL_STRENGTH_NONE_OR_UNKNOWN
-            = TelephonyProtoEnums.SIGNAL_STRENGTH_NONE_OR_UNKNOWN; // = 0
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    public static final int SIGNAL_STRENGTH_NONE_OR_UNKNOWN =
+            CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN; // = 0
     /** @hide */
-    @UnsupportedAppUsage
-    public static final int SIGNAL_STRENGTH_POOR
-            = TelephonyProtoEnums.SIGNAL_STRENGTH_POOR; // = 1
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    public static final int SIGNAL_STRENGTH_POOR =
+            CellSignalStrength.SIGNAL_STRENGTH_POOR; // = 1
     /** @hide */
-    @UnsupportedAppUsage
-    public static final int SIGNAL_STRENGTH_MODERATE
-            = TelephonyProtoEnums.SIGNAL_STRENGTH_MODERATE; // = 2
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    public static final int SIGNAL_STRENGTH_MODERATE =
+            CellSignalStrength.SIGNAL_STRENGTH_MODERATE; // = 2
     /** @hide */
-    @UnsupportedAppUsage
-    public static final int SIGNAL_STRENGTH_GOOD
-            = TelephonyProtoEnums.SIGNAL_STRENGTH_GOOD; // = 3
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    public static final int SIGNAL_STRENGTH_GOOD =
+            CellSignalStrength.SIGNAL_STRENGTH_GOOD; // = 3
     /** @hide */
-    @UnsupportedAppUsage
-    public static final int SIGNAL_STRENGTH_GREAT
-            = TelephonyProtoEnums.SIGNAL_STRENGTH_GREAT; // = 4
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    public static final int SIGNAL_STRENGTH_GREAT =
+            CellSignalStrength.SIGNAL_STRENGTH_GREAT; // = 4
     /** @hide */
     @UnsupportedAppUsage
     public static final int NUM_SIGNAL_STRENGTH_BINS = 5;
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 22c1e58..b41e14e 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -143,9 +143,11 @@
     private boolean mIsOpportunistic;
 
     /**
-     * SubId of the parent subscription, if there is one.
+     * A UUID assigned to the subscription group. It returns
+     * null if not assigned.
      */
-    private int mParentSubId;
+    @Nullable
+    private String mGroupUUID;
 
     /**
      * @hide
@@ -156,7 +158,7 @@
             @Nullable UiccAccessRule[] accessRules, String cardId) {
         this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
                 roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardId,
-                false, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+                false, null);
     }
 
     /**
@@ -166,7 +168,7 @@
             CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
             Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
             @Nullable UiccAccessRule[] accessRules, String cardId, boolean isOpportunistic,
-            int parentSubId) {
+            @Nullable String groupUUID) {
         this.mId = id;
         this.mIccId = iccId;
         this.mSimSlotIndex = simSlotIndex;
@@ -184,7 +186,7 @@
         this.mAccessRules = accessRules;
         this.mCardId = cardId;
         this.mIsOpportunistic = isOpportunistic;
-        this.mParentSubId = parentSubId;
+        this.mGroupUUID = groupUUID;
     }
 
     /**
@@ -388,16 +390,16 @@
     }
 
     /**
-     * Used in scenarios where a child subscription is bundled with a primary parent subscription.
-     * The child subscription will typically be opportunistic (see {@link #isOpportunistic()})
-     * and will be used to provide data services where available, with the parent being the primary
-     * fallback subscription.
+     * Used in scenarios where different subscriptions are bundled as a group.
+     * It's typically a primary and an opportunistic subscription. (see {@link #isOpportunistic()})
+     * Such that those subscriptions will have some affiliated behaviors such as opportunistic
+     * subscription may be invisible to the user.
      *
-     * @return subId of parent subscription if it’s bundled with a primary subscription.
-     * If there isn't one, {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}
+     * @return group UUID a String of group UUID if it belongs to a group. Otherwise
+     * it will return null.
      */
-    public int getParentSubId() {
-        return mParentSubId;
+    public String getGroupUuid() {
+        return mGroupUUID;
     }
 
     /**
@@ -493,11 +495,11 @@
             UiccAccessRule[] accessRules = source.createTypedArray(UiccAccessRule.CREATOR);
             String cardId = source.readString();
             boolean isOpportunistic = source.readBoolean();
-            int parentSubId = source.readInt();
+            String groupUUID = source.readString();
 
             return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName,
                     nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso,
-                    isEmbedded, accessRules, cardId, isOpportunistic, parentSubId);
+                    isEmbedded, accessRules, cardId, isOpportunistic, groupUUID);
         }
 
         @Override
@@ -525,7 +527,7 @@
         dest.writeTypedArray(mAccessRules, flags);
         dest.writeString(mCardId);
         dest.writeBoolean(mIsOpportunistic);
-        dest.writeInt(mParentSubId);
+        dest.writeString(mGroupUUID);
     }
 
     @Override
@@ -559,13 +561,13 @@
                 + " mnc " + mMnc + "mCountryIso=" + mCountryIso + " isEmbedded " + mIsEmbedded
                 + " accessRules " + Arrays.toString(mAccessRules)
                 + " cardId=" + cardIdToPrint + " isOpportunistic " + mIsOpportunistic
-                + " parentSubId=" + mParentSubId + "}";
+                + " mGroupUUID=" + mGroupUUID + "}";
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded,
-                mIsOpportunistic, mParentSubId, mIccId, mNumber, mMcc, mMnc, mCountryIso,
+                mIsOpportunistic, mGroupUUID, mIccId, mNumber, mMcc, mMnc, mCountryIso,
                 mCardId, mDisplayName, mCarrierName, mAccessRules);
     }
 
@@ -588,7 +590,7 @@
                 && mDataRoaming == toCompare.mDataRoaming
                 && mIsEmbedded == toCompare.mIsEmbedded
                 && mIsOpportunistic == toCompare.mIsOpportunistic
-                && mParentSubId == toCompare.mParentSubId
+                && Objects.equals(mGroupUUID, toCompare.mGroupUUID)
                 && Objects.equals(mIccId, toCompare.mIccId)
                 && Objects.equals(mNumber, toCompare.mNumber)
                 && Objects.equals(mMcc, toCompare.mMcc)
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index b5cbf3b..a6fa6bf 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -119,7 +119,6 @@
     @UnsupportedAppUsage
     public static final Uri CONTENT_URI = Uri.parse("content://telephony/siminfo");
 
-
     /**
      * Generates a content {@link Uri} used to receive updates on simInfo change
      * on the given subscriptionId
@@ -577,6 +576,15 @@
     public static final String PARENT_SUB_ID = "parent_sub_id";
 
     /**
+     * TelephonyProvider column name for group ID. Subscriptions with same group ID
+     * are considered bundled together, and should behave as a single subscription at
+     * certain scenarios.
+     *
+     * @hide
+     */
+    public static final String GROUP_UUID = "group_uuid";
+
+    /**
      * Broadcast Action: The user has changed one of the default subs related to
      * data, phone calls, or sms</p>
      *
@@ -2365,19 +2373,40 @@
     }
 
     /**
-     * Set parent subId by simInfo index
+     * Inform SubscriptionManager that subscriptions in the list are bundled
+     * as a group. Typically it's a primary subscription and an opportunistic
+     * subscription. It should only affect multi-SIM scenarios where primary
+     * and opportunistic subscriptions can be activated together.
+     * Being in the same group means they might be activated or deactivated
+     * together, some of them may be invisible to the users, etc.
      *
-     * @param parentSubId subId of its parent subscription.
-     * @param subId the unique SubscriptionInfo index in database
-     * @return the number of records updated
-     * @hide
+     * Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE}
+     * permission or can manage all subscriptions in the list, according to their
+     * acess rules.
+     *
+     * @param subIdList list of subId that will be in the same group
+     * @return groupUUID a UUID assigned to the subscription group. It returns
+     * null if fails.
      *
      */
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-    public int setParentSubId(int parentSubId, int subId) {
-        if (VDBG) logd("[setParentSubId]+ parentSubId:" + parentSubId + " subId:" + subId);
-        return setSubscriptionPropertyHelper(subId, "parentSubId",
-                (iSub)-> iSub.setParentSubId(parentSubId, subId));
+    public String setSubscriptionGroup(int[] subIdList) {
+        String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>";
+        if (VDBG) {
+            logd("[setSubscriptionGroup]+ subIdList:" + Arrays.toString(subIdList));
+        }
+
+        String groupUUID = null;
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub != null) {
+                groupUUID = iSub.setSubscriptionGroup(subIdList, pkgForDebug);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return groupUUID;
     }
 
     private interface CallISubMethodHelper {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index bd1a0fb..d72b5ec 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -21,6 +21,7 @@
 import static com.android.internal.util.Preconditions.checkNotNull;
 
 import android.Manifest;
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -43,6 +44,7 @@
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.BatteryStats;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -52,6 +54,7 @@
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
+import android.os.WorkSource;
 import android.provider.Settings.SettingNotFoundException;
 import android.service.carrier.CarrierIdentifier;
 import android.telecom.PhoneAccount;
@@ -4750,37 +4753,42 @@
     }
 
     /**
-     * Returns all observed cell information from all radios on the
-     * device including the primary and neighboring cells. Calling this method does
-     * not trigger a call to {@link android.telephony.PhoneStateListener#onCellInfoChanged
-     * onCellInfoChanged()}, or change the rate at which
-     * {@link android.telephony.PhoneStateListener#onCellInfoChanged
-     * onCellInfoChanged()} is called.
+     * Requests all available cell information from all radios on the device including the
+     * camped/registered, serving, and neighboring cells.
      *
-     *<p>
-     * The list can include one or more {@link android.telephony.CellInfoGsm CellInfoGsm},
+     * <p>The response can include one or more {@link android.telephony.CellInfoGsm CellInfoGsm},
      * {@link android.telephony.CellInfoCdma CellInfoCdma},
+     * {@link android.telephony.CellInfoTdscdma CellInfoTdscdma},
      * {@link android.telephony.CellInfoLte CellInfoLte}, and
      * {@link android.telephony.CellInfoWcdma CellInfoWcdma} objects, in any combination.
-     * On devices with multiple radios it is typical to see instances of
-     * one or more of any these in the list. In addition, zero, one, or more
-     * of the returned objects may be considered registered; that is, their
+     * It is typical to see instances of one or more of any these in the list. In addition, zero
+     * or more of the returned objects may be considered registered; that is, their
      * {@link android.telephony.CellInfo#isRegistered CellInfo.isRegistered()}
-     * methods may return true.
+     * methods may return true, indicating that the cell is being used or would be used for
+     * signaling communication if necessary.
      *
-     * <p>This method returns valid data for registered cells on devices with
-     * {@link android.content.pm.PackageManager#FEATURE_TELEPHONY}. In cases where only
-     * partial information is available for a particular CellInfo entry, unavailable fields
-     * will be reported as Integer.MAX_VALUE. All reported cells will include at least a
-     * valid set of technology-specific identification info and a power level measurement.
+     * <p>Beginning with {@link android.os.Build.VERSION_CODES#Q Android Q},
+     * if this API results in a change of the cached CellInfo, that change will be reported via
+     * {@link android.telephony.PhoneStateListener#onCellInfoChanged onCellInfoChanged()}.
      *
-     *<p>
-     * This method is preferred over using {@link
+     * <p>Apps targeting {@link android.os.Build.VERSION_CODES#Q Android Q} or higher will no
+     * longer trigger a refresh of the cached CellInfo by invoking this API. Instead, those apps
+     * will receive the latest cached results. Apps targeting
+     * {@link android.os.Build.VERSION_CODES#Q Android Q} or higher that wish to request updated
+     * CellInfo should call
+     * {android.telephony.TelephonyManager#requestCellInfoUpdate requestCellInfoUpdate()} and
+     * listen for responses via {@link android.telephony.PhoneStateListener#onCellInfoChanged
+     * onCellInfoChanged()}.
+     *
+     * <p>This method returns valid data for devices with
+     * {@link android.content.pm.PackageManager#FEATURE_TELEPHONY FEATURE_TELEPHONY}. In cases
+     * where only partial information is available for a particular CellInfo entry, unavailable
+     * fields will be reported as {@link android.telephony.CellInfo#UNAVAILABLE}. All reported
+     * cells will include at least a valid set of technology-specific identification info and a
+     * power level measurement.
+     *
+     * <p>This method is preferred over using {@link
      * android.telephony.TelephonyManager#getCellLocation getCellLocation()}.
-     * However, for older devices, <code>getAllCellInfo()</code> may return
-     * null. In these cases, you should call {@link
-     * android.telephony.TelephonyManager#getCellLocation getCellLocation()}
-     * instead.
      *
      * @return List of {@link android.telephony.CellInfo}; null if cell
      * information is unavailable.
@@ -4791,11 +4799,92 @@
             ITelephony telephony = getITelephony();
             if (telephony == null)
                 return null;
-            return telephony.getAllCellInfo(getOpPackageName());
+            return telephony.getAllCellInfo(
+                    getOpPackageName());
         } catch (RemoteException ex) {
-            return null;
         } catch (NullPointerException ex) {
-            return null;
+        }
+        return null;
+    }
+
+    /** Callback for providing asynchronous {@link CellInfo} on request */
+    public abstract static class CellInfoCallback {
+        /**
+         * Response to
+         * {@link android.telephony.TelephonyManager#requestCellInfoUpdate requestCellInfoUpdate()}.
+         *
+         * <p>Invoked when there is a response to
+         * {@link android.telephony.TelephonyManager#requestCellInfoUpdate requestCellInfoUpdate()}
+         * to provide a list of {@link CellInfo}. If no {@link CellInfo} is available then an empty
+         * list will be provided. If an error occurs, null will be provided.
+         *
+         * @param cellInfo a list of {@link CellInfo}, an empty list, or null.
+         *
+         * {@see android.telephony.TelephonyManager#getAllCellInfo getAllCellInfo()}
+         */
+        public abstract void onCellInfo(List<CellInfo> cellInfo);
+    };
+
+    /**
+     * Requests all available cell information from the current subscription for observed
+     * camped/registered, serving, and neighboring cells.
+     *
+     * <p>Any available results from this request will be provided by calls to
+     * {@link android.telephony.PhoneStateListener#onCellInfoChanged onCellInfoChanged()}
+     * for each active subscription.
+     *
+     * @param executor the executor on which callback will be invoked.
+     * @param callback a callback to receive CellInfo.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+    public void requestCellInfoUpdate(
+            @NonNull Executor executor, @NonNull CellInfoCallback callback) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony == null) return;
+            telephony.requestCellInfoUpdate(
+                    getSubId(),
+                    new ICellInfoCallback.Stub() {
+                        public void onCellInfo(List<CellInfo> cellInfo) {
+                            Binder.withCleanCallingIdentity(() ->
+                                    executor.execute(() -> callback.onCellInfo(cellInfo)));
+                        }
+                    }, getOpPackageName());
+
+        } catch (RemoteException ex) {
+        }
+    }
+
+    /**
+     * Requests all available cell information from the current subscription for observed
+     * camped/registered, serving, and neighboring cells.
+     *
+     * <p>Any available results from this request will be provided by calls to
+     * {@link android.telephony.PhoneStateListener#onCellInfoChanged onCellInfoChanged()}
+     * for each active subscription.
+     *
+     * @param workSource the requestor to whom the power consumption for this should be attributed.
+     * @param executor the executor on which callback will be invoked.
+     * @param callback a callback to receive CellInfo.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(allOf = {android.Manifest.permission.ACCESS_COARSE_LOCATION,
+            android.Manifest.permission.MODIFY_PHONE_STATE})
+    public void requestCellInfoUpdate(@NonNull WorkSource workSource,
+            @NonNull @CallbackExecutor Executor executor, @NonNull CellInfoCallback callback) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony == null) return;
+            telephony.requestCellInfoUpdateWithWorkSource(
+                    getSubId(),
+                    new ICellInfoCallback.Stub() {
+                        public void onCellInfo(List<CellInfo> cellInfo) {
+                            Binder.withCleanCallingIdentity(() ->
+                                    executor.execute(() -> callback.onCellInfo(cellInfo)));
+                        }
+                    }, getOpPackageName(), workSource);
+        } catch (RemoteException ex) {
         }
     }
 
@@ -6349,7 +6438,6 @@
 
     /**
      * Set the preferred network type.
-     * Used for device configuration by some CDMA operators.
      *
      * <p>Requires Permission:
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 2e9bffe..4fd7066 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -60,6 +60,7 @@
     private static final String V3_FORMAT_REGEX = "^\\[ApnSettingV3\\]\\s*";
     private static final String V4_FORMAT_REGEX = "^\\[ApnSettingV4\\]\\s*";
     private static final String V5_FORMAT_REGEX = "^\\[ApnSettingV5\\]\\s*";
+    private static final String V6_FORMAT_REGEX = "^\\[ApnSettingV6\\]\\s*";
 
     /**
      * Default value for mtu if it's not set. Moved from PhoneConstants.
@@ -268,6 +269,7 @@
     private final int mApnSetId;
 
     private boolean mPermanentFailed = false;
+    private final int mCarrierId;
 
     /**
      * Returns the MTU size of the mobile interface to which the APN connected.
@@ -596,6 +598,16 @@
         return mMvnoType;
     }
 
+    /**
+     * Returns the carrier id for this APN.
+     *
+     * @see Builder#setCarrierId(int)
+     * @return the carrier id
+     */
+    public int getCarrierId() {
+        return mCarrierId;
+    }
+
     private ApnSetting(Builder builder) {
         this.mEntryName = builder.mEntryName;
         this.mApnName = builder.mApnName;
@@ -623,47 +635,53 @@
         this.mMvnoType = builder.mMvnoType;
         this.mMvnoMatchData = builder.mMvnoMatchData;
         this.mApnSetId = builder.mApnSetId;
+        this.mCarrierId = builder.mCarrierId;
     }
 
-    /** @hide */
+    /**
+     * @hide
+     */
     public static ApnSetting makeApnSetting(int id, String operatorNumeric, String entryName,
             String apnName, String proxyAddress, int proxyPort, Uri mmsc,
             String mmsProxyAddress, int mmsProxyPort, String user, String password,
             int authType, int mApnTypeBitmask, int protocol, int roamingProtocol,
-            boolean carrierEnabled, int networkTypeBitmask, int profileId, boolean modemCognitive,
-            int maxConns, int waitTime, int maxConnsTime, int mtu, int mvnoType,
-            String mvnoMatchData, int apnSetId) {
+            boolean carrierEnabled, int networkTypeBitmask, int profileId,
+            boolean modemCognitive, int maxConns, int waitTime, int maxConnsTime, int mtu,
+            int mvnoType, String mvnoMatchData, int apnSetId, int carrierId) {
         return new Builder()
-                .setId(id)
-                .setOperatorNumeric(operatorNumeric)
-                .setEntryName(entryName)
-                .setApnName(apnName)
-                .setProxyAddress(proxyAddress)
-                .setProxyPort(proxyPort)
-                .setMmsc(mmsc)
-                .setMmsProxyAddress(mmsProxyAddress)
-                .setMmsProxyPort(mmsProxyPort)
-                .setUser(user)
-                .setPassword(password)
-                .setAuthType(authType)
-                .setApnTypeBitmask(mApnTypeBitmask)
-                .setProtocol(protocol)
-                .setRoamingProtocol(roamingProtocol)
-                .setCarrierEnabled(carrierEnabled)
-                .setNetworkTypeBitmask(networkTypeBitmask)
-                .setProfileId(profileId)
-                .setModemCognitive(modemCognitive)
-                .setMaxConns(maxConns)
-                .setWaitTime(waitTime)
-                .setMaxConnsTime(maxConnsTime)
-                .setMtu(mtu)
-                .setMvnoType(mvnoType)
-                .setMvnoMatchData(mvnoMatchData)
-                .setApnSetId(apnSetId)
-                .buildWithoutCheck();
+            .setId(id)
+            .setOperatorNumeric(operatorNumeric)
+            .setEntryName(entryName)
+            .setApnName(apnName)
+            .setProxyAddress(proxyAddress)
+            .setProxyPort(proxyPort)
+            .setMmsc(mmsc)
+            .setMmsProxyAddress(mmsProxyAddress)
+            .setMmsProxyPort(mmsProxyPort)
+            .setUser(user)
+            .setPassword(password)
+            .setAuthType(authType)
+            .setApnTypeBitmask(mApnTypeBitmask)
+            .setProtocol(protocol)
+            .setRoamingProtocol(roamingProtocol)
+            .setCarrierEnabled(carrierEnabled)
+            .setNetworkTypeBitmask(networkTypeBitmask)
+            .setProfileId(profileId)
+            .setModemCognitive(modemCognitive)
+            .setMaxConns(maxConns)
+            .setWaitTime(waitTime)
+            .setMaxConnsTime(maxConnsTime)
+            .setMtu(mtu)
+            .setMvnoType(mvnoType)
+            .setMvnoMatchData(mvnoMatchData)
+            .setApnSetId(apnSetId)
+            .setCarrierId(carrierId)
+            .buildWithoutCheck();
     }
 
-    /** @hide */
+    /**
+     * @hide
+     */
     public static ApnSetting makeApnSetting(int id, String operatorNumeric, String entryName,
             String apnName, String proxyAddress, int proxyPort, Uri mmsc,
             String mmsProxyAddress, int mmsProxyPort, String user, String password,
@@ -675,10 +693,12 @@
             mmsc, mmsProxyAddress, mmsProxyPort, user, password, authType, mApnTypeBitmask,
             protocol, roamingProtocol, carrierEnabled, networkTypeBitmask, profileId,
             modemCognitive, maxConns, waitTime, maxConnsTime, mtu, mvnoType, mvnoMatchData,
-            Carriers.NO_SET_SET);
+            Carriers.NO_SET_SET, TelephonyManager.UNKNOWN_CARRIER_ID);
     }
 
-    /** @hide */
+    /**
+     * @hide
+     */
     public static ApnSetting makeApnSetting(Cursor cursor) {
         final int apnTypesBitmask = getApnTypesBitmaskFromString(
                 cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.TYPE)));
@@ -688,60 +708,64 @@
             final int bearerBitmask = cursor.getInt(cursor.getColumnIndexOrThrow(
                     Telephony.Carriers.BEARER_BITMASK));
             networkTypeBitmask =
-                    ServiceState.convertBearerBitmaskToNetworkTypeBitmask(bearerBitmask);
+                ServiceState.convertBearerBitmaskToNetworkTypeBitmask(bearerBitmask);
         }
 
         return makeApnSetting(
-                cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers._ID)),
-                cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.NUMERIC)),
-                cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.NAME)),
-                cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.APN)),
-                cursor.getString(
-                        cursor.getColumnIndexOrThrow(Telephony.Carriers.PROXY)),
-                portFromString(cursor.getString(
-                        cursor.getColumnIndexOrThrow(Telephony.Carriers.PORT))),
-                UriFromString(cursor.getString(
-                        cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSC))),
-                cursor.getString(
-                        cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSPROXY)),
-                portFromString(cursor.getString(
-                        cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSPORT))),
-                cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.USER)),
-                cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.PASSWORD)),
-                cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.AUTH_TYPE)),
-                apnTypesBitmask,
-                getProtocolIntFromString(
-                    cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.PROTOCOL))),
-                getProtocolIntFromString(
-                    cursor.getString(cursor.getColumnIndexOrThrow(
-                        Telephony.Carriers.ROAMING_PROTOCOL))),
-                cursor.getInt(cursor.getColumnIndexOrThrow(
-                        Telephony.Carriers.CARRIER_ENABLED)) == 1,
-                networkTypeBitmask,
-                cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.PROFILE_ID)),
-                cursor.getInt(cursor.getColumnIndexOrThrow(
-                        Telephony.Carriers.MODEM_COGNITIVE)) == 1,
-                cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MAX_CONNS)),
-                cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.WAIT_TIME)),
-                cursor.getInt(cursor.getColumnIndexOrThrow(
-                        Telephony.Carriers.MAX_CONNS_TIME)),
-                cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MTU)),
-                getMvnoTypeIntFromString(
-                    cursor.getString(cursor.getColumnIndexOrThrow(
-                        Telephony.Carriers.MVNO_TYPE))),
+            cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers._ID)),
+            cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.NUMERIC)),
+            cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.NAME)),
+            cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.APN)),
+            cursor.getString(
+                cursor.getColumnIndexOrThrow(Telephony.Carriers.PROXY)),
+            portFromString(cursor.getString(
+                cursor.getColumnIndexOrThrow(Telephony.Carriers.PORT))),
+            UriFromString(cursor.getString(
+                cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSC))),
+            cursor.getString(
+                cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSPROXY)),
+            portFromString(cursor.getString(
+                cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSPORT))),
+            cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.USER)),
+            cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.PASSWORD)),
+            cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.AUTH_TYPE)),
+            apnTypesBitmask,
+            getProtocolIntFromString(
+                cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.PROTOCOL))),
+            getProtocolIntFromString(
                 cursor.getString(cursor.getColumnIndexOrThrow(
-                        Telephony.Carriers.MVNO_MATCH_DATA)),
-                cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.APN_SET_ID)));
+                    Telephony.Carriers.ROAMING_PROTOCOL))),
+            cursor.getInt(cursor.getColumnIndexOrThrow(
+                Telephony.Carriers.CARRIER_ENABLED)) == 1,
+            networkTypeBitmask,
+            cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.PROFILE_ID)),
+            cursor.getInt(cursor.getColumnIndexOrThrow(
+                Telephony.Carriers.MODEM_COGNITIVE)) == 1,
+            cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MAX_CONNS)),
+            cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.WAIT_TIME)),
+            cursor.getInt(cursor.getColumnIndexOrThrow(
+                Telephony.Carriers.MAX_CONNS_TIME)),
+            cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MTU)),
+            getMvnoTypeIntFromString(
+                cursor.getString(cursor.getColumnIndexOrThrow(
+                    Telephony.Carriers.MVNO_TYPE))),
+            cursor.getString(cursor.getColumnIndexOrThrow(
+                Telephony.Carriers.MVNO_MATCH_DATA)),
+            cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.APN_SET_ID)),
+            cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.CARRIER_ID)));
     }
 
-    /** @hide */
+    /**
+     * @hide
+     */
     public static ApnSetting makeApnSetting(ApnSetting apn) {
         return makeApnSetting(apn.mId, apn.mOperatorNumeric, apn.mEntryName, apn.mApnName,
-                apn.mProxyAddress, apn.mProxyPort, apn.mMmsc, apn.mMmsProxyAddress,
-                apn.mMmsProxyPort, apn.mUser, apn.mPassword, apn.mAuthType, apn.mApnTypeBitmask,
-                apn.mProtocol, apn.mRoamingProtocol, apn.mCarrierEnabled, apn.mNetworkTypeBitmask,
-                apn.mProfileId, apn.mPersistent, apn.mMaxConns, apn.mWaitTime,
-                apn.mMaxConnsTime, apn.mMtu, apn.mMvnoType, apn.mMvnoMatchData, apn.mApnSetId);
+            apn.mProxyAddress, apn.mProxyPort, apn.mMmsc, apn.mMmsProxyAddress,
+            apn.mMmsProxyPort, apn.mUser, apn.mPassword, apn.mAuthType, apn.mApnTypeBitmask,
+            apn.mProtocol, apn.mRoamingProtocol, apn.mCarrierEnabled, apn.mNetworkTypeBitmask,
+            apn.mProfileId, apn.mPersistent, apn.mMaxConns, apn.mWaitTime,
+            apn.mMaxConnsTime, apn.mMtu, apn.mMvnoType, apn.mMvnoMatchData, apn.mApnSetId,
+            apn.mCarrierId);
     }
 
     /**
@@ -783,6 +807,13 @@
      *   <profileId>, <modemCognitive>, <maxConns>, <waitTime>, <maxConnsTime>, <mtu>,
      *   <mvnoType>, <mvnoMatchData>, <networkTypeBitmask>, <apnSetId>
      *
+     * v6 format:
+     *   [ApnSettingV6] <carrier>, <apn>, <proxy>, <port>, <user>, <password>, <server>,
+     *   <mmsc>, <mmsproxy>, <mmsport>, <mcc>, <mnc>, <authtype>,
+     *   <type>[| <type>...], <protocol>, <roaming_protocol>, <carrierEnabled>, <bearerBitmask>,
+     *   <profileId>, <modemCognitive>, <maxConns>, <waitTime>, <maxConnsTime>, <mtu>,
+     *   <mvnoType>, <mvnoMatchData>, <networkTypeBitmask>, <apnSetId>, <carrierId>
+     *
      * Note that the strings generated by {@link #toString()} do not contain the username
      * and password and thus cannot be read by this method.
      *
@@ -795,7 +826,10 @@
 
         int version;
         // matches() operates on the whole string, so append .* to the regex.
-        if (data.matches(V5_FORMAT_REGEX + ".*")) {
+        if (data.matches(V6_FORMAT_REGEX + ".*")) {
+            version = 6;
+            data = data.replaceFirst(V6_FORMAT_REGEX, "");
+        } else if (data.matches(V5_FORMAT_REGEX + ".*")) {
             version = 5;
             data = data.replaceFirst(V5_FORMAT_REGEX, "");
         } else if (data.matches(V4_FORMAT_REGEX + ".*")) {
@@ -837,6 +871,7 @@
         String mvnoType = "";
         String mvnoMatchData = "";
         int apnSetId = Carriers.NO_SET_SET;
+        int carrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
         if (version == 1) {
             typeArray = new String[a.length - 13];
             System.arraycopy(a, 13, typeArray, 0, a.length - 13);
@@ -880,6 +915,9 @@
             if (a.length > 27) {
                 apnSetId = Integer.parseInt(a[27]);
             }
+            if (a.length > 28) {
+                carrierId = Integer.parseInt(a[28]);
+            }
         }
 
         // If both bearerBitmask and networkTypeBitmask were specified, bearerBitmask would be
@@ -894,7 +932,8 @@
             getApnTypesBitmaskFromString(TextUtils.join(",", typeArray)),
             getProtocolIntFromString(protocol), getProtocolIntFromString(roamingProtocol),
             carrierEnabled, networkTypeBitmask, profileId, modemCognitive, maxConns, waitTime,
-            maxConnsTime, mtu, getMvnoTypeIntFromString(mvnoType), mvnoMatchData, apnSetId);
+            maxConnsTime, mtu, getMvnoTypeIntFromString(mvnoType), mvnoMatchData, apnSetId,
+            carrierId);
     }
 
     /**
@@ -1013,7 +1052,10 @@
 
     // TODO - if we have this function we should also have hashCode.
     // Also should handle changes in type order and perhaps case-insensitivity.
-    /** @hide */
+
+    /**
+     * @hide
+     */
     public boolean equals(Object o) {
         if (o instanceof ApnSetting == false) {
             return false;
@@ -1022,31 +1064,32 @@
         ApnSetting other = (ApnSetting) o;
 
         return mEntryName.equals(other.mEntryName)
-                && Objects.equals(mId, other.mId)
-                && Objects.equals(mOperatorNumeric, other.mOperatorNumeric)
-                && Objects.equals(mApnName, other.mApnName)
-                && Objects.equals(mProxyAddress, other.mProxyAddress)
-                && Objects.equals(mMmsc, other.mMmsc)
-                && Objects.equals(mMmsProxyAddress, other.mMmsProxyAddress)
-                && Objects.equals(mMmsProxyPort, other.mMmsProxyPort)
-                && Objects.equals(mProxyPort, other.mProxyPort)
-                && Objects.equals(mUser, other.mUser)
-                && Objects.equals(mPassword, other.mPassword)
-                && Objects.equals(mAuthType, other.mAuthType)
-                && Objects.equals(mApnTypeBitmask, other.mApnTypeBitmask)
-                && Objects.equals(mProtocol, other.mProtocol)
-                && Objects.equals(mRoamingProtocol, other.mRoamingProtocol)
-                && Objects.equals(mCarrierEnabled, other.mCarrierEnabled)
-                && Objects.equals(mProfileId, other.mProfileId)
-                && Objects.equals(mPersistent, other.mPersistent)
-                && Objects.equals(mMaxConns, other.mMaxConns)
-                && Objects.equals(mWaitTime, other.mWaitTime)
-                && Objects.equals(mMaxConnsTime, other.mMaxConnsTime)
-                && Objects.equals(mMtu, other.mMtu)
-                && Objects.equals(mMvnoType, other.mMvnoType)
-                && Objects.equals(mMvnoMatchData, other.mMvnoMatchData)
-                && Objects.equals(mNetworkTypeBitmask, other.mNetworkTypeBitmask)
-                && Objects.equals(mApnSetId, other.mApnSetId);
+            && Objects.equals(mId, other.mId)
+            && Objects.equals(mOperatorNumeric, other.mOperatorNumeric)
+            && Objects.equals(mApnName, other.mApnName)
+            && Objects.equals(mProxyAddress, other.mProxyAddress)
+            && Objects.equals(mMmsc, other.mMmsc)
+            && Objects.equals(mMmsProxyAddress, other.mMmsProxyAddress)
+            && Objects.equals(mMmsProxyPort, other.mMmsProxyPort)
+            && Objects.equals(mProxyPort, other.mProxyPort)
+            && Objects.equals(mUser, other.mUser)
+            && Objects.equals(mPassword, other.mPassword)
+            && Objects.equals(mAuthType, other.mAuthType)
+            && Objects.equals(mApnTypeBitmask, other.mApnTypeBitmask)
+            && Objects.equals(mProtocol, other.mProtocol)
+            && Objects.equals(mRoamingProtocol, other.mRoamingProtocol)
+            && Objects.equals(mCarrierEnabled, other.mCarrierEnabled)
+            && Objects.equals(mProfileId, other.mProfileId)
+            && Objects.equals(mPersistent, other.mPersistent)
+            && Objects.equals(mMaxConns, other.mMaxConns)
+            && Objects.equals(mWaitTime, other.mWaitTime)
+            && Objects.equals(mMaxConnsTime, other.mMaxConnsTime)
+            && Objects.equals(mMtu, other.mMtu)
+            && Objects.equals(mMvnoType, other.mMvnoType)
+            && Objects.equals(mMvnoMatchData, other.mMvnoMatchData)
+            && Objects.equals(mNetworkTypeBitmask, other.mNetworkTypeBitmask)
+            && Objects.equals(mApnSetId, other.mApnSetId)
+            && Objects.equals(mCarrierId, other.mCarrierId);
     }
 
     /**
@@ -1069,29 +1112,30 @@
         ApnSetting other = (ApnSetting) o;
 
         return mEntryName.equals(other.mEntryName)
-                && Objects.equals(mOperatorNumeric, other.mOperatorNumeric)
-                && Objects.equals(mApnName, other.mApnName)
-                && Objects.equals(mProxyAddress, other.mProxyAddress)
-                && Objects.equals(mMmsc, other.mMmsc)
-                && Objects.equals(mMmsProxyAddress, other.mMmsProxyAddress)
-                && Objects.equals(mMmsProxyPort, other.mMmsProxyPort)
-                && Objects.equals(mProxyPort, other.mProxyPort)
-                && Objects.equals(mUser, other.mUser)
-                && Objects.equals(mPassword, other.mPassword)
-                && Objects.equals(mAuthType, other.mAuthType)
-                && Objects.equals(mApnTypeBitmask, other.mApnTypeBitmask)
-                && (isDataRoaming || Objects.equals(mProtocol, other.mProtocol))
-                && (!isDataRoaming || Objects.equals(mRoamingProtocol, other.mRoamingProtocol))
-                && Objects.equals(mCarrierEnabled, other.mCarrierEnabled)
-                && Objects.equals(mProfileId, other.mProfileId)
-                && Objects.equals(mPersistent, other.mPersistent)
-                && Objects.equals(mMaxConns, other.mMaxConns)
-                && Objects.equals(mWaitTime, other.mWaitTime)
-                && Objects.equals(mMaxConnsTime, other.mMaxConnsTime)
-                && Objects.equals(mMtu, other.mMtu)
-                && Objects.equals(mMvnoType, other.mMvnoType)
-                && Objects.equals(mMvnoMatchData, other.mMvnoMatchData)
-                && Objects.equals(mApnSetId, other.mApnSetId);
+            && Objects.equals(mOperatorNumeric, other.mOperatorNumeric)
+            && Objects.equals(mApnName, other.mApnName)
+            && Objects.equals(mProxyAddress, other.mProxyAddress)
+            && Objects.equals(mMmsc, other.mMmsc)
+            && Objects.equals(mMmsProxyAddress, other.mMmsProxyAddress)
+            && Objects.equals(mMmsProxyPort, other.mMmsProxyPort)
+            && Objects.equals(mProxyPort, other.mProxyPort)
+            && Objects.equals(mUser, other.mUser)
+            && Objects.equals(mPassword, other.mPassword)
+            && Objects.equals(mAuthType, other.mAuthType)
+            && Objects.equals(mApnTypeBitmask, other.mApnTypeBitmask)
+            && (isDataRoaming || Objects.equals(mProtocol, other.mProtocol))
+            && (!isDataRoaming || Objects.equals(mRoamingProtocol, other.mRoamingProtocol))
+            && Objects.equals(mCarrierEnabled, other.mCarrierEnabled)
+            && Objects.equals(mProfileId, other.mProfileId)
+            && Objects.equals(mPersistent, other.mPersistent)
+            && Objects.equals(mMaxConns, other.mMaxConns)
+            && Objects.equals(mWaitTime, other.mWaitTime)
+            && Objects.equals(mMaxConnsTime, other.mMaxConnsTime)
+            && Objects.equals(mMtu, other.mMtu)
+            && Objects.equals(mMvnoType, other.mMvnoType)
+            && Objects.equals(mMvnoMatchData, other.mMvnoMatchData)
+            && Objects.equals(mApnSetId, other.mApnSetId)
+            && Objects.equals(mCarrierId, other.mCarrierId);
     }
 
     /**
@@ -1103,22 +1147,23 @@
      */
     public boolean similar(ApnSetting other) {
         return (!this.canHandleType(TYPE_DUN)
-                && !other.canHandleType(TYPE_DUN)
-                && Objects.equals(this.mApnName, other.mApnName)
-                && !typeSameAny(this, other)
-                && xorEquals(this.mProxyAddress, other.mProxyAddress)
-                && xorEqualsInt(this.mProxyPort, other.mProxyPort)
-                && xorEquals(this.mProtocol, other.mProtocol)
-                && xorEquals(this.mRoamingProtocol, other.mRoamingProtocol)
-                && Objects.equals(this.mCarrierEnabled, other.mCarrierEnabled)
-                && Objects.equals(this.mProfileId, other.mProfileId)
-                && Objects.equals(this.mMvnoType, other.mMvnoType)
-                && Objects.equals(this.mMvnoMatchData, other.mMvnoMatchData)
-                && xorEquals(this.mMmsc, other.mMmsc)
-                && xorEquals(this.mMmsProxyAddress, other.mMmsProxyAddress)
-                && xorEqualsInt(this.mMmsProxyPort, other.mMmsProxyPort))
-                && Objects.equals(this.mNetworkTypeBitmask, other.mNetworkTypeBitmask)
-                && Objects.equals(mApnSetId, other.mApnSetId);
+            && !other.canHandleType(TYPE_DUN)
+            && Objects.equals(this.mApnName, other.mApnName)
+            && !typeSameAny(this, other)
+            && xorEquals(this.mProxyAddress, other.mProxyAddress)
+            && xorEqualsInt(this.mProxyPort, other.mProxyPort)
+            && xorEquals(this.mProtocol, other.mProtocol)
+            && xorEquals(this.mRoamingProtocol, other.mRoamingProtocol)
+            && Objects.equals(this.mCarrierEnabled, other.mCarrierEnabled)
+            && Objects.equals(this.mProfileId, other.mProfileId)
+            && Objects.equals(this.mMvnoType, other.mMvnoType)
+            && Objects.equals(this.mMvnoMatchData, other.mMvnoMatchData)
+            && xorEquals(this.mMmsc, other.mMmsc)
+            && xorEquals(this.mMmsProxyAddress, other.mMmsProxyAddress)
+            && xorEqualsInt(this.mMmsProxyPort, other.mMmsProxyPort))
+            && Objects.equals(this.mNetworkTypeBitmask, other.mNetworkTypeBitmask)
+            && Objects.equals(mApnSetId, other.mApnSetId)
+            && Objects.equals(mCarrierId, other.mCarrierId);
     }
 
     // Equal or one is null.
@@ -1164,6 +1209,7 @@
         apnValue.put(Telephony.Carriers.CARRIER_ENABLED, mCarrierEnabled);
         apnValue.put(Telephony.Carriers.MVNO_TYPE, getMvnoTypeStringFromInt(mMvnoType));
         apnValue.put(Telephony.Carriers.NETWORK_TYPE_BITMASK, mNetworkTypeBitmask);
+        apnValue.put(Telephony.Carriers.CARRIER_ID, mCarrierId);
 
         return apnValue;
     }
@@ -1321,6 +1367,8 @@
         dest.writeBoolean(mCarrierEnabled);
         dest.writeInt(mMvnoType);
         dest.writeInt(mNetworkTypeBitmask);
+        dest.writeInt(mApnSetId);
+        dest.writeInt(mCarrierId);
     }
 
     private static ApnSetting readFromParcel(Parcel in) {
@@ -1330,7 +1378,7 @@
         final String apnName = in.readString();
         final String proxy = in.readString();
         final int port = in.readInt();
-        final Uri mmsc = (Uri)in.readValue(Uri.class.getClassLoader());
+        final Uri mmsc = (Uri) in.readValue(Uri.class.getClassLoader());
         final String mmsProxy = in.readString();
         final int mmsPort = in.readInt();
         final String user = in.readString();
@@ -1342,11 +1390,13 @@
         final boolean carrierEnabled = in.readBoolean();
         final int mvnoType = in.readInt();
         final int networkTypeBitmask = in.readInt();
+        final int apnSetId = in.readInt();
+        final int carrierId = in.readInt();
 
         return makeApnSetting(id, operatorNumeric, entryName, apnName,
             proxy, port, mmsc, mmsProxy, mmsPort, user, password, authType, apnTypesBitmask,
             protocol, roamingProtocol, carrierEnabled, networkTypeBitmask, 0, false,
-            0, 0, 0, 0, mvnoType, null);
+            0, 0, 0, 0, mvnoType, null, apnSetId, carrierId);
     }
 
     public static final Parcelable.Creator<ApnSetting> CREATOR =
@@ -1422,6 +1472,7 @@
         private int mMvnoType = UNSPECIFIED_INT;
         private String mMvnoMatchData;
         private int mApnSetId;
+        private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
 
         /**
          * Default constructor for Builder.
@@ -1751,6 +1802,19 @@
         }
 
         /**
+         * Sets the carrier id for this APN.
+         *
+         * See {@link TelephonyManager#getSimCarrierId()} which provides more background for what a
+         * carrier ID is.
+         *
+         * @param carrierId the carrier id to set for this APN
+         */
+        public Builder setCarrierId(int carrierId) {
+            this.mCarrierId = carrierId;
+            return this;
+        }
+
+        /**
          * Builds {@link ApnSetting} from this builder.
          *
          * @return {@code null} if {@link #setApnName(String)} or {@link #setEntryName(String)}
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
index 7f69f43..3f22f98 100644
--- a/telephony/java/android/telephony/ims/feature/ImsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -211,12 +211,19 @@
      * Contains the capabilities defined and supported by an ImsFeature in the form of a bit mask.
      * @hide
      */
+    @SystemApi  // SystemApi only because it was leaked through type usage in a previous release.
     public static class Capabilities {
         protected int mCapabilities = 0;
 
+        /**
+         * @hide
+         */
         public Capabilities() {
         }
 
+        /**
+         * @hide
+         */
         protected Capabilities(int capabilities) {
             mCapabilities = capabilities;
         }
@@ -224,6 +231,7 @@
         /**
          * @param capabilities Capabilities to be added to the configuration in the form of a
          *     bit mask.
+         * @hide
          */
         public void addCapabilities(int capabilities) {
             mCapabilities |= capabilities;
@@ -232,6 +240,7 @@
         /**
          * @param capabilities Capabilities to be removed to the configuration in the form of a
          *     bit mask.
+         * @hide
          */
         public void removeCapabilities(int capabilities) {
             mCapabilities &= ~capabilities;
@@ -239,6 +248,7 @@
 
         /**
          * @return true if all of the capabilities specified are capable.
+         * @hide
          */
         public boolean isCapable(int capabilities) {
             return (mCapabilities & capabilities) == capabilities;
@@ -246,6 +256,7 @@
 
         /**
          * @return a deep copy of the Capabilites.
+         * @hide
          */
         public Capabilities copy() {
             return new Capabilities(mCapabilities);
@@ -253,6 +264,7 @@
 
         /**
          * @return a bitmask containing the capability flags directly.
+         * @hide
          */
         public int getMask() {
             return mCapabilities;
diff --git a/telephony/java/com/android/internal/telephony/IApnSourceService.aidl b/telephony/java/com/android/internal/telephony/IApnSourceService.aidl
index 07bb18b..34c9067 100644
--- a/telephony/java/com/android/internal/telephony/IApnSourceService.aidl
+++ b/telephony/java/com/android/internal/telephony/IApnSourceService.aidl
@@ -20,5 +20,5 @@
 
 interface IApnSourceService {
     /** Retreive APNs. */
-    ContentValues[] getApns();
+    ContentValues[] getApns(int subId);
 }
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 4bdec08..bc44519 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -165,13 +165,23 @@
     int setOpportunistic(boolean opportunistic, int subId);
 
     /**
-     * Set parent subId by simInfo index
+     * Inform SubscriptionManager that subscriptions in the list are bundled
+     * as a group. Typically it's a primary subscription and an opportunistic
+     * subscription. It should only affect multi-SIM scenarios where primary
+     * and opportunistic subscriptions can be activated together.
+     * Being in the same group means they might be activated or deactivated
+     * together, some of them may be invisible to the users, etc.
      *
-     * @param parentSubId: subId of its parent subscription.
-     * @param subId the unique SubscriptionInfo index in database
-     * @return the number of records updated
+     * Caller will either have {@link android.Manifest.permission.MODIFY_PHONE_STATE}
+     * permission or can manage all subscriptions in the list, according to their
+     * acess rules.
+     *
+     * @param subIdList list of subId that will be in the same group
+     * @return groupUUID a UUID assigned to the subscription group. It returns
+     * null if fails.
+     *
      */
-    int setParentSubId(int parentSubId, int subId);
+    String setSubscriptionGroup(in int[] subIdList, String callingPackage);
 
     /**
      * Set which subscription is preferred for cellular data. It's
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 85a9cf5..f0e8586 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -22,6 +22,7 @@
 import android.os.IBinder;
 import android.os.Messenger;
 import android.os.ResultReceiver;
+import android.os.WorkSource;
 import android.net.NetworkStats;
 import android.net.Uri;
 import android.service.carrier.CarrierIdentifier;
@@ -30,6 +31,7 @@
 import android.telephony.CellInfo;
 import android.telephony.ClientRequestStats;
 import android.telephony.IccOpenLogicalChannelResponse;
+import android.telephony.ICellInfoCallback;
 import android.telephony.ModemActivityInfo;
 import android.telephony.NeighboringCellInfo;
 import android.telephony.NetworkScanRequest;
@@ -507,11 +509,26 @@
     int getLteOnCdmaModeForSubscriber(int subId, String callingPackage);
 
     /**
-     * Returns the all observed cell information of the device.
+     * Returns all observed cell information of the device.
      */
     List<CellInfo> getAllCellInfo(String callingPkg);
 
     /**
+     * Request a cell information update for the specified subscription,
+     * reported via the CellInfoCallback.
+     */
+    void requestCellInfoUpdate(int subId, in ICellInfoCallback cb, String callingPkg);
+
+    /**
+     * Request a cell information update for the specified subscription,
+     * reported via the CellInfoCallback.
+     *
+     * @param workSource the requestor to whom the power consumption for this should be attributed.
+     */
+    void requestCellInfoUpdateWithWorkSource(
+            int subId, in ICellInfoCallback cb, in String callingPkg, in WorkSource ws);
+
+    /**
      * Sets minimum time in milli-seconds between onCellInfoChanged
      */
     void setCellInfoListRate(int rateInMillis);
diff --git a/tests/net/java/android/net/MacAddressTest.java b/tests/net/java/android/net/MacAddressTest.java
index b9222a8..17486e0 100644
--- a/tests/net/java/android/net/MacAddressTest.java
+++ b/tests/net/java/android/net/MacAddressTest.java
@@ -16,6 +16,7 @@
 
 package android.net;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -24,12 +25,13 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
-import java.util.Arrays;
-import java.util.Random;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.net.Inet6Address;
+import java.util.Arrays;
+import java.util.Random;
+
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class MacAddressTest {
@@ -285,6 +287,19 @@
                 MacAddress.fromString("00:00:00:00:00:00")));
     }
 
+    /**
+     * Tests that link-local address generation from MAC is valid.
+     */
+    @Test
+    public void testLinkLocalFromMacGeneration() {
+        MacAddress mac = MacAddress.fromString("52:74:f2:b1:a8:7f");
+        byte[] inet6ll = {(byte) 0xfe, (byte) 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x74,
+            (byte) 0xf2, (byte) 0xff, (byte) 0xfe, (byte) 0xb1, (byte) 0xa8, 0x7f};
+        Inet6Address llv6 = mac.getLinkLocalIpv6FromEui48Mac();
+        assertTrue(llv6.isLinkLocalAddress());
+        assertArrayEquals(inet6ll, llv6.getAddress());
+    }
+
     static byte[] toByteArray(int... in) {
         byte[] out = new byte[in.length];
         for (int i = 0; i < in.length; i++) {
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 0bc5221..583f14a 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -431,7 +431,7 @@
 
 void Debug::DumpResStringPool(const android::ResStringPool* pool, text::Printer* printer) {
   using namespace android;
-  
+
   if (pool->getError() == NO_INIT) {
     printer->Print("String pool is unitialized.\n");
     return;
@@ -460,7 +460,7 @@
   const size_t NS = pool->size();
   for (size_t s=0; s<NS; s++) {
     String8 str = pool->string8ObjectAt(s);
-    printer->Print(StringPrintf("String #%zd : %s\n", s, str.string()));
+    printer->Print(StringPrintf("String #%zd: %s\n", s, str.string()));
   }
 }
 
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index dbe5ac5..da22e88 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -718,7 +718,7 @@
           // This must be a FileReference.
           std::unique_ptr<FileReference> file_ref =
               util::make_unique<FileReference>(dst_pool->MakeRef(
-                  str, StringPool::Context(StringPool::Context::kHighPriority, config)));
+                  str, StringPool::Context(StringPool::Context::kHighPriority, config), data));
           if (type == ResourceType::kRaw) {
             file_ref->type = ResourceFile::Type::kUnknown;
           } else if (util::EndsWith(*file_ref->path, ".xml")) {
@@ -730,7 +730,7 @@
         }
 
         // There are no styles associated with this string, so treat it as a simple string.
-        return util::make_unique<String>(dst_pool->MakeRef(str, StringPool::Context(config)));
+        return util::make_unique<String>(dst_pool->MakeRef(str, StringPool::Context(config), data));
       }
     } break;
 
diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp
index 8eabd32..a8c2666 100644
--- a/tools/aapt2/StringPool.cpp
+++ b/tools/aapt2/StringPool.cpp
@@ -165,12 +165,13 @@
   return MakeRefImpl(str, Context{}, true);
 }
 
-StringPool::Ref StringPool::MakeRef(const StringPiece& str, const Context& context) {
-  return MakeRefImpl(str, context, true);
+StringPool::Ref StringPool::MakeRef(const StringPiece& str, const Context& context,
+                                    Maybe<size_t> index) {
+  return MakeRefImpl(str, context, true, index);
 }
 
 StringPool::Ref StringPool::MakeRefImpl(const StringPiece& str, const Context& context,
-                                        bool unique) {
+                                        bool unique, Maybe<size_t> index) {
   if (unique) {
     auto range = indexed_strings_.equal_range(str);
     for (auto iter = range.first; iter != range.second; ++iter) {
@@ -180,15 +181,26 @@
     }
   }
 
+  const size_t size = strings_.size();
+  // Insert the string at the end of the string vector if no index is specified
+  const size_t insertion_index = index ? index.value() : size;
+
   std::unique_ptr<Entry> entry(new Entry());
   entry->value = str.to_string();
   entry->context = context;
-  entry->index_ = strings_.size();
+  entry->index_ = insertion_index;
   entry->ref_ = 0;
   entry->pool_ = this;
 
   Entry* borrow = entry.get();
-  strings_.emplace_back(std::move(entry));
+  if (insertion_index == size) {
+    strings_.emplace_back(std::move(entry));
+  } else {
+    // Allocate enough space for the string at the index
+    strings_.resize(std::max(insertion_index + 1, size));
+    strings_[insertion_index] = std::move(entry);
+  }
+
   indexed_strings_.insert(std::make_pair(StringPiece(borrow->value), borrow));
   return Ref(borrow);
 }
diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h
index 1006ca9..115d5d3 100644
--- a/tools/aapt2/StringPool.h
+++ b/tools/aapt2/StringPool.h
@@ -166,7 +166,8 @@
 
   // Adds a string to the pool, unless it already exists, with a context object that can be used
   // when sorting the string pool. Returns a reference to the string in the pool.
-  Ref MakeRef(const android::StringPiece& str, const Context& context);
+  Ref MakeRef(const android::StringPiece& str, const Context& context,
+              Maybe<size_t> index = {});
 
   // Adds a string from another string pool. Returns a reference to the string in the string pool.
   Ref MakeRef(const Ref& ref);
@@ -210,7 +211,8 @@
 
   static bool Flatten(BigBuffer* out, const StringPool& pool, bool utf8, IDiagnostics* diag);
 
-  Ref MakeRefImpl(const android::StringPiece& str, const Context& context, bool unique);
+  Ref MakeRefImpl(const android::StringPiece& str, const Context& context, bool unique,
+                  Maybe<size_t> index = {});
   void ReAssignIndices();
 
   std::vector<std::unique_ptr<Entry>> strings_;
diff --git a/tools/aapt2/StringPool_test.cpp b/tools/aapt2/StringPool_test.cpp
index 9a7238b..648be7d 100644
--- a/tools/aapt2/StringPool_test.cpp
+++ b/tools/aapt2/StringPool_test.cpp
@@ -84,6 +84,24 @@
   EXPECT_THAT(ref_c.index(), Eq(2u));
 }
 
+TEST(StringPoolTest, AssignStringIndex) {
+  StringPool pool;
+
+  StringPool::Ref ref_a = pool.MakeRef("0", StringPool::Context{}, 0u);
+  StringPool::Ref ref_b = pool.MakeRef("1", StringPool::Context{}, 1u);
+  StringPool::Ref ref_c = pool.MakeRef("5", StringPool::Context{}, 5u);
+  StringPool::Ref ref_d = pool.MakeRef("2", StringPool::Context{}, 2u);
+  StringPool::Ref ref_e = pool.MakeRef("4", StringPool::Context{}, 4u);
+  StringPool::Ref ref_f = pool.MakeRef("3", StringPool::Context{}, 3u);
+
+  EXPECT_THAT(ref_a.index(), Eq(0u));
+  EXPECT_THAT(ref_b.index(), Eq(1u));
+  EXPECT_THAT(ref_d.index(), Eq(2u));
+  EXPECT_THAT(ref_f.index(), Eq(3u));
+  EXPECT_THAT(ref_e.index(), Eq(4u));
+  EXPECT_THAT(ref_c.index(), Eq(5u));
+}
+
 TEST(StringPoolTest, PruneStringsWithNoReferences) {
   StringPool pool;
 
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
index 3ea1755..4492f6b 100644
--- a/tools/aapt2/cmd/Convert.cpp
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -57,78 +57,6 @@
   Source source_;
 };
 
-bool ConvertApk(IAaptContext* context, unique_ptr<LoadedApk> apk, IApkSerializer* serializer,
-                IArchiveWriter* writer) {
-  io::IFile* manifest = apk->GetFileCollection()->FindFile(kAndroidManifestPath);
-  if (!serializer->SerializeXml(apk->GetManifest(), kAndroidManifestPath, true /*utf16*/, writer,
-                                (manifest != nullptr && manifest->WasCompressed())
-                                    ? ArchiveEntry::kCompress : 0u)) {
-    context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
-                                     << "failed to serialize AndroidManifest.xml");
-    return false;
-  }
-
-  if (apk->GetResourceTable() != nullptr) {
-    // The table might be modified by below code.
-    auto converted_table = apk->GetResourceTable();
-
-    // Resources
-    for (const auto& package : converted_table->packages) {
-      for (const auto& type : package->types) {
-        for (const auto& entry : type->entries) {
-          for (const auto& config_value : entry->values) {
-            FileReference* file = ValueCast<FileReference>(config_value->value.get());
-            if (file != nullptr) {
-              if (file->file == nullptr) {
-                context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
-                                                 << "no file associated with " << *file);
-                return false;
-              }
-
-              if (!serializer->SerializeFile(file, writer)) {
-                context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
-                                                 << "failed to serialize file " << *file->path);
-                return false;
-              }
-            } // file
-          } // config_value
-        } // entry
-      } // type
-    } // package
-
-    // Converted resource table
-    if (!serializer->SerializeTable(converted_table, writer)) {
-      context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
-                                       << "failed to serialize the resource table");
-      return false;
-    }
-  }
-
-  // Other files
-  std::unique_ptr<io::IFileCollectionIterator> iterator = apk->GetFileCollection()->Iterator();
-  while (iterator->HasNext()) {
-    io::IFile* file = iterator->Next();
-    std::string path = file->GetSource().path;
-
-    // Manifest, resource table and resources have already been taken care of.
-    if (path == kAndroidManifestPath ||
-        path == kApkResourceTablePath ||
-        path == kProtoResourceTablePath ||
-        path.find("res/") == 0) {
-      continue;
-    }
-
-    if (!io::CopyFileToArchivePreserveCompression(context, file, path, writer)) {
-      context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
-                                       << "failed to copy file " << path);
-      return false;
-    }
-  }
-
-  return true;
-}
-
-
 class BinaryApkSerializer : public IApkSerializer {
  public:
   BinaryApkSerializer(IAaptContext* context, const Source& source,
@@ -323,12 +251,97 @@
   StdErrDiagnostics diag_;
 };
 
+int Convert(IAaptContext* context, LoadedApk* apk, IArchiveWriter* output_writer,
+            ApkFormat output_format, TableFlattenerOptions& options) {
+  // Do not change the ordering of strings in the values string pool
+  options.sort_stringpool_entries = false;
+
+  unique_ptr<IApkSerializer> serializer;
+  if (output_format == ApkFormat::kBinary) {
+    serializer.reset(new BinaryApkSerializer(context, apk->GetSource(), options));
+  } else if (output_format == ApkFormat::kProto) {
+    serializer.reset(new ProtoApkSerializer(context, apk->GetSource()));
+  } else {
+    context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+                                     << "Cannot convert APK to unknown format");
+    return 1;
+  }
+
+  io::IFile* manifest = apk->GetFileCollection()->FindFile(kAndroidManifestPath);
+  if (!serializer->SerializeXml(apk->GetManifest(), kAndroidManifestPath, true /*utf16*/,
+                                output_writer, (manifest != nullptr && manifest->WasCompressed())
+                                ? ArchiveEntry::kCompress : 0u)) {
+    context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+                                     << "failed to serialize AndroidManifest.xml");
+    return 1;
+  }
+
+  if (apk->GetResourceTable() != nullptr) {
+    // The table might be modified by below code.
+    auto converted_table = apk->GetResourceTable();
+
+    // Resources
+    for (const auto& package : converted_table->packages) {
+      for (const auto& type : package->types) {
+        for (const auto& entry : type->entries) {
+          for (const auto& config_value : entry->values) {
+            FileReference* file = ValueCast<FileReference>(config_value->value.get());
+            if (file != nullptr) {
+              if (file->file == nullptr) {
+                context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+                                                 << "no file associated with " << *file);
+                return 1;
+              }
+
+              if (!serializer->SerializeFile(file, output_writer)) {
+                context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+                                                 << "failed to serialize file " << *file->path);
+                return 1;
+              }
+            } // file
+          } // config_value
+        } // entry
+      } // type
+    } // package
+
+    // Converted resource table
+    if (!serializer->SerializeTable(converted_table, output_writer)) {
+      context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+                                       << "failed to serialize the resource table");
+      return 1;
+    }
+  }
+
+  // Other files
+  std::unique_ptr<io::IFileCollectionIterator> iterator = apk->GetFileCollection()->Iterator();
+  while (iterator->HasNext()) {
+    io::IFile* file = iterator->Next();
+    std::string path = file->GetSource().path;
+
+    // Manifest, resource table and resources have already been taken care of.
+    if (path == kAndroidManifestPath ||
+        path == kApkResourceTablePath ||
+        path == kProtoResourceTablePath ||
+        path.find("res/") == 0) {
+      continue;
+    }
+
+    if (!io::CopyFileToArchivePreserveCompression(context, file, path, output_writer)) {
+      context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+                                       << "failed to copy file " << path);
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
 const char* ConvertCommand::kOutputFormatProto = "proto";
 const char* ConvertCommand::kOutputFormatBinary = "binary";
 
 int ConvertCommand::Action(const std::vector<std::string>& args) {
   if (args.size() != 1) {
-    std::cerr << "must supply a single proto APK\n";
+    std::cerr << "must supply a single APK\n";
     Usage(&std::cerr);
     return 1;
   }
@@ -341,34 +354,31 @@
     return 1;
   }
 
-  Maybe<AppInfo> app_info =
-      ExtractAppInfoFromBinaryManifest(*apk->GetManifest(), context.GetDiagnostics());
+  Maybe<AppInfo> app_info = ExtractAppInfoFromBinaryManifest(*apk->GetManifest(),
+                                                             context.GetDiagnostics());
   if (!app_info) {
     return 1;
   }
 
   context.package_ = app_info.value().package;
-
-  unique_ptr<IArchiveWriter> writer =
-      CreateZipFileArchiveWriter(context.GetDiagnostics(), output_path_);
+  unique_ptr<IArchiveWriter> writer = CreateZipFileArchiveWriter(context.GetDiagnostics(),
+                                                                 output_path_);
   if (writer == nullptr) {
     return 1;
   }
 
-  unique_ptr<IApkSerializer> serializer;
+  ApkFormat format;
   if (!output_format_ || output_format_.value() == ConvertCommand::kOutputFormatBinary) {
-
-    serializer.reset(new BinaryApkSerializer(&context, apk->GetSource(), options_));
+    format = ApkFormat::kBinary;
   } else if (output_format_.value() == ConvertCommand::kOutputFormatProto) {
-    serializer.reset(new ProtoApkSerializer(&context, apk->GetSource()));
+    format = ApkFormat::kProto;
   } else {
-    context.GetDiagnostics()->Error(DiagMessage(path)
-        << "Invalid value for flag --output-format: "
-        << output_format_.value());
+    context.GetDiagnostics()->Error(DiagMessage(path) << "Invalid value for flag --output-format: "
+                                                      << output_format_.value());
     return 1;
   }
 
-  return ConvertApk(&context, std::move(apk), serializer.get(), writer.get()) ? 0 : 1;
+  return Convert(&context, apk.get(), writer.get(), format, options_);
 }
 
 }  // namespace aapt
diff --git a/tools/aapt2/cmd/Convert.h b/tools/aapt2/cmd/Convert.h
index fcec23d..6a6719c 100644
--- a/tools/aapt2/cmd/Convert.h
+++ b/tools/aapt2/cmd/Convert.h
@@ -18,6 +18,7 @@
 #define AAPT2_CONVERT_H
 
 #include "Command.h"
+#include "LoadedApk.h"
 #include "format/binary/TableFlattener.h"
 
 namespace aapt {
@@ -49,6 +50,9 @@
   bool verbose_ = false;
 };
 
-}// namespace aapt
+int Convert(IAaptContext* context, LoadedApk* input, IArchiveWriter* output_writer,
+            ApkFormat output_format, TableFlattenerOptions& options);
+
+}  // namespace aapt
 
 #endif //AAPT2_CONVERT_H
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 8a86f63a..6c1a9ba 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -573,15 +573,17 @@
 }  // namespace
 
 bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) {
-  // We must do this before writing the resources, since the string pool IDs may change.
-  table->string_pool.Prune();
-  table->string_pool.Sort([](const StringPool::Context& a, const StringPool::Context& b) -> int {
-    int diff = util::compare(a.priority, b.priority);
-    if (diff == 0) {
-      diff = a.config.compare(b.config);
-    }
-    return diff;
-  });
+  if (options_.sort_stringpool_entries) {
+    // We must do this before writing the resources, since the string pool IDs may change.
+    table->string_pool.Prune();
+    table->string_pool.Sort([](const StringPool::Context &a, const StringPool::Context &b) -> int {
+      int diff = util::compare(a.priority, b.priority);
+      if (diff == 0) {
+        diff = a.config.compare(b.config);
+      }
+      return diff;
+    });
+  }
 
   // Write the ResTable header.
   ChunkWriter table_writer(buffer_);
diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h
index c2e1d4b..635cb21 100644
--- a/tools/aapt2/format/binary/TableFlattener.h
+++ b/tools/aapt2/format/binary/TableFlattener.h
@@ -43,6 +43,9 @@
 
   // Set of whitelisted resource names to avoid altering in key stringpool
   std::set<std::string> whitelisted_resources;
+
+  // When true, sort the entries in the values string pool by priority and configuration.
+  bool sort_stringpool_entries = true;
 };
 
 class TableFlattener : public IResourceTableConsumer {
diff --git a/wifi/java/android/net/wifi/INetworkRequestMatchCallback.aidl b/wifi/java/android/net/wifi/INetworkRequestMatchCallback.aidl
index f472a02..d14ec57 100644
--- a/wifi/java/android/net/wifi/INetworkRequestMatchCallback.aidl
+++ b/wifi/java/android/net/wifi/INetworkRequestMatchCallback.aidl
@@ -17,6 +17,7 @@
 package android.net.wifi;
 
 import android.net.wifi.INetworkRequestUserSelectionCallback;
+import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 
 /**
@@ -28,7 +29,9 @@
 {
    void onUserSelectionCallbackRegistration(in INetworkRequestUserSelectionCallback userSelectionCallback);
 
-   void onMatch(in List<WifiConfiguration> wificonfigurations);
+   void onAbort();
+
+   void onMatch(in List<ScanResult> scanResults);
 
    void onUserSelectionConnectSuccess(in WifiConfiguration wificonfiguration);
 
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 1fd68ec..3ec8a41 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -33,6 +33,7 @@
 import android.net.wifi.WifiActivityEnergyInfo;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiNetworkSuggestion;
 
 import android.os.Messenger;
 import android.os.ResultReceiver;
@@ -61,11 +62,9 @@
 
     ParceledListSlice getPrivilegedConfiguredNetworks();
 
-    WifiConfiguration getMatchingWifiConfig(in ScanResult scanResult);
+    List<WifiConfiguration> getAllMatchingWifiConfigs(in List<ScanResult> scanResult);
 
-    List<WifiConfiguration> getAllMatchingWifiConfigs(in ScanResult scanResult);
-
-    List<OsuProvider> getMatchingOsuProviders(in ScanResult scanResult);
+    List<OsuProvider> getMatchingOsuProviders(in List<ScanResult> scanResult);
 
     int addOrUpdateNetwork(in WifiConfiguration config, String packageName);
 
@@ -190,5 +189,9 @@
     void registerNetworkRequestMatchCallback(in IBinder binder, in INetworkRequestMatchCallback callback, int callbackIdentifier);
 
     void unregisterNetworkRequestMatchCallback(int callbackIdentifier);
+
+    boolean addNetworkSuggestions(in List<WifiNetworkSuggestion> networkSuggestions, in String packageName);
+
+    boolean removeNetworkSuggestions(in List<WifiNetworkSuggestion> networkSuggestions, in String packageName);
 }
 
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index b34ac26..64f8adb 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -762,6 +762,13 @@
     }
 
     /**
+     * Indicate whther the network is trusted or not. Networks are considered trusted
+     * if the user explicitly allowed this network connection.
+     * @hide
+     */
+    public boolean trusted;
+
+    /**
      * Indicates if the creator of this configuration has expressed that it
      * should be considered metered.
      *
@@ -1638,6 +1645,7 @@
         selfAdded = false;
         didSelfAdd = false;
         ephemeral = false;
+        trusted = true; // Networks are considered trusted by default.
         meteredHint = false;
         meteredOverride = METERED_OVERRIDE_NONE;
         useExternalScores = false;
@@ -1747,10 +1755,11 @@
         if (this.selfAdded) sbuf.append(" selfAdded");
         if (this.validatedInternetAccess) sbuf.append(" validatedInternetAccess");
         if (this.ephemeral) sbuf.append(" ephemeral");
+        if (this.trusted) sbuf.append(" trusted");
         if (this.meteredHint) sbuf.append(" meteredHint");
         if (this.useExternalScores) sbuf.append(" useExternalScores");
         if (this.didSelfAdd || this.selfAdded || this.validatedInternetAccess
-            || this.ephemeral || this.meteredHint || this.useExternalScores) {
+                || this.ephemeral || this.trusted || this.meteredHint || this.useExternalScores) {
             sbuf.append("\n");
         }
         if (this.meteredOverride != METERED_OVERRIDE_NONE) {
@@ -2235,6 +2244,7 @@
             validatedInternetAccess = source.validatedInternetAccess;
             isLegacyPasspointConfig = source.isLegacyPasspointConfig;
             ephemeral = source.ephemeral;
+            trusted = source.trusted;
             meteredHint = source.meteredHint;
             meteredOverride = source.meteredOverride;
             useExternalScores = source.useExternalScores;
@@ -2310,6 +2320,7 @@
         dest.writeInt(validatedInternetAccess ? 1 : 0);
         dest.writeInt(isLegacyPasspointConfig ? 1 : 0);
         dest.writeInt(ephemeral ? 1 : 0);
+        dest.writeInt(trusted ? 1 : 0);
         dest.writeInt(meteredHint ? 1 : 0);
         dest.writeInt(meteredOverride);
         dest.writeInt(useExternalScores ? 1 : 0);
@@ -2379,6 +2390,7 @@
                 config.validatedInternetAccess = in.readInt() != 0;
                 config.isLegacyPasspointConfig = in.readInt() != 0;
                 config.ephemeral = in.readInt() != 0;
+                config.trusted = in.readInt() != 0;
                 config.meteredHint = in.readInt() != 0;
                 config.meteredOverride = in.readInt();
                 config.useExternalScores = in.readInt() != 0;
diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index e37a856..e5dcef0 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -110,6 +110,8 @@
 
     private boolean mEphemeral;
 
+    private boolean mTrusted;
+
     /**
      * Running total count of lost (not ACKed) transmitted unicast data packets.
      * @hide
@@ -215,6 +217,7 @@
             mMacAddress = source.mMacAddress;
             mMeteredHint = source.mMeteredHint;
             mEphemeral = source.mEphemeral;
+            mTrusted = source.mTrusted;
             txBad = source.txBad;
             txRetries = source.txRetries;
             txSuccess = source.txSuccess;
@@ -397,6 +400,17 @@
         return mEphemeral;
     }
 
+    /** {@hide} */
+    public void setTrusted(boolean trusted) {
+        mTrusted = trusted;
+    }
+
+    /** {@hide} */
+    public boolean isTrusted() {
+        return mTrusted;
+    }
+
+
     /** @hide */
     @UnsupportedAppUsage
     public void setNetworkId(int id) {
@@ -539,6 +553,7 @@
         dest.writeString(mMacAddress);
         dest.writeInt(mMeteredHint ? 1 : 0);
         dest.writeInt(mEphemeral ? 1 : 0);
+        dest.writeInt(mTrusted ? 1 : 0);
         dest.writeInt(score);
         dest.writeLong(txSuccess);
         dest.writeDouble(txSuccessRate);
@@ -573,6 +588,7 @@
                 info.mMacAddress = in.readString();
                 info.mMeteredHint = in.readInt() != 0;
                 info.mEphemeral = in.readInt() != 0;
+                info.mTrusted = in.readInt() != 0;
                 info.score = in.readInt();
                 info.txSuccess = in.readLong();
                 info.txSuccessRate = in.readDouble();
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 7919074..7aff03c 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -25,7 +25,6 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.UnsupportedAppUsage;
-import android.app.PendingIntent;
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
 import android.net.ConnectivityManager;
@@ -871,6 +870,28 @@
     public static final String ACTION_REQUEST_DISABLE = "android.net.wifi.action.REQUEST_DISABLE";
 
     /**
+     * Directed broadcast intent action indicating that the device has connected to one of the
+     * network suggestions provided by the app. This will be sent post connection to a network
+     * which was created with {@link WifiNetworkConfigBuilder#setIsAppInteractionRequired()} flag
+     * set.
+     * <p>
+     * Note: The broadcast is sent to the app only if it holds either one of
+     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or
+     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission.
+     *
+     * @see #EXTRA_NETWORK_SUGGESTION
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION =
+            "android.net.wifi.action.WIFI_NETWORK_SUGGESTION_POST_CONNECTION";
+    /**
+     * Sent as as a part of {@link #ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION} that holds
+     * an instance of {@link WifiNetworkSuggestion} corresponding to the connected network.
+     */
+    public static final String EXTRA_NETWORK_SUGGESTION =
+            "android.net.wifi.extra.NETWORK_SUGGESTION";
+
+    /**
      * Internally used Wi-Fi lock mode representing the case were no locks are held.
      * @hide
      */
@@ -1036,7 +1057,7 @@
      * @deprecated
      * a) See {@link WifiNetworkConfigBuilder#buildNetworkSpecifier()} for new
      * mechanism to trigger connection to a Wi-Fi network.
-     * b) See {@link #addNetworkSuggestions(List, PendingIntent)},
+     * b) See {@link #addNetworkSuggestions(List)},
      * {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration
      * when auto-connecting to wifi.
      * <b>Compatibility Note:</b> For applications targeting
@@ -1073,55 +1094,43 @@
     }
 
     /**
-     * Returns a WifiConfiguration matching this ScanResult
-     *
-     * @param scanResult scanResult that represents the BSSID
-     * @return {@link WifiConfiguration} that matches this BSSID or null
-     * @throws UnsupportedOperationException if Passpoint is not enabled on the device.
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public WifiConfiguration getMatchingWifiConfig(ScanResult scanResult) {
-        try {
-            return mService.getMatchingWifiConfig(scanResult);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Return all matching WifiConfigurations for this ScanResult.
+     * Returns all matching WifiConfigurations for a given list of ScanResult.
      *
      * An empty list will be returned when no configurations are installed or if no configurations
      * match the ScanResult.
-     *
-     * @param scanResult scanResult that represents the BSSID
-     * @return A list of {@link WifiConfiguration}
+
+     * @param scanResults a list of scanResult that represents the BSSID
+     * @return A list of {@link WifiConfiguration} that can have duplicate entries.
      * @throws UnsupportedOperationException if Passpoint is not enabled on the device.
      * @hide
      */
-    public List<WifiConfiguration> getAllMatchingWifiConfigs(ScanResult scanResult) {
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+    public List<WifiConfiguration> getAllMatchingWifiConfigs(
+            @NonNull List<ScanResult> scanResults) {
         try {
-            return mService.getAllMatchingWifiConfigs(scanResult);
+            return mService.getAllMatchingWifiConfigs(scanResults);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
-
     /**
-     * Returns a list of Hotspot 2.0 OSU (Online Sign-Up) providers associated with the given AP.
+     * Returns a list of unique Hotspot 2.0 OSU (Online Sign-Up) providers associated with a given
+     * list of ScanResult.
      *
      * An empty list will be returned if no match is found.
      *
-     * @param scanResult scanResult that represents the BSSID
-     * @return list of {@link OsuProvider}
+     * @param scanResults a list of ScanResult
+     * @return A list of {@link OsuProvider} that does not contain duplicate entries.
      * @throws UnsupportedOperationException if Passpoint is not enabled on the device.
      * @hide
      */
-    public List<OsuProvider> getMatchingOsuProviders(ScanResult scanResult) {
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+    public List<OsuProvider> getMatchingOsuProviders(List<ScanResult> scanResults) {
         try {
-            return mService.getMatchingOsuProviders(scanResult);
+            return mService.getMatchingOsuProviders(scanResults);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1146,7 +1155,7 @@
      * @deprecated
      * a) See {@link WifiNetworkConfigBuilder#buildNetworkSpecifier()} for new
      * mechanism to trigger connection to a Wi-Fi network.
-     * b) See {@link #addNetworkSuggestions(List, PendingIntent)},
+     * b) See {@link #addNetworkSuggestions(List)},
      * {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration
      * when auto-connecting to wifi.
      * <b>Compatibility Note:</b> For applications targeting
@@ -1181,7 +1190,7 @@
      * @deprecated
      * a) See {@link WifiNetworkConfigBuilder#buildNetworkSpecifier()} for new
      * mechanism to trigger connection to a Wi-Fi network.
-     * b) See {@link #addNetworkSuggestions(List, PendingIntent)},
+     * b) See {@link #addNetworkSuggestions(List)},
      * {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration
      * when auto-connecting to wifi.
      * <b>Compatibility Note:</b> For applications targeting
@@ -1260,14 +1269,23 @@
                 @NonNull NetworkRequestUserSelectionCallback userSelectionCallback);
 
         /**
+         * Invoked when the active network request is aborted, either because
+         * <li> The app released the request, OR</li>
+         * <li> Request was overridden by a new request</li>
+         * This signals the end of processing for the current request and should stop the UI
+         * component. No subsequent calls from the UI component will be handled by the platform.
+         */
+        void onAbort();
+
+        /**
          * Invoked when a network request initiated by an app matches some networks in scan results.
          * This may be invoked multiple times for a single network request as the platform finds new
-         * networks in scan results.
+         * matching networks in scan results.
          *
-         * @param wifiConfigurations List of {@link WifiConfiguration} objects corresponding to the
-         *                           networks matching the request.
+         * @param scanResults List of {@link ScanResult} objects corresponding to the networks
+         *                    matching the request.
          */
-        void onMatch(@NonNull List<WifiConfiguration> wifiConfigurations);
+        void onMatch(@NonNull List<ScanResult> scanResults);
 
         /**
          * Invoked on a successful connection with the network that the user selected
@@ -1356,13 +1374,23 @@
         }
 
         @Override
-        public void onMatch(List<WifiConfiguration> wifiConfigurations) {
+        public void onAbort() {
             if (mVerboseLoggingEnabled) {
-                Log.v(TAG, "NetworkRequestMatchCallbackProxy: onMatch wificonfigurations: "
-                        + wifiConfigurations);
+                Log.v(TAG, "NetworkRequestMatchCallbackProxy: onAbort");
             }
             mHandler.post(() -> {
-                mCallback.onMatch(wifiConfigurations);
+                mCallback.onAbort();
+            });
+        }
+
+        @Override
+        public void onMatch(List<ScanResult> scanResults) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "NetworkRequestMatchCallbackProxy: onMatch scanResults: "
+                        + scanResults);
+            }
+            mHandler.post(() -> {
+                mCallback.onMatch(scanResults);
             });
         }
 
@@ -1451,13 +1479,10 @@
     /**
      * Provide a list of network suggestions to the device. See {@link WifiNetworkSuggestion}
      * for a detailed explanation of the parameters.
-     *<p>
-     * When the device decides to connect to one of the provided network suggestions, platform fires
-     * the associated {@code pendingIntent} if
+     * When the device decides to connect to one of the provided network suggestions, platform sends
+     * a directed broadcast {@link #ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION} to the app if
      * the network was created with {@link WifiNetworkConfigBuilder#setIsAppInteractionRequired()}
-     * flag set and the provided {@code pendingIntent} is non-null.
-     *<p>
-     * Registration of a non-null pending intent {@code pendingIntent} requires
+     * flag set and the app holds either one of
      * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or
      * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission.
      *<p>
@@ -1472,19 +1497,17 @@
      * suggestion back using this API.</li>
      *
      * @param networkSuggestions List of network suggestions provided by the app.
-     * @param pendingIntent Pending intent to be fired post connection for networks. These will be
-     *                      fired only when connecting to a network that was created with
-     *                      {@link WifiNetworkConfigBuilder#setIsAppInteractionRequired()} flag set.
-     *                      Pending intent must hold a foreground service, else will be rejected.
      * @return true on success, false if any of the suggestions match (See
      * {@link WifiNetworkSuggestion#equals(Object)} any previously provided suggestions by the app.
      * @throws {@link SecurityException} if the caller is missing required permissions.
      */
-    public boolean addNetworkSuggestions(
-            @NonNull List<WifiNetworkSuggestion> networkSuggestions,
-            @Nullable PendingIntent pendingIntent) {
-        // TODO(b/115504887): Implementation
-        return false;
+    @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE)
+    public boolean addNetworkSuggestions(@NonNull List<WifiNetworkSuggestion> networkSuggestions) {
+        try {
+            return mService.addNetworkSuggestions(networkSuggestions, mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
 
@@ -1500,10 +1523,15 @@
      * previously provided by the app. Any matching suggestions are removed from the device and
      * will not be considered for any further connection attempts.
      */
+    @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE)
     public boolean removeNetworkSuggestions(
             @NonNull List<WifiNetworkSuggestion> networkSuggestions) {
-        // TODO(b/115504887): Implementation
-        return false;
+        try {
+            return mService.removeNetworkSuggestions(
+                    networkSuggestions, mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -1625,7 +1653,7 @@
      * @deprecated
      * a) See {@link WifiNetworkConfigBuilder#buildNetworkSpecifier()} for new
      * mechanism to trigger connection to a Wi-Fi network.
-     * b) See {@link #addNetworkSuggestions(List, PendingIntent)},
+     * b) See {@link #addNetworkSuggestions(List)},
      * {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration
      * when auto-connecting to wifi.
      * <b>Compatibility Note:</b> For applications targeting
@@ -1669,7 +1697,7 @@
      * @deprecated
      * a) See {@link WifiNetworkConfigBuilder#buildNetworkSpecifier()} for new
      * mechanism to trigger connection to a Wi-Fi network.
-     * b) See {@link #addNetworkSuggestions(List, PendingIntent)},
+     * b) See {@link #addNetworkSuggestions(List)},
      * {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration
      * when auto-connecting to wifi.
      * <b>Compatibility Note:</b> For applications targeting
@@ -1701,7 +1729,7 @@
      * @deprecated
      * a) See {@link WifiNetworkConfigBuilder#buildNetworkSpecifier()} for new
      * mechanism to trigger connection to a Wi-Fi network.
-     * b) See {@link #addNetworkSuggestions(List, PendingIntent)},
+     * b) See {@link #addNetworkSuggestions(List)},
      * {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration
      * when auto-connecting to wifi.
      * <b>Compatibility Note:</b> For applications targeting
@@ -1724,7 +1752,7 @@
      * @deprecated
      * a) See {@link WifiNetworkConfigBuilder#buildNetworkSpecifier()} for new
      * mechanism to trigger connection to a Wi-Fi network.
-     * b) See {@link #addNetworkSuggestions(List, PendingIntent)},
+     * b) See {@link #addNetworkSuggestions(List)},
      * {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration
      * when auto-connecting to wifi.
      * <b>Compatibility Note:</b> For applications targeting
@@ -1749,7 +1777,7 @@
      * @deprecated
      * a) See {@link WifiNetworkConfigBuilder#buildNetworkSpecifier()} for new
      * mechanism to trigger connection to a Wi-Fi network.
-     * b) See {@link #addNetworkSuggestions(List, PendingIntent)},
+     * b) See {@link #addNetworkSuggestions(List)},
      * {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration
      * when auto-connecting to wifi.
      * <b>Compatibility Note:</b> For applications targeting
@@ -1774,7 +1802,7 @@
      * @deprecated
      * a) See {@link WifiNetworkConfigBuilder#buildNetworkSpecifier()} for new
      * mechanism to trigger connection to a Wi-Fi network.
-     * b) See {@link #addNetworkSuggestions(List, PendingIntent)},
+     * b) See {@link #addNetworkSuggestions(List)},
      * {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration
      * when auto-connecting to wifi.
      * <b>Compatibility Note:</b> For applications targeting
diff --git a/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java b/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java
index 55fde4c..955e040 100644
--- a/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java
+++ b/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java
@@ -99,11 +99,7 @@
         if (other instanceof WifiNetworkSpecifier) {
             return satisfiesNetworkSpecifier((WifiNetworkSpecifier) other);
         }
-        if (other instanceof WifiNetworkAgentSpecifier) {
-            throw new IllegalStateException("WifiNetworkAgentSpecifier instances should never be "
-                    + "compared");
-        }
-        return false;
+        return equals(other);
     }
 
     /**
@@ -172,7 +168,7 @@
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder("WifiNetworkAgentSpecifier [");
-        sb.append(", WifiConfiguration=").append(
+        sb.append("WifiConfiguration=").append(
                 mWifiConfiguration == null ? null : mWifiConfiguration.configKey())
                 .append(", mOriginalRequestorUid=").append(mOriginalRequestorUid)
                 .append("]");
diff --git a/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java b/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java
index 67e2189..aa8d325 100644
--- a/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java
+++ b/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java
@@ -20,7 +20,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.PendingIntent;
 import android.net.MacAddress;
 import android.net.NetworkRequest;
 import android.net.NetworkSpecifier;
@@ -38,7 +37,7 @@
  * <li>See {@link #buildNetworkSpecifier()} for creating a network specifier to use in
  * {@link NetworkRequest}.</li>
  * <li>See {@link #buildNetworkSuggestion()} for creating a network suggestion to use in
- * {@link WifiManager#addNetworkSuggestions(List, PendingIntent)}.</li>
+ * {@link WifiManager#addNetworkSuggestions(List)}.</li>
  */
 public class WifiNetworkConfigBuilder {
     private static final String MATCH_ALL_SSID_PATTERN_PATH = ".*";
@@ -242,13 +241,11 @@
     /**
      * Specifies whether the app needs to log in to a captive portal to obtain Internet access.
      * <p>
-     * This will dictate if the associated pending intent in
-     * {@link WifiManager#addNetworkSuggestions(List, PendingIntent)} will be sent after
-     * successfully connecting to the network.
+     * This will dictate if the directed broadcast
+     * {@link WifiManager#ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION} will be sent to the app
+     * after successfully connecting to the network.
      * Use this for captive portal type networks where the app needs to authenticate the user
      * before the device can access the network.
-     * This setting will be ignored if the {@code PendingIntent} used to add this network
-     * suggestion is null.
      * <p>
      * <li>Only allowed for creating network suggestion, i.e {@link #buildNetworkSuggestion()}.</li>
      * <li>If not set, defaults to false (i.e no app interaction required).</li>
@@ -481,7 +478,7 @@
 
     /**
      * Create a network suggestion object use in
-     * {@link WifiManager#addNetworkSuggestions(List, PendingIntent)}.
+     * {@link WifiManager#addNetworkSuggestions(List)}.
      * See {@link WifiNetworkSuggestion}.
      *
      * @return Instance of {@link WifiNetworkSuggestion}.
diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.aidl b/wifi/java/android/net/wifi/WifiNetworkSuggestion.aidl
new file mode 100644
index 0000000..eb6995f
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.net.wifi;
+
+parcelable WifiNetworkSuggestion;
diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
index 04b9cb5..25121e2 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
@@ -18,7 +18,6 @@
 
 import static com.android.internal.util.Preconditions.checkNotNull;
 
-import android.app.PendingIntent;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -32,7 +31,7 @@
  * of this object.
  *<p>
  * Apps can provide a list of such networks to the platform using
- * {@link WifiManager#addNetworkSuggestions(List, PendingIntent)}.
+ * {@link WifiManager#addNetworkSuggestions(List)}.
  */
 public final class WifiNetworkSuggestion implements Parcelable {
     /**
@@ -43,9 +42,6 @@
 
     /**
      * Whether app needs to log in to captive portal to obtain Internet access.
-     * This will dictate if the associated pending intent in
-     * {@link WifiManager#addNetworkSuggestions(List, PendingIntent)} needs to be sent after
-     * successfully connecting to the network.
      * @hide
      */
     public final boolean isAppInteractionRequired;
diff --git a/wifi/java/com/android/server/wifi/AbstractWifiService.java b/wifi/java/com/android/server/wifi/AbstractWifiService.java
index eede23b..04bc557 100644
--- a/wifi/java/com/android/server/wifi/AbstractWifiService.java
+++ b/wifi/java/com/android/server/wifi/AbstractWifiService.java
@@ -25,11 +25,11 @@
 import android.net.wifi.ISoftApCallback;
 import android.net.wifi.ITrafficStateCallback;
 import android.net.wifi.IWifiManager;
-import android.net.wifi.PasspointManagementObjectDefinition;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiActivityEnergyInfo;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiNetworkSuggestion;
 import android.net.wifi.hotspot2.IProvisioningCallback;
 import android.net.wifi.hotspot2.OsuProvider;
 import android.net.wifi.hotspot2.PasspointConfiguration;
@@ -37,7 +37,6 @@
 import android.os.Messenger;
 import android.os.ResultReceiver;
 import android.os.WorkSource;
-import android.util.Slog;
 
 import java.util.List;
 
@@ -83,22 +82,51 @@
         throw new UnsupportedOperationException();
     }
 
-    @Override
+    /**
+     * Returns a WifiConfiguration matching this ScanResult
+     * @param scanResult a single ScanResult Object
+     * @return
+     * @deprecated use {@link #getAllMatchingWifiConfigs(List)} instead.
+     */
+    @Deprecated
     public WifiConfiguration getMatchingWifiConfig(ScanResult scanResult) {
         throw new UnsupportedOperationException();
     }
 
-    @Override
+    /**
+     * Returns all matching WifiConfigurations for this ScanResult.
+     * @param scanResult a single ScanResult Object
+     * @return
+     * @deprecated use {@link #getAllMatchingWifiConfigs(List)} instead.
+     */
+    @Deprecated
     public List<WifiConfiguration> getAllMatchingWifiConfigs(ScanResult scanResult) {
         throw new UnsupportedOperationException();
     }
 
     @Override
+    public List<WifiConfiguration> getAllMatchingWifiConfigs(List<ScanResult> scanResults) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Returns a list of Hotspot 2.0 OSU (Online Sign-Up) providers associated with the given AP.
+     *
+     * @param scanResult a single ScanResult Object
+     * @return
+     * @deprecated use {@link #getMatchingOsuProviders(List)} instead.
+     */
+    @Deprecated
     public List<OsuProvider> getMatchingOsuProviders(ScanResult scanResult) {
         throw new UnsupportedOperationException();
     }
 
     @Override
+    public List<OsuProvider> getMatchingOsuProviders(List<ScanResult> scanResults) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public int addOrUpdateNetwork(WifiConfiguration config, String packageName) {
         throw new UnsupportedOperationException();
     }
@@ -412,4 +440,16 @@
     public void unregisterNetworkRequestMatchCallback(int callbackIdentifier) {
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public boolean addNetworkSuggestions(
+            List<WifiNetworkSuggestion> networkSuggestions, String callingPackageName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean removeNetworkSuggestions(
+            List<WifiNetworkSuggestion> networkSuggestions, String callingPackageName) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index bf6feac..8d97307 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -57,6 +57,7 @@
         String cookie = "C O.o |<IE";
         WifiConfiguration config = new WifiConfiguration();
         config.setPasspointManagementObjectTree(cookie);
+        config.trusted = false;
         MacAddress macBeforeParcel = config.getOrCreateRandomizedMacAddress();
         Parcel parcelW = Parcel.obtain();
         config.writeToParcel(parcelW, 0);
@@ -71,6 +72,7 @@
         // lacking a useful config.equals, check two fields near the end.
         assertEquals(cookie, reconfig.getMoTree());
         assertEquals(macBeforeParcel, reconfig.getOrCreateRandomizedMacAddress());
+        assertFalse(reconfig.trusted);
 
         Parcel parcelWW = Parcel.obtain();
         reconfig.writeToParcel(parcelWW, 0);
diff --git a/wifi/tests/src/android/net/wifi/WifiInfoTest.java b/wifi/tests/src/android/net/wifi/WifiInfoTest.java
index f5a8b29..f9fb062 100644
--- a/wifi/tests/src/android/net/wifi/WifiInfoTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiInfoTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 import android.os.Parcel;
 import android.support.test.filters.SmallTest;
@@ -44,6 +45,7 @@
         writeWifiInfo.txRetries = TEST_TX_RETRIES;
         writeWifiInfo.txBad = TEST_TX_BAD;
         writeWifiInfo.rxSuccess = TEST_RX_SUCCESS;
+        writeWifiInfo.setTrusted(true);
 
         Parcel parcel = Parcel.obtain();
         writeWifiInfo.writeToParcel(parcel, 0);
@@ -56,5 +58,6 @@
         assertEquals(TEST_TX_RETRIES, readWifiInfo.txRetries);
         assertEquals(TEST_TX_BAD, readWifiInfo.txBad);
         assertEquals(TEST_RX_SUCCESS, readWifiInfo.rxSuccess);
+        assertTrue(readWifiInfo.isTrusted());
     }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index ea41bb3..8fe5af9 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -35,7 +35,20 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyList;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -62,6 +75,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Unit tests for {@link android.net.wifi.WifiManager}.
@@ -1188,7 +1202,12 @@
                 mock(INetworkRequestUserSelectionCallback.class);
 
         assertEquals(0, mLooper.dispatchAll());
-        callbackCaptor.getValue().onMatch(new ArrayList<WifiConfiguration>());
+
+        callbackCaptor.getValue().onAbort();
+        assertEquals(1, mLooper.dispatchAll());
+        verify(mNetworkRequestMatchCallback).onAbort();
+
+        callbackCaptor.getValue().onMatch(new ArrayList<ScanResult>());
         assertEquals(1, mLooper.dispatchAll());
         verify(mNetworkRequestMatchCallback).onMatch(anyList());
 
@@ -1250,4 +1269,42 @@
         userSelectionCallbackCaptor.getValue().reject();
         verify(iUserSelectionCallback).reject();
     }
+
+    /**
+     * Check the call to getAllMatchingWifiConfigs calls getAllMatchingWifiConfigs of WifiService
+     * with the provided a list of ScanResult.
+     */
+    @Test
+    public void testGetAllMatchingWifiConfigs() throws Exception {
+        mWifiManager.getAllMatchingWifiConfigs(new ArrayList<>());
+
+        verify(mWifiService).getAllMatchingWifiConfigs(any(List.class));
+    }
+
+    /**
+     * Check the call to getMatchingOsuProviders calls getMatchingOsuProviders of WifiService
+     * with the provided a list of ScanResult.
+     */
+    @Test
+    public void testGetMatchingOsuProviders() throws Exception {
+        mWifiManager.getMatchingOsuProviders(new ArrayList<>());
+
+        verify(mWifiService).getMatchingOsuProviders(any(List.class));
+    }
+
+    /**
+     * Verify calls to {@link WifiManager#addNetworkSuggestions(List)} and
+     * {@link WifiManager#removeNetworkSuggestions(List)}.
+     */
+    @Test
+    public void addRemoveNetworkSuggestions() throws Exception {
+        when(mWifiService.addNetworkSuggestions(any(List.class), anyString())).thenReturn(true);
+        when(mWifiService.removeNetworkSuggestions(any(List.class), anyString())).thenReturn(true);
+
+        assertTrue(mWifiManager.addNetworkSuggestions(new ArrayList<>()));
+        verify(mWifiService).addNetworkSuggestions(anyList(), eq(TEST_PACKAGE_NAME));
+
+        assertTrue(mWifiManager.removeNetworkSuggestions(new ArrayList<>()));
+        verify(mWifiService).removeNetworkSuggestions(anyList(), eq(TEST_PACKAGE_NAME));
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java
index 1b0007c..f8ab8a2 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java
@@ -23,7 +23,6 @@
 import android.net.MacAddress;
 import android.net.MatchAllNetworkSpecifier;
 import android.net.NetworkRequest;
-import android.net.NetworkSpecifier;
 import android.os.Parcel;
 import android.os.PatternMatcher;
 import android.support.test.filters.SmallTest;
@@ -182,11 +181,10 @@
      * Validate NetworkAgentSpecifier matching with itself.
      * a) Create network agent specifier 1 for WPA_PSK network
      * b) Create network agent specifier 2 with the same params as specifier 1.
-     * c) Ensure that invoking {@link NetworkSpecifier#satisfiedBy(NetworkSpecifier)} on 2
-     * {@link WifiNetworkAgentSpecifier} throws an exception.
+     * c) Ensure that the agent specifier is satisfied by itself.
      */
-    @Test(expected = IllegalStateException.class)
-    public void testWifiNetworkAgentSpecifierDoesNotSatisifySame() {
+    @Test
+    public void testWifiNetworkAgentSpecifierDoesSatisifySame() {
         WifiNetworkAgentSpecifier specifier1 = createDefaultNetworkAgentSpecifier();
         WifiNetworkAgentSpecifier specifier2 = createDefaultNetworkAgentSpecifier();