Merge "AndroidManifest.xml: Add new network permission for managed provisioning"
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/api/current.txt b/api/current.txt
index ea6f190..b192d7f 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -5370,6 +5370,7 @@
     field public static final android.os.Parcelable.Creator<android.app.Notification.Action> CREATOR;
     field public static final int SEMANTIC_ACTION_ARCHIVE = 5; // 0x5
     field public static final int SEMANTIC_ACTION_CALL = 10; // 0xa
+    field public static final int SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION = 11; // 0xb
     field public static final int SEMANTIC_ACTION_DELETE = 4; // 0x4
     field public static final int SEMANTIC_ACTION_MARK_AS_READ = 2; // 0x2
     field public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3; // 0x3
@@ -23250,6 +23251,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();
@@ -24464,6 +24466,7 @@
   }
 
   public static final class MediaExtractor.CasInfo {
+    method public byte[] getPrivateData();
     method public android.media.MediaCas.Session getSession();
     method public int getSystemId();
   }
@@ -28980,7 +28983,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);
@@ -29000,11 +29003,14 @@
     method public boolean is5GHzBandSupported();
     method public boolean isDeviceToApRttSupported();
     method public boolean isEnhancedPowerReportingSupported();
+    method public boolean isOweSupported();
     method public boolean isP2pSupported();
     method public boolean isPreferredNetworkOffloadSupported();
     method public deprecated boolean isScanAlwaysAvailable();
     method public boolean isTdlsSupported();
     method public boolean isWifiEnabled();
+    method public boolean isWpa3SaeSupported();
+    method public boolean isWpa3SuiteBSupported();
     method public deprecated boolean pingSupplicant();
     method public deprecated boolean reassociate();
     method public deprecated boolean reconnect();
@@ -29021,9 +29027,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";
@@ -43326,6 +43334,7 @@
 
   public class PhoneStateListener {
     ctor public PhoneStateListener();
+    ctor public PhoneStateListener(java.util.concurrent.Executor);
     method public void onCallForwardingIndicatorChanged(boolean);
     method public void onCallStateChanged(int, java.lang.String);
     method public void onCellInfoChanged(java.util.List<android.telephony.CellInfo>);
@@ -43551,6 +43560,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();
@@ -43558,7 +43568,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();
@@ -43592,6 +43601,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>);
@@ -43723,6 +43733,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);
@@ -43825,6 +43836,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);
@@ -43913,6 +43929,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();
@@ -43963,6 +43980,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);
@@ -54354,6 +54372,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 {
@@ -54364,6 +54385,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 fff743b..9fe4c2d 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -138,6 +138,7 @@
     field public static final java.lang.String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE";
     field public static final java.lang.String PROVIDE_TRUST_AGENT = "android.permission.PROVIDE_TRUST_AGENT";
     field public static final java.lang.String QUERY_TIME_ZONE_RULES = "android.permission.QUERY_TIME_ZONE_RULES";
+    field public static final java.lang.String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
     field public static final java.lang.String READ_CONTENT_RATING_SYSTEMS = "android.permission.READ_CONTENT_RATING_SYSTEMS";
     field public static final java.lang.String READ_DREAM_STATE = "android.permission.READ_DREAM_STATE";
     field public static final java.lang.String READ_INSTALL_SESSIONS = "android.permission.READ_INSTALL_SESSIONS";
@@ -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);
@@ -4546,10 +4548,6 @@
   public final class Settings {
     field public static final java.lang.String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS";
     field public static final java.lang.String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS";
-    field public static final int USER_SETUP_PERSONALIZATION_COMPLETE = 10; // 0xa
-    field public static final int USER_SETUP_PERSONALIZATION_NOT_STARTED = 0; // 0x0
-    field public static final int USER_SETUP_PERSONALIZATION_PAUSED = 2; // 0x2
-    field public static final int USER_SETUP_PERSONALIZATION_STARTED = 1; // 0x1
   }
 
   public static final class Settings.Global extends android.provider.Settings.NameValueTable {
@@ -4593,6 +4591,10 @@
     field public static final java.lang.String LOCK_SCREEN_SHOW_NOTIFICATIONS = "lock_screen_show_notifications";
     field public static final java.lang.String MANUAL_RINGER_TOGGLE_COUNT = "manual_ringer_toggle_count";
     field public static final java.lang.String USER_SETUP_COMPLETE = "user_setup_complete";
+    field public static final int USER_SETUP_PERSONALIZATION_COMPLETE = 10; // 0xa
+    field public static final int USER_SETUP_PERSONALIZATION_NOT_STARTED = 0; // 0x0
+    field public static final int USER_SETUP_PERSONALIZATION_PAUSED = 2; // 0x2
+    field public static final int USER_SETUP_PERSONALIZATION_STARTED = 1; // 0x1
     field public static final java.lang.String USER_SETUP_PERSONALIZATION_STATE = "user_setup_personalization_state";
     field public static final java.lang.String VOLUME_HUSH_GESTURE = "volume_hush_gesture";
   }
@@ -4784,6 +4786,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 {
@@ -5462,6 +5474,7 @@
     method public static android.os.PersistableBundle getDefaultConfig();
     method public void overrideConfig(int, android.os.PersistableBundle);
     method public void updateConfigForPhoneId(int, java.lang.String);
+    field public static final java.lang.String KEY_CARRIER_SETUP_APP_STRING = "carrier_setup_app_string";
     field public static final java.lang.String KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING = "config_plans_package_override_string";
   }
 
@@ -5536,8 +5549,10 @@
   public class PhoneStateListener {
     method public void onRadioPowerStateChanged(int);
     method public void onSrvccStateChanged(int);
+    method public void onVoiceActivationStateChanged(int);
     field public static final int LISTEN_RADIO_POWER_STATE_CHANGED = 8388608; // 0x800000
     field public static final int LISTEN_SRVCC_STATE_CHANGED = 16384; // 0x4000
+    field public static final int LISTEN_VOICE_ACTIVATION_STATE = 131072; // 0x20000
   }
 
   public class ServiceState implements android.os.Parcelable {
@@ -5694,6 +5709,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);
@@ -6601,6 +6617,22 @@
     method public void receiveSessionModifyResponse(int, android.telecom.VideoProfile, android.telecom.VideoProfile);
   }
 
+  public class ProvisioningManager {
+    method public static android.telephony.ims.ProvisioningManager createForSubscriptionId(android.content.Context, int);
+    method public int getProvisioningIntValue(int);
+    method public java.lang.String getProvisioningStringValue(int);
+    method public void registerProvisioningChangedCallback(java.util.concurrent.Executor, android.telephony.ims.ProvisioningManager.Callback);
+    method public int setProvisioningIntValue(int, int);
+    method public int setProvisioningStringValue(int, java.lang.String);
+    method public void unregisterProvisioningChangedCallback(android.telephony.ims.ProvisioningManager.Callback);
+  }
+
+  public static class ProvisioningManager.Callback {
+    ctor public ProvisioningManager.Callback();
+    method public void onProvisioningIntChanged(int, int);
+    method public void onProvisioningStringChanged(int, java.lang.String);
+  }
+
 }
 
 package android.telephony.ims.feature {
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/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index eb498f5..a981997 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -370,11 +370,9 @@
     // This skips the uid map if it's an empty config.
     if (it->second->getNumMetrics() > 0) {
         uint64_t uidMapToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_ID_UID_MAP);
-        if (it->second->hashStringInReport()) {
-            mUidMap->appendUidMap(dumpTimeStampNs, key, &str_set, proto);
-        } else {
-            mUidMap->appendUidMap(dumpTimeStampNs, key, nullptr, proto);
-        }
+        mUidMap->appendUidMap(
+                dumpTimeStampNs, key, it->second->hashStringInReport() ? &str_set : nullptr,
+                it->second->versionStringsInReport(), it->second->installerInReport(), proto);
         proto->end(uidMapToken);
     }
 
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 27685fc..7fa05be 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -787,21 +787,24 @@
 }
 
 Status StatsService::informAllUidData(const vector<int32_t>& uid, const vector<int64_t>& version,
-                                      const vector<String16>& app) {
+                                      const vector<String16>& version_string,
+                                      const vector<String16>& app,
+                                      const vector<String16>& installer) {
     ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::informAllUidData was called");
-    mUidMap->updateMap(getElapsedRealtimeNs(), uid, version, app);
+    mUidMap->updateMap(getElapsedRealtimeNs(), uid, version, version_string, app, installer);
     VLOG("StatsService::informAllUidData succeeded");
 
     return Status::ok();
 }
 
-Status StatsService::informOnePackage(const String16& app, int32_t uid, int64_t version) {
+Status StatsService::informOnePackage(const String16& app, int32_t uid, int64_t version,
+                                      const String16& version_string, const String16& installer) {
     ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::informOnePackage was called");
-    mUidMap->updateApp(getElapsedRealtimeNs(), app, uid, version);
+    mUidMap->updateApp(getElapsedRealtimeNs(), app, uid, version, version_string, installer);
     return Status::ok();
 }
 
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 4a5f05f..cd4d601 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -73,8 +73,10 @@
     virtual Status informAlarmForSubscriberTriggeringFired();
 
     virtual Status informAllUidData(const vector<int32_t>& uid, const vector<int64_t>& version,
-                                    const vector<String16>& app);
-    virtual Status informOnePackage(const String16& app, int32_t uid, int64_t version);
+                                    const vector<String16>& version_string,
+                                    const vector<String16>& app, const vector<String16>& installer);
+    virtual Status informOnePackage(const String16& app, int32_t uid, int64_t version,
+                                    const String16& version_string, const String16& installer);
     virtual Status informOnePackageRemoved(const String16& app, int32_t uid);
     virtual Status informDeviceShutdown();
 
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 53d9673..9757f32 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;
@@ -148,6 +149,13 @@
         UserRestrictionChanged user_restriction_changed = 96;
         SettingsUIChanged settings_ui_changed = 97;
         ConnectivityStateChanged connectivity_state_changed = 98;
+        // TODO: service state change is very noisy shortly after boot, as well
+        // as at other transitions - coming out of doze, device plugged in, etc.
+        // 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.
@@ -192,6 +200,9 @@
         NativeProcessMemoryState native_process_memory_state = 10036;
         CpuTimePerThreadFreq cpu_time_per_thread_freq = 10037;
         OnDevicePowerMeasurement on_device_power_measurement = 10038;
+        DeviceCalculatedPowerUse device_calculated_power_use = 10039;
+        DeviceCalculatedPowerBlameUid device_calculated_power_blame_uid = 10040;
+        DeviceCalculatedPowerBlameOther device_calculated_power_blame_other = 10041;
     }
 
     // DO NOT USE field numbers above 100,000 in AOSP.
@@ -486,7 +497,6 @@
     optional State state = 3;
 }
 
-
 /**
  * Logs when GPS state changes.
  *
@@ -503,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.
@@ -2131,20 +2151,92 @@
 }
 
 /*
+ * 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
  */
 message ConnectivityStateChanged {
-    // Id of the network.
-    optional int32 net_id = 1;
+  // Id of the network.
+  optional int32 net_id = 1;
+
+  enum State {
+    UNKNOWN = 0;
+    CONNECTED = 1;
+    DISCONNECTED = 2;
+  }
+  // Connected state of a network.
+  optional State state = 2;
+}
+
+/**
+ * Logs when a service starts and stops.
+ * Logged from:
+ *   services/core/java/com/android/server/am/ActiveServices.java
+ */
+message ServiceStateChanged {
+
+    optional int32 uid = 1 [(is_uid) = true];
+
+    optional string package_name = 2;
+
+    optional string service_name = 3;
 
     enum State {
-        UNKNOWN = 0;
-        CONNECTED = 1;
-        DISCONNECTED = 2;
+        START = 1;
+        STOP = 2;
     }
-    // Connected state of a network.
-    optional State state = 2;
+
+    optional State state = 4;
+}
+
+/**
+ * Logs when a service is launched.
+ * Logged from:
+ *   services/core/java/com/android/server/am/ActiveServices.java
+ */
+message ServiceLaunchReported {
+
+    optional int32 uid = 1 [(is_uid) = true];
+
+    optional string package_name = 2;
+
+    optional string service_name = 3;
 }
 
 //////////////////////////////////////////////////////////////////////
@@ -3198,3 +3290,63 @@
     // Time spent in frequency in milliseconds, since thread start.
     optional uint32 time_millis = 7;
 }
+
+/**
+ * Pulls on-device BatteryStats power use calculations for the overall device.
+ */
+message DeviceCalculatedPowerUse {
+    // Power used by the device in mAh, as computed by BatteryStats, since BatteryStats last reset
+    // (i.e. roughly since device was last significantly charged).
+    // Currently, this is BatteryStatsHelper.getComputedPower() (not getTotalPower()).
+    optional float computed_power_milli_amp_hours = 1;
+}
+
+/**
+ * Pulls on-device BatteryStats power use calculations broken down by uid.
+ * This atom should be complemented by DeviceCalculatedPowerBlameOther, which contains the power use
+ * that is attributed to non-uid items. They must all be included to get the total power use.
+ */
+message DeviceCalculatedPowerBlameUid {
+    // Uid being blamed. Note: isolated uids have already been mapped to host uid.
+    optional int32 uid = 1 [(is_uid) = true];
+
+    // Power used by this uid in mAh, as computed by BatteryStats, since BatteryStats last reset
+    // (i.e. roughly since device was last significantly charged).
+    optional float power_milli_amp_hours = 2;
+}
+
+/**
+ * Pulls on-device BatteryStats power use calculations that are not due to a uid, broken down by
+ * drain type.
+ * This atom should be complemented by DeviceCalculatedPowerBlameUid, which contains the blame that
+ * is attributed uids. They must all be included to get the total power use.
+ */
+message DeviceCalculatedPowerBlameOther {
+    // The type of item whose power use is being reported.
+    enum DrainType {
+        AMBIENT_DISPLAY = 0;
+        // reserved 1; reserved "APP"; // Logged instead in DeviceCalculatedPowerBlameUid.
+        BLUETOOTH = 2;
+        CAMERA = 3;
+        // Cell-standby
+        CELL = 4;
+        FLASHLIGHT = 5;
+        IDLE = 6;
+        MEMORY = 7;
+        // Amount that total computed drain exceeded the drain estimated using the
+        // battery level changes and capacity.
+        OVERCOUNTED = 8;
+        PHONE = 9;
+        SCREEN = 10;
+        // Amount that total computed drain was below the drain estimated using the
+        // battery level changes and capacity.
+        UNACCOUNTED = 11;
+        // reserved 12; reserved "USER"; // Entire drain for a user. This is NOT supported.
+        WIFI = 13;
+    }
+    optional DrainType drain_type = 1;
+
+    // Power used by this item in mAh, as computed by BatteryStats, since BatteryStats last reset
+    // (i.e. roughly since device was last significantly charged).
+    optional float power_milli_amp_hours = 2;
+}
\ No newline at end of file
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 8378ae1..0e131cb 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -243,6 +243,20 @@
           {2, 3, 4, 5, 6},
           1 * NS_PER_SEC,
           new StatsCompanionServicePuller(android::util::CPU_TIME_PER_THREAD_FREQ)}},
+        // DeviceCalculatedPowerUse.
+        {android::util::DEVICE_CALCULATED_POWER_USE,
+         {{}, {}, 1 * NS_PER_SEC,
+          new StatsCompanionServicePuller(android::util::DEVICE_CALCULATED_POWER_USE)}},
+        // DeviceCalculatedPowerBlameUid.
+        {android::util::DEVICE_CALCULATED_POWER_BLAME_UID,
+         {{}, {}, // BatteryStats already merged isolated with host ids so it's unnecessary here.
+          1 * NS_PER_SEC,
+          new StatsCompanionServicePuller(android::util::DEVICE_CALCULATED_POWER_BLAME_UID)}},
+        // DeviceCalculatedPowerBlameOther.
+        {android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER,
+         {{}, {},
+          1 * NS_PER_SEC,
+          new StatsCompanionServicePuller(android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER)}},
 };
 
 StatsPullerManager::StatsPullerManager() : mNextPullTimeNs(NO_ALARM_UPDATE) {
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index febb922..625294c 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -156,9 +156,6 @@
             FieldValue(Field(mTagId, getSimpleField(1)), Value(speakerImpedance.speakerLocation)));
     mValues.push_back(
             FieldValue(Field(mTagId, getSimpleField(2)), Value(speakerImpedance.milliOhms)));
-    if (!mValues.empty()) {
-        mValues.back().mField.decorateLastPos(1);
-    }
 }
 
 LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
@@ -173,9 +170,6 @@
             FieldValue(Field(mTagId, getSimpleField(2)), Value(hardwareFailed.hardwareLocation)));
     mValues.push_back(
             FieldValue(Field(mTagId, getSimpleField(3)), Value(int32_t(hardwareFailed.errorCode))));
-    if (!mValues.empty()) {
-        mValues.back().mField.decorateLastPos(1);
-    }
 }
 
 LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
@@ -190,9 +184,6 @@
             FieldValue(Field(mTagId, getSimpleField(2)), Value(physicalDropDetected.accelPeak)));
     mValues.push_back(FieldValue(Field(mTagId, getSimpleField(3)),
                                  Value(physicalDropDetected.freefallDuration)));
-    if (!mValues.empty()) {
-        mValues.back().mField.decorateLastPos(1);
-    }
 }
 
 LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
@@ -205,10 +196,6 @@
         mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 1)),
                                      Value(chargeCycles.cycleBucket[i])));
     }
-
-    if (!mValues.empty()) {
-        mValues.back().mField.decorateLastPos(1);
-    }
 }
 
 LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
@@ -231,10 +218,6 @@
                                  Value(batteryHealthSnapshotArgs.resistanceMicroOhm)));
     mValues.push_back(FieldValue(Field(mTagId, getSimpleField(7)),
                                  Value(batteryHealthSnapshotArgs.levelPercent)));
-
-    if (!mValues.empty()) {
-        mValues.back().mField.decorateLastPos(1);
-    }
 }
 
 LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, const SlowIo& slowIo) {
@@ -247,10 +230,6 @@
             FieldValue(Field(mTagId, getSimpleField(1)), Value(int32_t(slowIo.operation))));
     pos[0]++;
     mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)), Value(slowIo.count)));
-
-    if (!mValues.empty()) {
-        mValues.back().mField.decorateLastPos(1);
-    }
 }
 
 LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
@@ -261,10 +240,6 @@
 
     mValues.push_back(FieldValue(Field(mTagId, getSimpleField(1)),
                                  Value(batteryCausedShutdown.voltageMicroV)));
-
-    if (!mValues.empty()) {
-        mValues.back().mField.decorateLastPos(1);
-    }
 }
 
 LogEvent::LogEvent(int32_t tagId, int64_t timestampNs) : LogEvent(tagId, timestampNs, 0) {}
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 4244d5b..ac34f47 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -77,6 +77,8 @@
             mActivationAtomTrackerToMetricMap, mMetricIndexesWithActivation, mNoReportMetricIds);
 
     mHashStringsInReport = config.hash_strings_in_metric_report();
+    mVersionStringsInReport = config.version_strings_in_metric_report();
+    mInstallerInReport = config.installer_in_metric_report();
 
     if (config.allowed_log_source_size() == 0) {
         mConfigValid = false;
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index a4672b6..a31efbd 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -83,6 +83,14 @@
         return mHashStringsInReport;
     };
 
+    inline bool versionStringsInReport() const {
+        return mVersionStringsInReport;
+    };
+
+    inline bool installerInReport() const {
+        return mInstallerInReport;
+    };
+
     void refreshTtl(const int64_t currentTimestampNs) {
         if (mTtlNs > 0) {
             mTtlEndNs = currentTimestampNs + mTtlNs;
@@ -126,6 +134,8 @@
     bool mConfigValid = false;
 
     bool mHashStringsInReport = false;
+    bool mVersionStringsInReport = false;
+    bool mInstallerInReport = false;
 
     const int64_t mTtlNs;
     int64_t mTtlEndNs;
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
index 37a0067..59f3f04 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -49,6 +49,10 @@
 const int FIELD_ID_SNAPSHOT_PACKAGE_UID = 3;
 const int FIELD_ID_SNAPSHOT_PACKAGE_DELETED = 4;
 const int FIELD_ID_SNAPSHOT_PACKAGE_NAME_HASH = 5;
+const int FIELD_ID_SNAPSHOT_PACKAGE_VERSION_STRING = 6;
+const int FIELD_ID_SNAPSHOT_PACKAGE_VERSION_STRING_HASH = 7;
+const int FIELD_ID_SNAPSHOT_PACKAGE_INSTALLER = 8;
+const int FIELD_ID_SNAPSHOT_PACKAGE_INSTALLER_HASH = 9;
 const int FIELD_ID_SNAPSHOT_TIMESTAMP = 1;
 const int FIELD_ID_SNAPSHOT_PACKAGE_INFO = 2;
 const int FIELD_ID_SNAPSHOTS = 1;
@@ -60,6 +64,10 @@
 const int FIELD_ID_CHANGE_NEW_VERSION = 5;
 const int FIELD_ID_CHANGE_PREV_VERSION = 6;
 const int FIELD_ID_CHANGE_PACKAGE_HASH = 7;
+const int FIELD_ID_CHANGE_NEW_VERSION_STRING = 8;
+const int FIELD_ID_CHANGE_PREV_VERSION_STRING = 9;
+const int FIELD_ID_CHANGE_NEW_VERSION_STRING_HASH = 10;
+const int FIELD_ID_CHANGE_PREV_VERSION_STRING_HASH = 11;
 
 UidMap::UidMap() : mBytesUsed(0) {}
 
@@ -104,7 +112,8 @@
 }
 
 void UidMap::updateMap(const int64_t& timestamp, const vector<int32_t>& uid,
-                       const vector<int64_t>& versionCode, const vector<String16>& packageName) {
+                       const vector<int64_t>& versionCode, const vector<String16>& versionString,
+                       const vector<String16>& packageName, const vector<String16>& installer) {
     vector<wp<PackageInfoListener>> broadcastList;
     {
         lock_guard<mutex> lock(mMutex);  // Exclusively lock for updates.
@@ -121,7 +130,9 @@
         mMap.clear();
         for (size_t j = 0; j < uid.size(); j++) {
             string package = string(String8(packageName[j]).string());
-            mMap[std::make_pair(uid[j], package)] = AppData(versionCode[j]);
+            mMap[std::make_pair(uid[j], package)] =
+                    AppData(versionCode[j], string(String8(versionString[j]).string()),
+                            string(String8(installer[j]).string()));
         }
 
         for (const auto& kv : deletedApps) {
@@ -150,23 +161,30 @@
 }
 
 void UidMap::updateApp(const int64_t& timestamp, const String16& app_16, const int32_t& uid,
-                       const int64_t& versionCode) {
+                       const int64_t& versionCode, const String16& versionString,
+                       const String16& installer) {
     vector<wp<PackageInfoListener>> broadcastList;
     string appName = string(String8(app_16).string());
     {
         lock_guard<mutex> lock(mMutex);
         int32_t prevVersion = 0;
+        string prevVersionString = "";
+        string newVersionString = string(String8(versionString).string());
         bool found = false;
         auto it = mMap.find(std::make_pair(uid, appName));
         if (it != mMap.end()) {
             found = true;
             prevVersion = it->second.versionCode;
+            prevVersionString = it->second.versionString;
             it->second.versionCode = versionCode;
+            it->second.versionString = newVersionString;
+            it->second.installer = string(String8(installer).string());
             it->second.deleted = false;
         }
         if (!found) {
             // Otherwise, we need to add an app at this uid.
-            mMap[std::make_pair(uid, appName)] = AppData(versionCode);
+            mMap[std::make_pair(uid, appName)] =
+                    AppData(versionCode, newVersionString, string(String8(installer).string()));
         } else {
             // Only notify the listeners if this is an app upgrade. If this app is being installed
             // for the first time, then we don't notify the listeners.
@@ -174,7 +192,8 @@
             // app after deletion.
             getListenerListCopyLocked(&broadcastList);
         }
-        mChanges.emplace_back(false, timestamp, appName, uid, versionCode, prevVersion);
+        mChanges.emplace_back(false, timestamp, appName, uid, versionCode, newVersionString,
+                              prevVersion, prevVersionString);
         mBytesUsed += kBytesChangeRecord;
         ensureBytesUsedBelowLimit();
         StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
@@ -226,10 +245,12 @@
         lock_guard<mutex> lock(mMutex);
 
         int64_t prevVersion = 0;
+        string prevVersionString = "";
         auto key = std::make_pair(uid, app);
         auto it = mMap.find(key);
         if (it != mMap.end() && !it->second.deleted) {
             prevVersion = it->second.versionCode;
+            prevVersionString = it->second.versionString;
             it->second.deleted = true;
             mDeletedApps.push_back(key);
         }
@@ -240,7 +261,7 @@
             mMap.erase(oldest);
             StatsdStats::getInstance().noteUidMapAppDeletionDropped();
         }
-        mChanges.emplace_back(true, timestamp, app, uid, 0, prevVersion);
+        mChanges.emplace_back(true, timestamp, app, uid, 0, "", prevVersion, prevVersionString);
         mBytesUsed += kBytesChangeRecord;
         ensureBytesUsedBelowLimit();
         StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
@@ -315,8 +336,9 @@
     return mBytesUsed;
 }
 
-void UidMap::appendUidMap(const int64_t& timestamp, const ConfigKey& key,
-                          std::set<string> *str_set, ProtoOutputStream* proto) {
+void UidMap::appendUidMap(const int64_t& timestamp, const ConfigKey& key, std::set<string>* str_set,
+                          bool includeVersionStrings, bool includeInstaller,
+                          ProtoOutputStream* proto) {
     lock_guard<mutex> lock(mMutex);  // Lock for updates
 
     for (const ChangeRecord& record : mChanges) {
@@ -330,8 +352,22 @@
                 str_set->insert(record.package);
                 proto->write(FIELD_TYPE_UINT64 | FIELD_ID_CHANGE_PACKAGE_HASH,
                              (long long)Hash64(record.package));
+                if (includeVersionStrings) {
+                    str_set->insert(record.versionString);
+                    proto->write(FIELD_TYPE_UINT64 | FIELD_ID_CHANGE_NEW_VERSION_STRING_HASH,
+                                 (long long)Hash64(record.versionString));
+                    str_set->insert(record.prevVersionString);
+                    proto->write(FIELD_TYPE_UINT64 | FIELD_ID_CHANGE_PREV_VERSION_STRING_HASH,
+                                 (long long)Hash64(record.prevVersionString));
+                }
             } else {
                 proto->write(FIELD_TYPE_STRING | FIELD_ID_CHANGE_PACKAGE, record.package);
+                if (includeVersionStrings) {
+                    proto->write(FIELD_TYPE_STRING | FIELD_ID_CHANGE_NEW_VERSION_STRING,
+                                 record.versionString);
+                    proto->write(FIELD_TYPE_STRING | FIELD_ID_CHANGE_PREV_VERSION_STRING,
+                                 record.prevVersionString);
+                }
             }
 
             proto->write(FIELD_TYPE_INT32 | FIELD_ID_CHANGE_UID, (int)record.uid);
@@ -354,8 +390,26 @@
             str_set->insert(kv.first.second);
             proto->write(FIELD_TYPE_UINT64 | FIELD_ID_SNAPSHOT_PACKAGE_NAME_HASH,
                          (long long)Hash64(kv.first.second));
+            if (includeVersionStrings) {
+                str_set->insert(kv.second.versionString);
+                proto->write(FIELD_TYPE_UINT64 | FIELD_ID_SNAPSHOT_PACKAGE_VERSION_STRING_HASH,
+                             (long long)Hash64(kv.second.versionString));
+            }
+            if (includeInstaller) {
+                str_set->insert(kv.second.installer);
+                proto->write(FIELD_TYPE_UINT64 | FIELD_ID_SNAPSHOT_PACKAGE_INSTALLER_HASH,
+                             (long long)Hash64(kv.second.installer));
+            }
         } else {
             proto->write(FIELD_TYPE_STRING | FIELD_ID_SNAPSHOT_PACKAGE_NAME, kv.first.second);
+            if (includeVersionStrings) {
+                proto->write(FIELD_TYPE_STRING | FIELD_ID_SNAPSHOT_PACKAGE_VERSION_STRING,
+                             kv.second.versionString);
+            }
+            if (includeInstaller) {
+                proto->write(FIELD_TYPE_STRING | FIELD_ID_SNAPSHOT_PACKAGE_INSTALLER,
+                             kv.second.installer);
+            }
         }
 
         proto->write(FIELD_TYPE_INT64 | FIELD_ID_SNAPSHOT_PACKAGE_VERSION,
@@ -391,8 +445,9 @@
 
     for (const auto& kv : mMap) {
         if (!kv.second.deleted) {
-            dprintf(out, "%s, v%" PRId64 " (%i)\n", kv.first.second.c_str(), kv.second.versionCode,
-                    kv.first.first);
+            dprintf(out, "%s, v%" PRId64 ", %s, %s (%i)\n", kv.first.second.c_str(),
+                    kv.second.versionCode, kv.second.versionString.c_str(),
+                    kv.second.installer.c_str(), kv.first.first);
         }
     }
 }
diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h
index 4598369..75ff507 100644
--- a/cmds/statsd/src/packages/UidMap.h
+++ b/cmds/statsd/src/packages/UidMap.h
@@ -44,12 +44,16 @@
 
 struct AppData {
     int64_t versionCode;
+    string versionString;
+    string installer;
     bool deleted;
 
     // Empty constructor needed for unordered map.
     AppData() {
     }
-    AppData(const int64_t v) : versionCode(v), deleted(false){};
+
+    AppData(const int64_t v, const string& versionString, const string& installer)
+        : versionCode(v), versionString(versionString), installer(installer), deleted(false){};
 };
 
 // When calling appendUidMap, we retrieve all the ChangeRecords since the last
@@ -61,15 +65,20 @@
     const int32_t uid;
     const int64_t version;
     const int64_t prevVersion;
+    const string versionString;
+    const string prevVersionString;
 
     ChangeRecord(const bool isDeletion, const int64_t timestampNs, const string& package,
-                 const int32_t uid, const int64_t version, const int64_t prevVersion)
+                 const int32_t uid, const int64_t version, const string versionString,
+                 const int64_t prevVersion, const string prevVersionString)
         : deletion(isDeletion),
           timestampNs(timestampNs),
           package(package),
           uid(uid),
           version(version),
-          prevVersion(prevVersion) {
+          prevVersion(prevVersion),
+          versionString(versionString),
+          prevVersionString(prevVersionString) {
     }
 };
 
@@ -87,10 +96,12 @@
      * tuple, ie. uid[j] corresponds to packageName[j] with versionCode[j].
      */
     void updateMap(const int64_t& timestamp, const vector<int32_t>& uid,
-                   const vector<int64_t>& versionCode, const vector<String16>& packageName);
+                   const vector<int64_t>& versionCode, const vector<String16>& versionString,
+                   const vector<String16>& packageName, const vector<String16>& installer);
 
     void updateApp(const int64_t& timestamp, const String16& packageName, const int32_t& uid,
-                   const int64_t& versionCode);
+                   const int64_t& versionCode, const String16& versionString,
+                   const String16& installer);
     void removeApp(const int64_t& timestamp, const String16& packageName, const int32_t& uid);
 
     // Returns true if the given uid contains the specified app (eg. com.google.android.gms).
@@ -127,8 +138,9 @@
     // Gets all snapshots and changes that have occurred since the last output.
     // If every config key has received a change or snapshot record, then this
     // record is deleted.
-    void appendUidMap(const int64_t& timestamp, const ConfigKey& key,
-                      std::set<string> *str_set, util::ProtoOutputStream* proto);
+    void appendUidMap(const int64_t& timestamp, const ConfigKey& key, std::set<string>* str_set,
+                      bool includeVersionStrings, bool includeInstaller,
+                      util::ProtoOutputStream* proto);
 
     // Forces the output to be cleared. We still generate a snapshot based on the current state.
     // This results in extra data uploaded but helps us reconstruct the uid mapping on the server
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index 5d0f3d1..32ee5af 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -233,6 +233,14 @@
             optional bool deleted = 4;
 
             optional uint64 name_hash = 5;
+
+            optional string version_string = 6;
+
+            optional uint64 version_string_hash = 7;
+
+            optional string installer = 8;
+
+            optional uint64 installer_hash = 9;
         }
         optional int64 elapsed_timestamp_nanos = 1;
 
@@ -250,6 +258,10 @@
         optional int64 new_version = 5;
         optional int64 prev_version = 6;
         optional uint64 app_hash = 7;
+        optional string new_version_string = 8;
+        optional string prev_version_string = 9;
+        optional uint64 new_version_string_hash = 10;
+        optional uint64 prev_version_string_hash = 11;
     }
     repeated Change changes = 2;
 }
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index aa789c7..f955df2 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -409,6 +409,10 @@
 
   repeated MetricActivation metric_activation = 17;
 
+  optional bool version_strings_in_metric_report = 18;
+
+  optional bool installer_in_metric_report = 19;
+
   // Field number 1000 is reserved for later use.
   reserved 1000;
 }
diff --git a/cmds/statsd/tests/LogEntryMatcher_test.cpp b/cmds/statsd/tests/LogEntryMatcher_test.cpp
index 4c6671d..2b9528f 100644
--- a/cmds/statsd/tests/LogEntryMatcher_test.cpp
+++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp
@@ -148,8 +148,12 @@
 
     uidMap.updateMap(
             1, {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */,
+            {android::String16("v1"), android::String16("v1"), android::String16("v2"),
+             android::String16("v1"), android::String16("v2")},
             {android::String16("pkg0"), android::String16("pkg1"), android::String16("pkg1"),
-             android::String16("Pkg2"), android::String16("PkG3")} /* package name list */);
+             android::String16("Pkg2"), android::String16("PkG3")} /* package name list */,
+            {android::String16(""), android::String16(""), android::String16(""),
+             android::String16(""), android::String16("")});
 
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
@@ -297,8 +301,12 @@
     UidMap uidMap;
     uidMap.updateMap(
             1, {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */,
+            {android::String16("v1"), android::String16("v1"), android::String16("v2"),
+             android::String16("v1"), android::String16("v2")},
             {android::String16("pkg0"), android::String16("pkg1"), android::String16("pkg1"),
-             android::String16("Pkg2"), android::String16("PkG3")} /* package name list */);
+             android::String16("Pkg2"), android::String16("PkG3")} /* package name list */,
+            {android::String16(""), android::String16(""), android::String16(""),
+             android::String16(""), android::String16("")});
 
     AttributionNodeInternal attribution_node1;
     attribution_node1.set_uid(1111);
@@ -372,8 +380,12 @@
     UidMap uidMap;
     uidMap.updateMap(
             1, {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */,
+            {android::String16("v1"), android::String16("v1"), android::String16("v2"),
+             android::String16("v1"), android::String16("v2")},
             {android::String16("pkg0"), android::String16("pkg1"), android::String16("pkg1"),
-             android::String16("Pkg2"), android::String16("PkG3")} /* package name list */);
+             android::String16("Pkg2"), android::String16("PkG3")} /* package name list */,
+            {android::String16(""), android::String16(""), android::String16(""),
+             android::String16(""), android::String16("")});
 
     AttributionNodeInternal attribution_node1;
     attribution_node1.set_uid(1067);
diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp
index 8864252..355df29 100644
--- a/cmds/statsd/tests/StatsLogProcessor_test.cpp
+++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp
@@ -153,7 +153,8 @@
     // Setup simple config key corresponding to empty config.
     sp<UidMap> m = new UidMap();
     sp<StatsPullerManager> pullerManager = new StatsPullerManager();
-    m->updateMap(1, {1, 2}, {1, 2}, {String16("p1"), String16("p2")});
+    m->updateMap(1, {1, 2}, {1, 2}, {String16("v1"), String16("v2")},
+                 {String16("p1"), String16("p2")}, {String16(""), String16("")});
     sp<AlarmMonitor> anomalyAlarmMonitor;
     sp<AlarmMonitor> subscriberAlarmMonitor;
     int broadcastCount = 0;
@@ -182,7 +183,8 @@
     // Setup simple config key corresponding to empty config.
     sp<UidMap> m = new UidMap();
     sp<StatsPullerManager> pullerManager = new StatsPullerManager();
-    m->updateMap(1, {1, 2}, {1, 2}, {String16("p1"), String16("p2")});
+    m->updateMap(1, {1, 2}, {1, 2}, {String16("v1"), String16("v2")},
+                 {String16("p1"), String16("p2")}, {String16(""), String16("")});
     sp<AlarmMonitor> anomalyAlarmMonitor;
     sp<AlarmMonitor> subscriberAlarmMonitor;
     int broadcastCount = 0;
diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp
index 99082cc..f0d9cf1 100644
--- a/cmds/statsd/tests/UidMap_test.cpp
+++ b/cmds/statsd/tests/UidMap_test.cpp
@@ -71,14 +71,20 @@
     vector<int32_t> uids;
     vector<int64_t> versions;
     vector<String16> apps;
+    vector<String16> versionStrings;
+    vector<String16> installers;
 
     uids.push_back(1000);
     uids.push_back(1000);
+    versionStrings.push_back(String16("v1"));
+    versionStrings.push_back(String16("v1"));
+    installers.push_back(String16(""));
+    installers.push_back(String16(""));
     apps.push_back(String16(kApp1.c_str()));
     apps.push_back(String16(kApp2.c_str()));
     versions.push_back(4);
     versions.push_back(5);
-    m.updateMap(1, uids, versions, apps);
+    m.updateMap(1, uids, versions, versionStrings, apps, installers);
     EXPECT_TRUE(m.hasApp(1000, kApp1));
     EXPECT_TRUE(m.hasApp(1000, kApp2));
     EXPECT_FALSE(m.hasApp(1000, "not.app"));
@@ -97,14 +103,20 @@
     vector<int32_t> uids;
     vector<int64_t> versions;
     vector<String16> apps;
+    vector<String16> versionStrings;
+    vector<String16> installers;
 
     uids.push_back(1000);
     uids.push_back(1000);
+    versionStrings.push_back(String16("v1"));
+    versionStrings.push_back(String16("v1"));
+    installers.push_back(String16(""));
+    installers.push_back(String16(""));
     apps.push_back(String16(kApp1.c_str()));
     apps.push_back(String16(kApp2.c_str()));
     versions.push_back(4);
     versions.push_back(5);
-    m.updateMap(1, uids, versions, apps);
+    m.updateMap(1, uids, versions, versionStrings, apps, installers);
 
     std::set<string> name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
     EXPECT_EQ(name_set.size(), 2u);
@@ -112,7 +124,7 @@
     EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
 
     // Update the app1 version.
-    m.updateApp(2, String16(kApp1.c_str()), 1000, 40);
+    m.updateApp(2, String16(kApp1.c_str()), 1000, 40, String16("v40"), String16(""));
     EXPECT_EQ(40, m.getAppVersion(1000, kApp1));
 
     name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
@@ -138,14 +150,15 @@
 
 TEST(UidMapTest, TestUpdateApp) {
     UidMap m;
-    m.updateMap(1, {1000, 1000}, {4, 5}, {String16(kApp1.c_str()), String16(kApp2.c_str())});
+    m.updateMap(1, {1000, 1000}, {4, 5}, {String16("v4"), String16("v5")},
+                {String16(kApp1.c_str()), String16(kApp2.c_str())}, {String16(""), String16("")});
     std::set<string> name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
     EXPECT_EQ(name_set.size(), 2u);
     EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
     EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
 
     // Adds a new name for uid 1000.
-    m.updateApp(2, String16("NeW_aPP1_NAmE"), 1000, 40);
+    m.updateApp(2, String16("NeW_aPP1_NAmE"), 1000, 40, String16("v40"), String16(""));
     name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
     EXPECT_EQ(name_set.size(), 3u);
     EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
@@ -154,7 +167,7 @@
     EXPECT_TRUE(name_set.find("new_app1_name") != name_set.end());
 
     // This name is also reused by another uid 2000.
-    m.updateApp(3, String16("NeW_aPP1_NAmE"), 2000, 1);
+    m.updateApp(3, String16("NeW_aPP1_NAmE"), 2000, 1, String16("v1"), String16(""));
     name_set = m.getAppNamesFromUid(2000, true /* returnNormalized */);
     EXPECT_EQ(name_set.size(), 1u);
     EXPECT_TRUE(name_set.find("NeW_aPP1_NAmE") == name_set.end());
@@ -185,21 +198,26 @@
     vector<int32_t> uids;
     vector<int64_t> versions;
     vector<String16> apps;
+    vector<String16> versionStrings;
+    vector<String16> installers;
     uids.push_back(1000);
     apps.push_back(String16(kApp2.c_str()));
+    versionStrings.push_back(String16("v1"));
+    installers.push_back(String16(""));
     versions.push_back(5);
-    m.updateMap(1, uids, versions, apps);
+    m.updateMap(1, uids, versions, versionStrings, apps, installers);
 
     // Set the last timestamp for this config key to be newer.
     m.mLastUpdatePerConfigKey[config1] = 2;
 
     ProtoOutputStream proto;
-    m.appendUidMap(3, config1, nullptr, &proto);
+    m.appendUidMap(3, config1, nullptr, true, true, &proto);
 
     // Check there's still a uidmap attached this one.
     UidMapping results;
     protoOutputStreamToUidMapping(&proto, &results);
     EXPECT_EQ(1, results.snapshots_size());
+    EXPECT_EQ("v1", results.snapshots(0).package_info(0).version_string());
 }
 
 TEST(UidMapTest, TestRemovedAppRetained) {
@@ -209,15 +227,19 @@
     m.OnConfigUpdated(config1);
     vector<int32_t> uids;
     vector<int64_t> versions;
+    vector<String16> versionStrings;
+    vector<String16> installers;
     vector<String16> apps;
     uids.push_back(1000);
     apps.push_back(String16(kApp2.c_str()));
     versions.push_back(5);
-    m.updateMap(1, uids, versions, apps);
+    versionStrings.push_back(String16("v5"));
+    installers.push_back(String16(""));
+    m.updateMap(1, uids, versions, versionStrings, apps, installers);
     m.removeApp(2, String16(kApp2.c_str()), 1000);
 
     ProtoOutputStream proto;
-    m.appendUidMap(3, config1, nullptr, &proto);
+    m.appendUidMap(3, config1, nullptr, true, true, &proto);
 
     // Snapshot should still contain this item as deleted.
     UidMapping results;
@@ -233,30 +255,34 @@
     m.OnConfigUpdated(config1);
     vector<int32_t> uids;
     vector<int64_t> versions;
+    vector<String16> versionStrings;
+    vector<String16> installers;
     vector<String16> apps;
     const int maxDeletedApps = StatsdStats::kMaxDeletedAppsInUidMap;
     for (int j = 0; j < maxDeletedApps + 10; j++) {
         uids.push_back(j);
         apps.push_back(String16(kApp1.c_str()));
         versions.push_back(j);
+        versionStrings.push_back(String16("v"));
+        installers.push_back(String16(""));
     }
-    m.updateMap(1, uids, versions, apps);
+    m.updateMap(1, uids, versions, versionStrings, apps, installers);
 
     // First, verify that we have the expected number of items.
     UidMapping results;
     ProtoOutputStream proto;
-    m.appendUidMap(3, config1, nullptr, &proto);
+    m.appendUidMap(3, config1, nullptr, true, true, &proto);
     protoOutputStreamToUidMapping(&proto, &results);
     EXPECT_EQ(maxDeletedApps + 10, results.snapshots(0).package_info_size());
 
     // Now remove all the apps.
-    m.updateMap(1, uids, versions, apps);
+    m.updateMap(1, uids, versions, versionStrings, apps, installers);
     for (int j = 0; j < maxDeletedApps + 10; j++) {
         m.removeApp(4, String16(kApp1.c_str()), j);
     }
 
     proto.clear();
-    m.appendUidMap(5, config1, nullptr, &proto);
+    m.appendUidMap(5, config1, nullptr, true, true, &proto);
     // Snapshot drops the first nine items.
     protoOutputStreamToUidMapping(&proto, &results);
     EXPECT_EQ(maxDeletedApps, results.snapshots(0).package_info_size());
@@ -272,6 +298,8 @@
 
     vector<int32_t> uids;
     vector<int64_t> versions;
+    vector<String16> versionStrings;
+    vector<String16> installers;
     vector<String16> apps;
     uids.push_back(1000);
     uids.push_back(1000);
@@ -279,45 +307,49 @@
     apps.push_back(String16(kApp2.c_str()));
     versions.push_back(4);
     versions.push_back(5);
-    m.updateMap(1, uids, versions, apps);
+    versionStrings.push_back(String16("v4"));
+    versionStrings.push_back(String16("v5"));
+    installers.push_back(String16(""));
+    installers.push_back(String16(""));
+    m.updateMap(1, uids, versions, versionStrings, apps, installers);
 
     ProtoOutputStream proto;
-    m.appendUidMap(2, config1, nullptr, &proto);
+    m.appendUidMap(2, config1, nullptr, true, true, &proto);
     UidMapping results;
     protoOutputStreamToUidMapping(&proto, &results);
     EXPECT_EQ(1, results.snapshots_size());
 
     // We have to keep at least one snapshot in memory at all times.
     proto.clear();
-    m.appendUidMap(2, config1, nullptr, &proto);
+    m.appendUidMap(2, config1, nullptr, true, true, &proto);
     protoOutputStreamToUidMapping(&proto, &results);
     EXPECT_EQ(1, results.snapshots_size());
 
     // Now add another configuration.
     m.OnConfigUpdated(config2);
-    m.updateApp(5, String16(kApp1.c_str()), 1000, 40);
+    m.updateApp(5, String16(kApp1.c_str()), 1000, 40, String16("v40"), String16(""));
     EXPECT_EQ(1U, m.mChanges.size());
     proto.clear();
-    m.appendUidMap(6, config1, nullptr, &proto);
+    m.appendUidMap(6, config1, nullptr, true, true, &proto);
     protoOutputStreamToUidMapping(&proto, &results);
     EXPECT_EQ(1, results.snapshots_size());
     EXPECT_EQ(1, results.changes_size());
     EXPECT_EQ(1U, m.mChanges.size());
 
     // Add another delta update.
-    m.updateApp(7, String16(kApp2.c_str()), 1001, 41);
+    m.updateApp(7, String16(kApp2.c_str()), 1001, 41, String16("v41"), String16(""));
     EXPECT_EQ(2U, m.mChanges.size());
 
     // We still can't remove anything.
     proto.clear();
-    m.appendUidMap(8, config1, nullptr, &proto);
+    m.appendUidMap(8, config1, nullptr, true, true, &proto);
     protoOutputStreamToUidMapping(&proto, &results);
     EXPECT_EQ(1, results.snapshots_size());
     EXPECT_EQ(1, results.changes_size());
     EXPECT_EQ(2U, m.mChanges.size());
 
     proto.clear();
-    m.appendUidMap(9, config2, nullptr, &proto);
+    m.appendUidMap(9, config2, nullptr, true, true, &proto);
     protoOutputStreamToUidMapping(&proto, &results);
     EXPECT_EQ(1, results.snapshots_size());
     EXPECT_EQ(2, results.changes_size());
@@ -335,19 +367,23 @@
     vector<int32_t> uids;
     vector<int64_t> versions;
     vector<String16> apps;
+    vector<String16> versionStrings;
+    vector<String16> installers;
     uids.push_back(1000);
     apps.push_back(String16(kApp1.c_str()));
     versions.push_back(1);
-    m.updateMap(1, uids, versions, apps);
+    versionStrings.push_back(String16("v1"));
+    installers.push_back(String16(""));
+    m.updateMap(1, uids, versions, versionStrings, apps, installers);
 
-    m.updateApp(3, String16(kApp1.c_str()), 1000, 40);
+    m.updateApp(3, String16(kApp1.c_str()), 1000, 40, String16("v40"), String16(""));
 
     ProtoOutputStream proto;
     vector<uint8_t> bytes;
-    m.appendUidMap(2, config1, nullptr, &proto);
+    m.appendUidMap(2, config1, nullptr, true, true, &proto);
     size_t prevBytes = m.mBytesUsed;
 
-    m.appendUidMap(4, config1, nullptr, &proto);
+    m.appendUidMap(4, config1, nullptr, true, true, &proto);
     EXPECT_TRUE(m.mBytesUsed < prevBytes);
 }
 
@@ -361,21 +397,27 @@
     size_t startBytes = m.mBytesUsed;
     vector<int32_t> uids;
     vector<int64_t> versions;
+    vector<String16> versionStrings;
+    vector<String16> installers;
     vector<String16> apps;
     for (int i = 0; i < 100; i++) {
         uids.push_back(1);
         buf = "EXTREMELY_LONG_STRING_FOR_APP_TO_WASTE_MEMORY." + to_string(i);
         apps.push_back(String16(buf.c_str()));
         versions.push_back(1);
+        versionStrings.push_back(String16("v1"));
+        installers.push_back(String16(""));
     }
-    m.updateMap(1, uids, versions, apps);
+    m.updateMap(1, uids, versions, versionStrings, apps, installers);
 
-    m.updateApp(3, String16("EXTREMELY_LONG_STRING_FOR_APP_TO_WASTE_MEMORY.0"), 1000, 2);
+    m.updateApp(3, String16("EXTREMELY_LONG_STRING_FOR_APP_TO_WASTE_MEMORY.0"), 1000, 2,
+                String16("v2"), String16(""));
     EXPECT_EQ(1U, m.mChanges.size());
 
     // Now force deletion by limiting the memory to hold one delta change.
-    m.maxBytesOverride = 80; // Since the app string alone requires >45 characters.
-    m.updateApp(5, String16("EXTREMELY_LONG_STRING_FOR_APP_TO_WASTE_MEMORY.0"), 1000, 4);
+    m.maxBytesOverride = 120; // Since the app string alone requires >45 characters.
+    m.updateApp(5, String16("EXTREMELY_LONG_STRING_FOR_APP_TO_WASTE_MEMORY.0"), 1000, 4,
+                String16("v4"), String16(""));
     EXPECT_EQ(1U, m.mChanges.size());
 }
 
diff --git a/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp b/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp
index 5c47af7..a9841c9 100644
--- a/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp
@@ -69,8 +69,10 @@
     // Here it assumes that GMS core has two uids.
     processor->getUidMap()->updateMap(
             1, {222, 444, 111, 333}, {1, 1, 2, 2},
+            {String16("v1"), String16("v1"), String16("v2"), String16("v2")},
             {String16("com.android.gmscore"), String16("com.android.gmscore"), String16("app1"),
-             String16("APP3")});
+             String16("APP3")},
+            {String16(""), String16(""), String16(""), String16("")});
 
     // GMS core node is in the middle.
     std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1"),
@@ -215,8 +217,10 @@
     // Here it assumes that GMS core has two uids.
     processor->getUidMap()->updateMap(
             1, {222, 444, 111, 333}, {1, 1, 2, 2},
+            {String16("v1"), String16("v1"), String16("v2"), String16("v2")},
             {String16("com.android.gmscore"), String16("com.android.gmscore"), String16("app1"),
-             String16("APP3")});
+             String16("APP3")},
+            {String16(""), String16(""), String16(""), String16("")});
 
     // GMS core node is in the middle.
     std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1"),
diff --git a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
index 3cb553f..2b0285b 100644
--- a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
@@ -132,7 +132,8 @@
     service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get());
     // This is a new installation, so there shouldn't be a split (should be same as the without
     // split case).
-    service.mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2);
+    service.mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2, String16("v2"),
+                               String16(""));
     // Goes into the second bucket.
     service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 3).get());
 
@@ -145,11 +146,13 @@
     SendConfig(service, MakeConfig());
     int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
                                              // initialized with.
-    service.mUidMap->updateMap(start, {1}, {1}, {String16(kApp1.c_str())});
+    service.mUidMap->updateMap(start, {1}, {1}, {String16("v1")}, {String16(kApp1.c_str())},
+                               {String16("")});
 
     // Force the uidmap to update at timestamp 2.
     service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get());
-    service.mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2);
+    service.mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2, String16("v2"),
+                               String16(""));
     // Goes into the second bucket.
     service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 3).get());
 
@@ -168,7 +171,8 @@
     SendConfig(service, MakeConfig());
     int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
                                              // initialized with.
-    service.mUidMap->updateMap(start, {1}, {1}, {String16(kApp1.c_str())});
+    service.mUidMap->updateMap(start, {1}, {1}, {String16("v1")}, {String16(kApp1.c_str())},
+                               {String16("")});
 
     // Force the uidmap to update at timestamp 2.
     service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get());
@@ -189,13 +193,14 @@
 TEST(PartialBucketE2eTest, TestValueMetricWithoutMinPartialBucket) {
     StatsService service(nullptr);
     // Partial buckets don't occur when app is first installed.
-    service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1);
+    service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
     SendConfig(service, MakeValueMetricConfig(0));
     int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
                                              // initialized with.
 
     service.mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
-    service.mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2);
+    service.mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2,
+                               String16("v2"), String16(""));
 
     ConfigMetricsReport report =
             GetReports(service.mProcessor, 5 * 60 * NS_PER_SEC + start + 100, true);
@@ -206,14 +211,15 @@
 TEST(PartialBucketE2eTest, TestValueMetricWithMinPartialBucket) {
     StatsService service(nullptr);
     // Partial buckets don't occur when app is first installed.
-    service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1);
+    service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
     SendConfig(service, MakeValueMetricConfig(60 * NS_PER_SEC /* One minute */));
     int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
                                              // initialized with.
 
     const int64_t endSkipped = 5 * 60 * NS_PER_SEC + start + 2;
     service.mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
-    service.mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2);
+    service.mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2, String16("v2"),
+                               String16(""));
 
     ConfigMetricsReport report =
             GetReports(service.mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC, true);
@@ -229,13 +235,14 @@
 TEST(PartialBucketE2eTest, TestGaugeMetricWithoutMinPartialBucket) {
     StatsService service(nullptr);
     // Partial buckets don't occur when app is first installed.
-    service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1);
+    service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
     SendConfig(service, MakeGaugeMetricConfig(0));
     int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
                                              // initialized with.
 
     service.mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
-    service.mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2);
+    service.mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2,
+                               String16("v2"), String16(""));
 
     ConfigMetricsReport report =
             GetReports(service.mProcessor, 5 * 60 * NS_PER_SEC + start + 100, true);
@@ -246,14 +253,15 @@
 TEST(PartialBucketE2eTest, TestGaugeMetricWithMinPartialBucket) {
     StatsService service(nullptr);
     // Partial buckets don't occur when app is first installed.
-    service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1);
+    service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
     SendConfig(service, MakeGaugeMetricConfig(60 * NS_PER_SEC /* One minute */));
     int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
                                              // initialized with.
 
     const int64_t endSkipped = 5 * 60 * NS_PER_SEC + start + 2;
     service.mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
-    service.mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2);
+    service.mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2, String16("v2"),
+                               String16(""));
 
     ConfigMetricsReport report =
             GetReports(service.mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC, true);
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/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt
index 851e35b..39b327c 100644
--- a/config/hiddenapi-light-greylist.txt
+++ b/config/hiddenapi-light-greylist.txt
@@ -1320,18 +1320,6 @@
 Landroid/security/Credentials;->convertToPem([Ljava/security/cert/Certificate;)[B
 Landroid/security/IKeyChainService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/security/IKeyChainService;
 Landroid/security/IKeyChainService;->requestPrivateKey(Ljava/lang/String;)Ljava/lang/String;
-Landroid/security/IKeystoreService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/security/IKeystoreService;
-Landroid/security/IKeystoreService;->clear_uid(J)I
-Landroid/security/IKeystoreService;->del(Ljava/lang/String;I)I
-Landroid/security/IKeystoreService;->exist(Ljava/lang/String;I)I
-Landroid/security/IKeystoreService;->generateKey(Ljava/lang/String;Landroid/security/keymaster/KeymasterArguments;[BIILandroid/security/keymaster/KeyCharacteristics;)I
-Landroid/security/IKeystoreService;->get(Ljava/lang/String;I)[B
-Landroid/security/IKeystoreService;->getState(I)I
-Landroid/security/IKeystoreService;->insert(Ljava/lang/String;[BII)I
-Landroid/security/IKeystoreService;->is_hardware_backed(Ljava/lang/String;)I
-Landroid/security/IKeystoreService;->list(Ljava/lang/String;I)[Ljava/lang/String;
-Landroid/security/IKeystoreService;->reset()I
-Landroid/security/IKeystoreService;->ungrant(Ljava/lang/String;I)I
 Landroid/security/keymaster/KeymasterBlobArgument;-><init>(ILandroid/os/Parcel;)V
 Landroid/security/keymaster/KeymasterBlobArgument;-><init>(I[B)V
 Landroid/security/keymaster/KeymasterBlobArgument;->blob:[B
@@ -1343,6 +1331,17 @@
 Landroid/security/keymaster/KeymasterLongArgument;-><init>(IJ)V
 Landroid/security/keymaster/KeymasterLongArgument;-><init>(ILandroid/os/Parcel;)V
 Landroid/security/keymaster/KeymasterLongArgument;->value:J
+Landroid/security/keystore/IKeystoreService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/security/keystore/IKeystoreService;
+Landroid/security/keystore/IKeystoreService;->clear_uid(J)I
+Landroid/security/keystore/IKeystoreService;->del(Ljava/lang/String;I)I
+Landroid/security/keystore/IKeystoreService;->exist(Ljava/lang/String;I)I
+Landroid/security/keystore/IKeystoreService;->get(Ljava/lang/String;I)[B
+Landroid/security/keystore/IKeystoreService;->getState(I)I
+Landroid/security/keystore/IKeystoreService;->insert(Ljava/lang/String;[BII)I
+Landroid/security/keystore/IKeystoreService;->is_hardware_backed(Ljava/lang/String;)I
+Landroid/security/keystore/IKeystoreService;->list(Ljava/lang/String;I)[Ljava/lang/String;
+Landroid/security/keystore/IKeystoreService;->reset()I
+Landroid/security/keystore/IKeystoreService;->ungrant(Ljava/lang/String;I)I
 Landroid/service/carrier/ICarrierMessagingCallback$Stub;-><init>()V
 Landroid/service/carrier/ICarrierMessagingService;->filterSms(Landroid/service/carrier/MessagePdu;Ljava/lang/String;IILandroid/service/carrier/ICarrierMessagingCallback;)V
 Landroid/service/dreams/IDreamManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/service/dreams/IDreamManager;
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/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 2be5dc9..9e109c5 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1625,9 +1625,6 @@
             case AppOpsManager.OP_READ_CALL_LOG:
             case AppOpsManager.OP_WRITE_CALL_LOG:
             case AppOpsManager.OP_PROCESS_OUTGOING_CALLS: {
-                if (sSmsAndCallLogRestrictionEnabled.get() < 0) {
-                    startWatchingSmsRestrictionEnabled();
-                }
                 if (sSmsAndCallLogRestrictionEnabled.get() == 1) {
                     return AppOpsManager.MODE_DEFAULT;
                 }
@@ -1640,26 +1637,24 @@
     private static final AtomicInteger sSmsAndCallLogRestrictionEnabled = new AtomicInteger(-1);
 
     // STOPSHIP b/118520006: Hardcode the default values once the feature is stable.
-    private static void startWatchingSmsRestrictionEnabled() {
+    static {
         final Context context = ActivityThread.currentApplication();
-        if (context == null) {
-            // Should never happen
-            return;
+        if (context != null) {
+            sSmsAndCallLogRestrictionEnabled.set(ActivityThread.currentActivityThread()
+                        .getIntCoreSetting(Settings.Global.SMS_ACCESS_RESTRICTION_ENABLED, 0));
+
+            final Uri uri =
+                    Settings.Global.getUriFor(Settings.Global.SMS_ACCESS_RESTRICTION_ENABLED);
+            context.getContentResolver().registerContentObserver(uri, false, new ContentObserver(
+                    context.getMainThreadHandler()) {
+                @Override
+                public void onChange(boolean selfChange) {
+                    sSmsAndCallLogRestrictionEnabled.set(Settings.Global.getInt(
+                            context.getContentResolver(),
+                            Settings.Global.SMS_ACCESS_RESTRICTION_ENABLED, 0));
+                }
+            });
         }
-
-        sSmsAndCallLogRestrictionEnabled.set(ActivityThread.currentActivityThread()
-                    .getIntCoreSetting(Settings.Global.SMS_ACCESS_RESTRICTION_ENABLED, 0));
-
-        final Uri uri = Settings.Global.getUriFor(Settings.Global.SMS_ACCESS_RESTRICTION_ENABLED);
-        context.getContentResolver().registerContentObserver(uri, false, new ContentObserver(
-                context.getMainThreadHandler()) {
-            @Override
-            public void onChange(boolean selfChange) {
-                sSmsAndCallLogRestrictionEnabled.set(Settings.Global.getInt(
-                        context.getContentResolver(),
-                        Settings.Global.SMS_ACCESS_RESTRICTION_ENABLED, 0));
-            }
-        });
     }
 
     /**
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/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index bd9cf6d..362f4ae 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -88,6 +88,8 @@
     ParceledListSlice getRecentNotifyingAppsForUser(int userId);
     int getBlockedAppCount(int userId);
     boolean areChannelsBypassingDnd();
+    int getAppsBypassingDndCount(int uid);
+    ParceledListSlice getNotificationChannelsBypassingDnd(String pkg, int userId);
 
     // TODO: Remove this when callers have been migrated to the equivalent
     // INotificationListener method.
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index df37a02..450efdf 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -207,7 +207,8 @@
     private static final int MAX_REPLY_HISTORY = 5;
 
     /**
-     * Maximum numbers of action buttons in a notification.
+     * Maximum number of (generic) action buttons in a notification (contextual action buttons are
+     * handled separately).
      * @hide
      */
     public static final int MAX_ACTION_BUTTONS = 3;
@@ -1421,6 +1422,12 @@
          */
         public static final int SEMANTIC_ACTION_CALL = 10;
 
+        /**
+         * {@code SemanticAction}: Contextual action - dependent on the current notification. E.g.
+         * open a Map application with an address shown in the notification.
+         */
+        public static final int SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION = 11;
+
         private final Bundle mExtras;
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
         private Icon mIcon;
@@ -2042,7 +2049,8 @@
                 SEMANTIC_ACTION_UNMUTE,
                 SEMANTIC_ACTION_THUMBS_UP,
                 SEMANTIC_ACTION_THUMBS_DOWN,
-                SEMANTIC_ACTION_CALL
+                SEMANTIC_ACTION_CALL,
+                SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION
         })
         @Retention(RetentionPolicy.SOURCE)
         public @interface SemanticAction {}
@@ -4962,6 +4970,18 @@
                     result);
         }
 
+        private static List<Notification.Action> filterOutContextualActions(
+                List<Notification.Action> actions) {
+            List<Notification.Action> nonContextualActions = new ArrayList<>();
+            for (Notification.Action action : actions) {
+                if (action.getSemanticAction()
+                        != Action.SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION) {
+                    nonContextualActions.add(action);
+                }
+            }
+            return nonContextualActions;
+        }
+
         private RemoteViews applyStandardTemplateWithActions(int layoutId,
                 StandardTemplateParams p, TemplateBindResult result) {
             RemoteViews big = applyStandardTemplate(layoutId, p, result);
@@ -4970,7 +4990,11 @@
 
             boolean validRemoteInput = false;
 
-            int N = mActions.size();
+            // In the UI contextual actions appear separately from the standard actions, so we
+            // filter them out here.
+            List<Notification.Action> nonContextualActions = filterOutContextualActions(mActions);
+
+            int N = nonContextualActions.size();
             boolean emphazisedMode = mN.fullScreenIntent != null && !p.ambient;
             big.setBoolean(R.id.actions, "setEmphasizedMode", emphazisedMode);
             if (N > 0) {
@@ -4979,7 +5003,8 @@
                 big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, 0);
                 if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS;
                 for (int i=0; i<N; i++) {
-                    Action action = mActions.get(i);
+                    Action action = nonContextualActions.get(i);
+
                     boolean actionHasValidInput = hasValidRemoteInput(action);
                     validRemoteInput |= actionHasValidInput;
 
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/Context.java b/core/java/android/content/Context.java
index 03eba7e..004417b 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4335,6 +4335,16 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve a
+     * {android.os.IIdmap2} for managing idmap files (used by overlay
+     * packages).
+     *
+     * @see #getSystemService(String)
+     * @hide
+     */
+    public static final String IDMAP_SERVICE = "idmap";
+
+    /**
+     * Use with {@link #getSystemService(String)} to retrieve a
      * {@link VrManager} for accessing the VR service.
      *
      * @see #getSystemService(String)
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 02f38a7..e9b2404 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -7508,7 +7508,7 @@
      *
      * @see #putExtra(String, String)
      */
-    public String getStringExtra(String name) {
+    public @Nullable String getStringExtra(String name) {
         return mExtras == null ? null : mExtras.getString(name);
     }
 
@@ -7522,7 +7522,7 @@
      *
      * @see #putExtra(String, CharSequence)
      */
-    public CharSequence getCharSequenceExtra(String name) {
+    public @Nullable CharSequence getCharSequenceExtra(String name) {
         return mExtras == null ? null : mExtras.getCharSequence(name);
     }
 
@@ -7536,7 +7536,7 @@
      *
      * @see #putExtra(String, Parcelable)
      */
-    public <T extends Parcelable> T getParcelableExtra(String name) {
+    public @Nullable <T extends Parcelable> T getParcelableExtra(String name) {
         return mExtras == null ? null : mExtras.<T>getParcelable(name);
     }
 
@@ -7550,7 +7550,7 @@
      *
      * @see #putExtra(String, Parcelable[])
      */
-    public Parcelable[] getParcelableArrayExtra(String name) {
+    public @Nullable Parcelable[] getParcelableArrayExtra(String name) {
         return mExtras == null ? null : mExtras.getParcelableArray(name);
     }
 
@@ -7565,7 +7565,7 @@
      *
      * @see #putParcelableArrayListExtra(String, ArrayList)
      */
-    public <T extends Parcelable> ArrayList<T> getParcelableArrayListExtra(String name) {
+    public @Nullable <T extends Parcelable> ArrayList<T> getParcelableArrayListExtra(String name) {
         return mExtras == null ? null : mExtras.<T>getParcelableArrayList(name);
     }
 
@@ -7579,7 +7579,7 @@
      *
      * @see #putExtra(String, Serializable)
      */
-    public Serializable getSerializableExtra(String name) {
+    public @Nullable Serializable getSerializableExtra(String name) {
         return mExtras == null ? null : mExtras.getSerializable(name);
     }
 
@@ -7594,7 +7594,7 @@
      *
      * @see #putIntegerArrayListExtra(String, ArrayList)
      */
-    public ArrayList<Integer> getIntegerArrayListExtra(String name) {
+    public @Nullable ArrayList<Integer> getIntegerArrayListExtra(String name) {
         return mExtras == null ? null : mExtras.getIntegerArrayList(name);
     }
 
@@ -7609,7 +7609,7 @@
      *
      * @see #putStringArrayListExtra(String, ArrayList)
      */
-    public ArrayList<String> getStringArrayListExtra(String name) {
+    public @Nullable ArrayList<String> getStringArrayListExtra(String name) {
         return mExtras == null ? null : mExtras.getStringArrayList(name);
     }
 
@@ -7624,7 +7624,7 @@
      *
      * @see #putCharSequenceArrayListExtra(String, ArrayList)
      */
-    public ArrayList<CharSequence> getCharSequenceArrayListExtra(String name) {
+    public @Nullable ArrayList<CharSequence> getCharSequenceArrayListExtra(String name) {
         return mExtras == null ? null : mExtras.getCharSequenceArrayList(name);
     }
 
@@ -7638,7 +7638,7 @@
      *
      * @see #putExtra(String, boolean[])
      */
-    public boolean[] getBooleanArrayExtra(String name) {
+    public @Nullable boolean[] getBooleanArrayExtra(String name) {
         return mExtras == null ? null : mExtras.getBooleanArray(name);
     }
 
@@ -7652,7 +7652,7 @@
      *
      * @see #putExtra(String, byte[])
      */
-    public byte[] getByteArrayExtra(String name) {
+    public @Nullable byte[] getByteArrayExtra(String name) {
         return mExtras == null ? null : mExtras.getByteArray(name);
     }
 
@@ -7666,7 +7666,7 @@
      *
      * @see #putExtra(String, short[])
      */
-    public short[] getShortArrayExtra(String name) {
+    public @Nullable short[] getShortArrayExtra(String name) {
         return mExtras == null ? null : mExtras.getShortArray(name);
     }
 
@@ -7680,7 +7680,7 @@
      *
      * @see #putExtra(String, char[])
      */
-    public char[] getCharArrayExtra(String name) {
+    public @Nullable char[] getCharArrayExtra(String name) {
         return mExtras == null ? null : mExtras.getCharArray(name);
     }
 
@@ -7694,7 +7694,7 @@
      *
      * @see #putExtra(String, int[])
      */
-    public int[] getIntArrayExtra(String name) {
+    public @Nullable int[] getIntArrayExtra(String name) {
         return mExtras == null ? null : mExtras.getIntArray(name);
     }
 
@@ -7708,7 +7708,7 @@
      *
      * @see #putExtra(String, long[])
      */
-    public long[] getLongArrayExtra(String name) {
+    public @Nullable long[] getLongArrayExtra(String name) {
         return mExtras == null ? null : mExtras.getLongArray(name);
     }
 
@@ -7722,7 +7722,7 @@
      *
      * @see #putExtra(String, float[])
      */
-    public float[] getFloatArrayExtra(String name) {
+    public @Nullable float[] getFloatArrayExtra(String name) {
         return mExtras == null ? null : mExtras.getFloatArray(name);
     }
 
@@ -7736,7 +7736,7 @@
      *
      * @see #putExtra(String, double[])
      */
-    public double[] getDoubleArrayExtra(String name) {
+    public @Nullable double[] getDoubleArrayExtra(String name) {
         return mExtras == null ? null : mExtras.getDoubleArray(name);
     }
 
@@ -7750,7 +7750,7 @@
      *
      * @see #putExtra(String, String[])
      */
-    public String[] getStringArrayExtra(String name) {
+    public @Nullable String[] getStringArrayExtra(String name) {
         return mExtras == null ? null : mExtras.getStringArray(name);
     }
 
@@ -7764,7 +7764,7 @@
      *
      * @see #putExtra(String, CharSequence[])
      */
-    public CharSequence[] getCharSequenceArrayExtra(String name) {
+    public @Nullable CharSequence[] getCharSequenceArrayExtra(String name) {
         return mExtras == null ? null : mExtras.getCharSequenceArray(name);
     }
 
@@ -7778,7 +7778,7 @@
      *
      * @see #putExtra(String, Bundle)
      */
-    public Bundle getBundleExtra(String name) {
+    public @Nullable Bundle getBundleExtra(String name) {
         return mExtras == null ? null : mExtras.getBundle(name);
     }
 
@@ -8584,7 +8584,7 @@
      * @see #removeExtra
      * @see #getStringExtra(String)
      */
-    public @NonNull Intent putExtra(String name, String value) {
+    public @NonNull Intent putExtra(String name, @Nullable String value) {
         if (mExtras == null) {
             mExtras = new Bundle();
         }
@@ -8607,7 +8607,7 @@
      * @see #removeExtra
      * @see #getCharSequenceExtra(String)
      */
-    public @NonNull Intent putExtra(String name, CharSequence value) {
+    public @NonNull Intent putExtra(String name, @Nullable CharSequence value) {
         if (mExtras == null) {
             mExtras = new Bundle();
         }
@@ -8630,7 +8630,7 @@
      * @see #removeExtra
      * @see #getParcelableExtra(String)
      */
-    public @NonNull Intent putExtra(String name, Parcelable value) {
+    public @NonNull Intent putExtra(String name, @Nullable Parcelable value) {
         if (mExtras == null) {
             mExtras = new Bundle();
         }
@@ -8653,7 +8653,7 @@
      * @see #removeExtra
      * @see #getParcelableArrayExtra(String)
      */
-    public @NonNull Intent putExtra(String name, Parcelable[] value) {
+    public @NonNull Intent putExtra(String name, @Nullable Parcelable[] value) {
         if (mExtras == null) {
             mExtras = new Bundle();
         }
@@ -8677,7 +8677,7 @@
      * @see #getParcelableArrayListExtra(String)
      */
     public @NonNull Intent putParcelableArrayListExtra(String name,
-            ArrayList<? extends Parcelable> value) {
+            @Nullable ArrayList<? extends Parcelable> value) {
         if (mExtras == null) {
             mExtras = new Bundle();
         }
@@ -8700,7 +8700,8 @@
      * @see #removeExtra
      * @see #getIntegerArrayListExtra(String)
      */
-    public @NonNull Intent putIntegerArrayListExtra(String name, ArrayList<Integer> value) {
+    public @NonNull Intent putIntegerArrayListExtra(String name,
+            @Nullable ArrayList<Integer> value) {
         if (mExtras == null) {
             mExtras = new Bundle();
         }
@@ -8723,7 +8724,7 @@
      * @see #removeExtra
      * @see #getStringArrayListExtra(String)
      */
-    public @NonNull Intent putStringArrayListExtra(String name, ArrayList<String> value) {
+    public @NonNull Intent putStringArrayListExtra(String name, @Nullable ArrayList<String> value) {
         if (mExtras == null) {
             mExtras = new Bundle();
         }
@@ -8747,7 +8748,7 @@
      * @see #getCharSequenceArrayListExtra(String)
      */
     public @NonNull Intent putCharSequenceArrayListExtra(String name,
-            ArrayList<CharSequence> value) {
+            @Nullable ArrayList<CharSequence> value) {
         if (mExtras == null) {
             mExtras = new Bundle();
         }
@@ -8770,7 +8771,7 @@
      * @see #removeExtra
      * @see #getSerializableExtra(String)
      */
-    public @NonNull Intent putExtra(String name, Serializable value) {
+    public @NonNull Intent putExtra(String name, @Nullable Serializable value) {
         if (mExtras == null) {
             mExtras = new Bundle();
         }
@@ -8793,7 +8794,7 @@
      * @see #removeExtra
      * @see #getBooleanArrayExtra(String)
      */
-    public @NonNull Intent putExtra(String name, boolean[] value) {
+    public @NonNull Intent putExtra(String name, @Nullable boolean[] value) {
         if (mExtras == null) {
             mExtras = new Bundle();
         }
@@ -8816,7 +8817,7 @@
      * @see #removeExtra
      * @see #getByteArrayExtra(String)
      */
-    public @NonNull Intent putExtra(String name, byte[] value) {
+    public @NonNull Intent putExtra(String name, @Nullable byte[] value) {
         if (mExtras == null) {
             mExtras = new Bundle();
         }
@@ -8839,7 +8840,7 @@
      * @see #removeExtra
      * @see #getShortArrayExtra(String)
      */
-    public @NonNull Intent putExtra(String name, short[] value) {
+    public @NonNull Intent putExtra(String name, @Nullable short[] value) {
         if (mExtras == null) {
             mExtras = new Bundle();
         }
@@ -8862,7 +8863,7 @@
      * @see #removeExtra
      * @see #getCharArrayExtra(String)
      */
-    public @NonNull Intent putExtra(String name, char[] value) {
+    public @NonNull Intent putExtra(String name, @Nullable char[] value) {
         if (mExtras == null) {
             mExtras = new Bundle();
         }
@@ -8885,7 +8886,7 @@
      * @see #removeExtra
      * @see #getIntArrayExtra(String)
      */
-    public @NonNull Intent putExtra(String name, int[] value) {
+    public @NonNull Intent putExtra(String name, @Nullable int[] value) {
         if (mExtras == null) {
             mExtras = new Bundle();
         }
@@ -8908,7 +8909,7 @@
      * @see #removeExtra
      * @see #getLongArrayExtra(String)
      */
-    public @NonNull Intent putExtra(String name, long[] value) {
+    public @NonNull Intent putExtra(String name, @Nullable long[] value) {
         if (mExtras == null) {
             mExtras = new Bundle();
         }
@@ -8931,7 +8932,7 @@
      * @see #removeExtra
      * @see #getFloatArrayExtra(String)
      */
-    public @NonNull Intent putExtra(String name, float[] value) {
+    public @NonNull Intent putExtra(String name, @Nullable float[] value) {
         if (mExtras == null) {
             mExtras = new Bundle();
         }
@@ -8954,7 +8955,7 @@
      * @see #removeExtra
      * @see #getDoubleArrayExtra(String)
      */
-    public @NonNull Intent putExtra(String name, double[] value) {
+    public @NonNull Intent putExtra(String name, @Nullable double[] value) {
         if (mExtras == null) {
             mExtras = new Bundle();
         }
@@ -8977,7 +8978,7 @@
      * @see #removeExtra
      * @see #getStringArrayExtra(String)
      */
-    public @NonNull Intent putExtra(String name, String[] value) {
+    public @NonNull Intent putExtra(String name, @Nullable String[] value) {
         if (mExtras == null) {
             mExtras = new Bundle();
         }
@@ -9000,7 +9001,7 @@
      * @see #removeExtra
      * @see #getCharSequenceArrayExtra(String)
      */
-    public @NonNull Intent putExtra(String name, CharSequence[] value) {
+    public @NonNull Intent putExtra(String name, @Nullable CharSequence[] value) {
         if (mExtras == null) {
             mExtras = new Bundle();
         }
@@ -9023,7 +9024,7 @@
      * @see #removeExtra
      * @see #getBundleExtra(String)
      */
-    public @NonNull Intent putExtra(String name, Bundle value) {
+    public @NonNull Intent putExtra(String name, @Nullable Bundle value) {
         if (mExtras == null) {
             mExtras = new Bundle();
         }
diff --git a/core/java/android/content/MimeTypeFilter.java b/core/java/android/content/MimeTypeFilter.java
new file mode 100644
index 0000000..1c26fd9
--- /dev/null
+++ b/core/java/android/content/MimeTypeFilter.java
@@ -0,0 +1,154 @@
+/*
+ * 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.content;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.ArrayList;
+
+/**
+ * Provides utility methods for matching MIME type filters used in ContentProvider.
+ *
+ * <p>Wildcards are allowed only instead of the entire type or subtype with a tree prefix.
+ * Eg. image\/*, *\/* is a valid filter and will match image/jpeg, but image/j* is invalid and
+ * it will not match image/jpeg. Suffixes and parameters are not supported, and they are treated
+ * as part of the subtype during matching. Neither type nor subtype can be empty.
+ *
+ * <p><em>Note: MIME type matching in the Android framework is case-sensitive, unlike the formal
+ * RFC definitions. As a result, you should always write these elements with lower case letters,
+ * or use {@link android.content.Intent#normalizeMimeType} to ensure that they are converted to
+ * lower case.</em>
+ *
+ * <p>MIME types can be null or ill-formatted. In such case they won't match anything.
+ *
+ * <p>MIME type filters must be correctly formatted, or an exception will be thrown.
+ * Copied from support library.
+ * {@hide}
+ */
+public final class MimeTypeFilter {
+
+    private MimeTypeFilter() {
+    }
+
+    private static boolean mimeTypeAgainstFilter(
+            @NonNull String[] mimeTypeParts, @NonNull String[] filterParts) {
+        if (filterParts.length != 2) {
+            throw new IllegalArgumentException(
+                    "Ill-formatted MIME type filter. Must be type/subtype.");
+        }
+        if (filterParts[0].isEmpty() || filterParts[1].isEmpty()) {
+            throw new IllegalArgumentException(
+                    "Ill-formatted MIME type filter. Type or subtype empty.");
+        }
+        if (mimeTypeParts.length != 2) {
+            return false;
+        }
+        if (!"*".equals(filterParts[0])
+                && !filterParts[0].equals(mimeTypeParts[0])) {
+            return false;
+        }
+        if (!"*".equals(filterParts[1])
+                && !filterParts[1].equals(mimeTypeParts[1])) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Matches one nullable MIME type against one MIME type filter.
+     * @return True if the {@code mimeType} matches the {@code filter}.
+     */
+    public static boolean matches(@Nullable String mimeType, @NonNull String filter) {
+        if (mimeType == null) {
+            return false;
+        }
+
+        final String[] mimeTypeParts = mimeType.split("/");
+        final String[] filterParts = filter.split("/");
+
+        return mimeTypeAgainstFilter(mimeTypeParts, filterParts);
+    }
+
+    /**
+     * Matches one nullable MIME type against an array of MIME type filters.
+     * @return The first matching filter, or null if nothing matches.
+     */
+    @Nullable
+    public static String matches(
+            @Nullable String mimeType, @NonNull String[] filters) {
+        if (mimeType == null) {
+            return null;
+        }
+
+        final String[] mimeTypeParts = mimeType.split("/");
+        for (String filter : filters) {
+            final String[] filterParts = filter.split("/");
+            if (mimeTypeAgainstFilter(mimeTypeParts, filterParts)) {
+                return filter;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Matches multiple MIME types against an array of MIME type filters.
+     * @return The first matching MIME type, or null if nothing matches.
+     */
+    @Nullable
+    public static String matches(
+            @Nullable String[] mimeTypes, @NonNull String filter) {
+        if (mimeTypes == null) {
+            return null;
+        }
+
+        final String[] filterParts = filter.split("/");
+        for (String mimeType : mimeTypes) {
+            final String[] mimeTypeParts = mimeType.split("/");
+            if (mimeTypeAgainstFilter(mimeTypeParts, filterParts)) {
+                return mimeType;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Matches multiple MIME types against an array of MIME type filters.
+     * @return The list of matching MIME types, or empty array if nothing matches.
+     */
+    @NonNull
+    public static String[] matchesMany(
+            @Nullable String[] mimeTypes, @NonNull String filter) {
+        if (mimeTypes == null) {
+            return new String[] {};
+        }
+
+        final ArrayList<String> list = new ArrayList<>();
+        final String[] filterParts = filter.split("/");
+        for (String mimeType : mimeTypes) {
+            final String[] mimeTypeParts = mimeType.split("/");
+            if (mimeTypeAgainstFilter(mimeTypeParts, filterParts)) {
+                list.add(mimeType);
+            }
+        }
+
+        return list.toArray(new String[list.size()]);
+    }
+}
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/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 5f23749..4371c77 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -58,6 +58,7 @@
 public final class AssetManager implements AutoCloseable {
     private static final String TAG = "AssetManager";
     private static final boolean DEBUG_REFS = false;
+    private static final boolean FEATURE_FLAG_IDMAP2 = false;
 
     private static final String FRAMEWORK_APK_PATH = "/system/framework/framework-res.apk";
 
@@ -195,13 +196,23 @@
             return;
         }
 
-        // Make sure that all IDMAPs are up to date.
-        nativeVerifySystemIdmaps();
 
         try {
             final ArrayList<ApkAssets> apkAssets = new ArrayList<>();
             apkAssets.add(ApkAssets.loadFromPath(FRAMEWORK_APK_PATH, true /*system*/));
-            loadStaticRuntimeOverlays(apkAssets);
+            if (FEATURE_FLAG_IDMAP2) {
+                final String[] systemIdmapPaths =
+                    nativeCreateIdmapsForStaticOverlaysTargetingAndroid();
+                if (systemIdmapPaths == null) {
+                    throw new IOException("idmap2 scan failed");
+                }
+                for (String idmapPath : systemIdmapPaths) {
+                    apkAssets.add(ApkAssets.loadOverlayFromPath(idmapPath, true /*system*/));
+                }
+            } else {
+                nativeVerifySystemIdmaps();
+                loadStaticRuntimeOverlays(apkAssets);
+            }
 
             sSystemApkAssetsSet = new ArraySet<>(apkAssets);
             sSystemApkAssets = apkAssets.toArray(new ApkAssets[apkAssets.size()]);
@@ -1404,6 +1415,7 @@
     private static native long nativeAssetGetRemainingLength(long assetPtr);
 
     private static native void nativeVerifySystemIdmaps();
+    private static native String[] nativeCreateIdmapsForStaticOverlaysTargetingAndroid();
 
     // Global debug native methods.
     /**
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/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 7952c41..bd149fd 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -426,6 +426,31 @@
     }
 
     /**
+     * Authenticates for the given user.
+     * @param cancel An object that can be used to cancel authentication
+     * @param executor An executor to handle callback events
+     * @param callback An object to receive authentication events
+     * @param userId The user to authenticate
+     * @hide
+     */
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+    public void authenticateUser(@NonNull CancellationSignal cancel,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull AuthenticationCallback callback,
+            int userId) {
+        if (cancel == null) {
+            throw new IllegalArgumentException("Must supply a cancellation signal");
+        }
+        if (executor == null) {
+            throw new IllegalArgumentException("Must supply an executor");
+        }
+        if (callback == null) {
+            throw new IllegalArgumentException("Must supply a callback");
+        }
+        authenticateInternal(null /* crypto */, cancel, executor, callback, userId);
+    }
+
+    /**
      * This call warms up the biometric hardware, displays a system-provided dialog, and starts
      * scanning for a biometric. It terminates when {@link
      * AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when {@link
@@ -465,7 +490,7 @@
         if (callback == null) {
             throw new IllegalArgumentException("Must supply a callback");
         }
-        authenticateInternal(crypto, cancel, executor, callback);
+        authenticateInternal(crypto, cancel, executor, callback, mContext.getUserId());
     }
 
     /**
@@ -502,7 +527,7 @@
         if (callback == null) {
             throw new IllegalArgumentException("Must supply a callback");
         }
-        authenticateInternal(null /* crypto */, cancel, executor, callback);
+        authenticateInternal(null /* crypto */, cancel, executor, callback, mContext.getUserId());
     }
 
     private void cancelAuthentication() {
@@ -518,7 +543,8 @@
     private void authenticateInternal(@Nullable CryptoObject crypto,
             @NonNull CancellationSignal cancel,
             @NonNull @CallbackExecutor Executor executor,
-            @NonNull AuthenticationCallback callback) {
+            @NonNull AuthenticationCallback callback,
+            int userId) {
         try {
             if (cancel.isCanceled()) {
                 Log.w(TAG, "Authentication already canceled");
@@ -531,7 +557,7 @@
             mExecutor = executor;
             mAuthenticationCallback = callback;
             final long sessionId = crypto != null ? crypto.getOpId() : 0;
-            mService.authenticate(mToken, sessionId, mContext.getUserId(),
+            mService.authenticate(mToken, sessionId, userId,
                     mBiometricServiceReceiver, 0 /* flags */, mContext.getOpPackageName(),
                     mBundle, mDialogReceiver);
         } catch (RemoteException e) {
diff --git a/core/java/android/hardware/display/ColorDisplayManager.java b/core/java/android/hardware/display/ColorDisplayManager.java
new file mode 100644
index 0000000..0a76c2b
--- /dev/null
+++ b/core/java/android/hardware/display/ColorDisplayManager.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.display;
+
+import android.content.Context;
+
+import com.android.internal.R;
+
+/**
+ * Manages the display's color transforms and modes.
+ * @hide
+ */
+public final class ColorDisplayManager {
+
+    /**
+     * Returns {@code true} if Night Display is supported by the device.
+     */
+    public static boolean isNightDisplayAvailable(Context context) {
+        return context.getResources().getBoolean(R.bool.config_nightDisplayAvailable);
+    }
+}
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/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 9cf7de5..c437dde 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -2421,7 +2421,7 @@
 
     public static final IntToString[] HISTORY_EVENT_INT_FORMATTERS = new IntToString[] {
             sUidToString, sUidToString, sUidToString, sUidToString, sUidToString, sUidToString,
-            sUidToString, sUidToString, sUidToString, sUidToString, sUidToString, sUidToString,
+            sUidToString, sUidToString, sUidToString, sUidToString, sUidToString, sIntToString,
             sUidToString, sUidToString, sUidToString, sUidToString, sUidToString, sUidToString,
             sUidToString, sUidToString, sUidToString, sIntToString
     };
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/FileUtils.java b/core/java/android/os/FileUtils.java
index 1f47f93..28ea553 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -1390,3 +1390,4 @@
         }
     }
 }
+
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/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl
index 124f207..74d434c 100644
--- a/core/java/android/os/IStatsManager.aidl
+++ b/core/java/android/os/IStatsManager.aidl
@@ -62,12 +62,15 @@
      * Inform statsd what the version and package are for each uid. Note that each array should
      * have the same number of elements, and version[i] and package[i] correspond to uid[i].
      */
-    oneway void informAllUidData(in int[] uid, in long[] version, in String[] app);
+    oneway void informAllUidData(in int[] uid, in long[] version, in String[] version_string,
+        in String[] app, in String[] installer);
 
     /**
-     * Inform statsd what the uid and version are for one app that was updated.
+     * Inform statsd what the uid, version, version_string, and installer are for one app that was
+     * updated.
      */
-    oneway void informOnePackage(in String app, in int uid, in long version);
+    oneway void informOnePackage(in String app, in int uid, in long version,
+        in String version_string, in String installer);
 
     /**
      * Inform stats that an app was removed.
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/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 126588a..44b9e311 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -1056,6 +1056,9 @@
     /**
      * Internal class representing a remote status read by
      * {@link ParcelFileDescriptor#readCommStatus(FileDescriptor, byte[])}.
+     *
+     * Warning: this must be kept in sync with ParcelFileDescriptorStatus at
+     * frameworks/native/libs/binder/Parcel.cpp
      */
     private static class Status {
         /** Special value indicating remote side died. */
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/StatsLogEventWrapper.java b/core/java/android/os/StatsLogEventWrapper.java
index 72e1ab9..866bd9a 100644
--- a/core/java/android/os/StatsLogEventWrapper.java
+++ b/core/java/android/os/StatsLogEventWrapper.java
@@ -104,14 +104,6 @@
     }
 
     /**
-     * Write a double value.
-     */
-    public void writeDouble(double val) {
-        mTypes.add(EVENT_TYPE_DOUBLE);
-        mValues.add(val);
-    }
-
-    /**
      * Write a storage value.
      */
     public void writeStorage(byte[] val) {
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/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 67e52aa..16d454d 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -16,12 +16,11 @@
 
 package android.provider;
 
-import static android.system.OsConstants.SEEK_SET;
-
 import static com.android.internal.util.Preconditions.checkArgument;
 import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
 import static com.android.internal.util.Preconditions.checkCollectionNotEmpty;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
 import android.content.ContentProviderClient;
@@ -29,13 +28,12 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.MimeTypeFilter;
 import android.content.pm.ResolveInfo;
 import android.content.res.AssetFileDescriptor;
 import android.database.Cursor;
 import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
 import android.graphics.ImageDecoder;
-import android.graphics.Matrix;
 import android.graphics.Point;
 import android.media.ExifInterface;
 import android.net.Uri;
@@ -50,20 +48,13 @@
 import android.os.ParcelableException;
 import android.os.RemoteException;
 import android.os.storage.StorageVolume;
-import android.system.ErrnoException;
-import android.system.Os;
 import android.util.DataUnit;
 import android.util.Log;
-import android.util.Size;
 
-import libcore.io.IoUtils;
-
-import java.io.BufferedInputStream;
 import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 
@@ -113,6 +104,54 @@
     public static final String EXTRA_TARGET_URI = "android.content.extra.TARGET_URI";
 
     /**
+     * Key for {@link DocumentsProvider} to query display name is matched.
+     * The match of display name is partial matching and case-insensitive.
+     * Ex: The value is "o", the display name of the results will contain
+     * both "foo" and "Open".
+     *
+     * @see DocumentsProvider#querySearchDocuments(String, String[],
+     *      Bundle)
+     * {@hide}
+     */
+    public static final String QUERY_ARG_DISPLAY_NAME = "android:query-arg-display-name";
+
+    /**
+     * Key for {@link DocumentsProvider} to query mime types is matched.
+     * The value is a string array, it can support different mime types.
+     * Each items will be treated as "OR" condition. Ex: {"image/*" ,
+     * "video/*"}. The mime types of the results will contain both image
+     * type and video type.
+     *
+     * @see DocumentsProvider#querySearchDocuments(String, String[],
+     *      Bundle)
+     * {@hide}
+     */
+    public static final String QUERY_ARG_MIME_TYPES = "android:query-arg-mime-types";
+
+    /**
+     * Key for {@link DocumentsProvider} to query the file size in bytes is
+     * larger than the value.
+     *
+     * @see DocumentsProvider#querySearchDocuments(String, String[],
+     *      Bundle)
+     * {@hide}
+     */
+    public static final String QUERY_ARG_FILE_SIZE_OVER = "android:query-arg-file-size-over";
+
+    /**
+     * Key for {@link DocumentsProvider} to query the last modified time
+     * is newer than the value. The unit is in milliseconds since
+     * January 1, 1970 00:00:00.0 UTC.
+     *
+     * @see DocumentsProvider#querySearchDocuments(String, String[],
+     *      Bundle)
+     * @see Document#COLUMN_LAST_MODIFIED
+     * {@hide}
+     */
+    public static final String QUERY_ARG_LAST_MODIFIED_AFTER =
+            "android:query-arg-last-modified-after";
+
+    /**
      * Sets the desired initial location visible to user when file chooser is shown.
      *
      * <p>Applicable to {@link Intent} with actions:
@@ -929,6 +968,89 @@
     }
 
     /**
+     * Check if the values match the query arguments.
+     *
+     * @param queryArgs the query arguments
+     * @param displayName the display time to check against
+     * @param mimeType the mime type to check against
+     * @param lastModified the last modified time to check against
+     * @param size the size to check against
+     * @hide
+     */
+    public static boolean matchSearchQueryArguments(Bundle queryArgs, String displayName,
+            String mimeType, long lastModified, long size) {
+        if (queryArgs == null) {
+            return true;
+        }
+
+        final String argDisplayName = queryArgs.getString(QUERY_ARG_DISPLAY_NAME, "");
+        if (!argDisplayName.isEmpty()) {
+            // TODO (118795812) : Enhance the search string handled in DocumentsProvider
+            if (!displayName.toLowerCase().contains(argDisplayName.toLowerCase())) {
+                return false;
+            }
+        }
+
+        final long argFileSize = queryArgs.getLong(QUERY_ARG_FILE_SIZE_OVER, -1 /* defaultValue */);
+        if (argFileSize != -1 && size < argFileSize) {
+            return false;
+        }
+
+        final long argLastModified = queryArgs.getLong(QUERY_ARG_LAST_MODIFIED_AFTER,
+                -1 /* defaultValue */);
+        if (argLastModified != -1 && lastModified < argLastModified) {
+            return false;
+        }
+
+        final String[] argMimeTypes = queryArgs.getStringArray(QUERY_ARG_MIME_TYPES);
+        if (argMimeTypes != null && argMimeTypes.length > 0) {
+            mimeType = Intent.normalizeMimeType(mimeType);
+            for (String type : argMimeTypes) {
+                if (MimeTypeFilter.matches(mimeType, Intent.normalizeMimeType(type))) {
+                    return true;
+                }
+            }
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Get the handled query arguments from the query bundle. The handled arguments are
+     * {@link DocumentsContract#QUERY_ARG_DISPLAY_NAME},
+     * {@link DocumentsContract#QUERY_ARG_MIME_TYPES},
+     * {@link DocumentsContract#QUERY_ARG_FILE_SIZE_OVER} and
+     * {@link DocumentsContract#QUERY_ARG_LAST_MODIFIED_AFTER}.
+     *
+     * @param queryArgs the query arguments to be parsed.
+     * @return the handled query arguments
+     * @hide
+     */
+    public static String[] getHandledQueryArguments(Bundle queryArgs) {
+        if (queryArgs == null) {
+            return new String[0];
+        }
+
+        final ArrayList<String> args = new ArrayList<>();
+        if (queryArgs.keySet().contains(QUERY_ARG_DISPLAY_NAME)) {
+            args.add(QUERY_ARG_DISPLAY_NAME);
+        }
+
+        if (queryArgs.keySet().contains(QUERY_ARG_FILE_SIZE_OVER)) {
+            args.add(QUERY_ARG_FILE_SIZE_OVER);
+        }
+
+        if (queryArgs.keySet().contains(QUERY_ARG_LAST_MODIFIED_AFTER)) {
+            args.add(QUERY_ARG_LAST_MODIFIED_AFTER);
+        }
+
+        if (queryArgs.keySet().contains(QUERY_ARG_MIME_TYPES)) {
+            args.add(QUERY_ARG_MIME_TYPES);
+        }
+        return args.toArray(new String[0]);
+    }
+
+    /**
      * Test if the given URI represents a {@link Document} backed by a
      * {@link DocumentsProvider}.
      *
@@ -1052,6 +1174,15 @@
         return searchDocumentsUri.getQueryParameter(PARAM_QUERY);
     }
 
+    /**
+     * Extract the search query from a Bundle
+     * {@link #QUERY_ARG_DISPLAY_NAME}.
+     * {@hide}
+     */
+    public static String getSearchDocumentsQuery(@NonNull Bundle bundle) {
+        return bundle.getString(QUERY_ARG_DISPLAY_NAME, "" /* defaultValue */);
+    }
+
     /** {@hide} */
     @UnsupportedAppUsage
     public static Uri setManageMode(Uri uri) {
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 68f8acd..58f8213 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -32,7 +32,6 @@
 import static android.provider.DocumentsContract.buildTreeDocumentUri;
 import static android.provider.DocumentsContract.getDocumentId;
 import static android.provider.DocumentsContract.getRootId;
-import static android.provider.DocumentsContract.getSearchDocumentsQuery;
 import static android.provider.DocumentsContract.getTreeDocumentId;
 import static android.provider.DocumentsContract.isTreeUri;
 
@@ -47,6 +46,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.MimeTypeFilter;
 import android.content.UriMatcher;
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
@@ -651,6 +651,55 @@
     }
 
     /**
+     * Return documents that match the given query under the requested
+     * root. The returned documents should be sorted by relevance in descending
+     * order. How documents are matched against the query string is an
+     * implementation detail left to each provider, but it's suggested that at
+     * least {@link Document#COLUMN_DISPLAY_NAME} be matched in a
+     * case-insensitive fashion.
+     * <p>
+     * If your provider is cloud-based, and you have some data cached or pinned
+     * locally, you may return the local data immediately, setting
+     * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that
+     * you are still fetching additional data. Then, when the network data is
+     * available, you can send a change notification to trigger a requery and
+     * return the complete contents.
+     * <p>
+     * To support change notifications, you must
+     * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
+     * Uri, such as {@link DocumentsContract#buildSearchDocumentsUri(String,
+     * String, String)}. Then you can call {@link ContentResolver#notifyChange(Uri,
+     * android.database.ContentObserver, boolean)} with that Uri to send change
+     * notifications.
+     *
+     * @param rootId the root to search under.
+     * @param projection list of {@link Document} columns to put into the
+     *            cursor. If {@code null} all supported columns should be
+     *            included.
+     * @param queryArgs the query arguments.
+     *            {@link DocumentsContract#QUERY_ARG_DISPLAY_NAME},
+     *            {@link DocumentsContract#QUERY_ARG_MIME_TYPES},
+     *            {@link DocumentsContract#QUERY_ARG_FILE_SIZE_OVER},
+     *            {@link DocumentsContract#QUERY_ARG_LAST_MODIFIED_AFTER}.
+     * @return cursor containing search result. Include
+     *         {@link ContentResolver#EXTRA_HONORED_ARGS} in {@link Cursor}
+     *         extras {@link Bundle} when any QUERY_ARG_* value was honored
+     *         during the preparation of the results.
+     *
+     * @see ContentResolver#EXTRA_HONORED_ARGS
+     * @see DocumentsContract#EXTRA_LOADING
+     * @see DocumentsContract#EXTRA_INFO
+     * @see DocumentsContract#EXTRA_ERROR
+     * {@hide}
+     */
+    @SuppressWarnings("unused")
+    public Cursor querySearchDocuments(String rootId, String[] projection, Bundle queryArgs)
+            throws FileNotFoundException {
+        return querySearchDocuments(rootId, DocumentsContract.getSearchDocumentsQuery(queryArgs),
+                projection);
+    }
+
+    /**
      * Ejects the root. Throws {@link IllegalStateException} if ejection failed.
      *
      * @param rootId the root to be ejected.
@@ -795,7 +844,7 @@
      *      {@link #queryDocument(String, String[])},
      *      {@link #queryRecentDocuments(String, String[])},
      *      {@link #queryRoots(String[])}, and
-     *      {@link #querySearchDocuments(String, String, String[])}.
+     *      {@link #querySearchDocuments(String, String[], Bundle)}.
      */
     @Override
     public Cursor query(Uri uri, String[] projection, String selection,
@@ -812,7 +861,7 @@
      * @see #queryRecentDocuments(String, String[], Bundle, CancellationSignal)
      * @see #queryDocument(String, String[])
      * @see #queryChildDocuments(String, String[], String)
-     * @see #querySearchDocuments(String, String, String[])
+     * @see #querySearchDocuments(String, String[], Bundle)
      */
     @Override
     public final Cursor query(
@@ -825,8 +874,7 @@
                     return queryRecentDocuments(
                             getRootId(uri), projection, queryArgs, cancellationSignal);
                 case MATCH_SEARCH:
-                    return querySearchDocuments(
-                            getRootId(uri), getSearchDocumentsQuery(uri), projection);
+                    return querySearchDocuments(getRootId(uri), projection, queryArgs);
                 case MATCH_DOCUMENT:
                 case MATCH_DOCUMENT_TREE:
                     enforceTree(uri);
@@ -1301,7 +1349,7 @@
                 final long flags =
                     cursor.getLong(cursor.getColumnIndexOrThrow(Document.COLUMN_FLAGS));
                 if ((flags & Document.FLAG_VIRTUAL_DOCUMENT) == 0 && mimeType != null &&
-                        mimeTypeMatches(mimeTypeFilter, mimeType)) {
+                        MimeTypeFilter.matches(mimeType, mimeTypeFilter)) {
                     return new String[] { mimeType };
                 }
             }
@@ -1354,21 +1402,4 @@
         // For any other yet unhandled case, let the provider subclass handle it.
         return openTypedDocument(documentId, mimeTypeFilter, opts, signal);
     }
-
-    /**
-     * @hide
-     */
-    public static boolean mimeTypeMatches(String filter, String test) {
-        if (test == null) {
-            return false;
-        } else if (filter == null || "*/*".equals(filter)) {
-            return true;
-        } else if (filter.equals(test)) {
-            return true;
-        } else if (filter.endsWith("/*")) {
-            return filter.regionMatches(0, test, 0, filter.indexOf('/'));
-        } else {
-            return false;
-        }
-    }
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 0402222..b266648 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1825,53 +1825,6 @@
     })
     public @interface ResetMode{}
 
-
-    /**
-     * Indicates that the user has not started setup personalization.
-     * One of the possible states for {@link Secure#USER_SETUP_PERSONALIZATION_STATE}.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final int USER_SETUP_PERSONALIZATION_NOT_STARTED = 0;
-
-    /**
-     * Indicates that the user has not yet completed setup personalization.
-     * One of the possible states for {@link Secure#USER_SETUP_PERSONALIZATION_STATE}.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final int USER_SETUP_PERSONALIZATION_STARTED = 1;
-
-    /**
-     * Indicates that the user has snoozed personalization and will complete it later.
-     * One of the possible states for {@link Secure#USER_SETUP_PERSONALIZATION_STATE}.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final int USER_SETUP_PERSONALIZATION_PAUSED = 2;
-
-    /**
-     * Indicates that the user has completed setup personalization.
-     * One of the possible states for {@link Secure#USER_SETUP_PERSONALIZATION_STATE}.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final int USER_SETUP_PERSONALIZATION_COMPLETE = 10;
-
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({
-            USER_SETUP_PERSONALIZATION_NOT_STARTED,
-            USER_SETUP_PERSONALIZATION_STARTED,
-            USER_SETUP_PERSONALIZATION_PAUSED,
-            USER_SETUP_PERSONALIZATION_COMPLETE
-    })
-    public @interface UserSetupPersonalization {}
-
     /**
      * Activity Extra: Number of certificates
      * <p>
@@ -5650,6 +5603,52 @@
         public static final String USER_SETUP_COMPLETE = "user_setup_complete";
 
         /**
+         * Indicates that the user has not started setup personalization.
+         * One of the possible states for {@link #USER_SETUP_PERSONALIZATION_STATE}.
+         *
+         * @hide
+         */
+        @SystemApi
+        public static final int USER_SETUP_PERSONALIZATION_NOT_STARTED = 0;
+
+        /**
+         * Indicates that the user has not yet completed setup personalization.
+         * One of the possible states for {@link #USER_SETUP_PERSONALIZATION_STATE}.
+         *
+         * @hide
+         */
+        @SystemApi
+        public static final int USER_SETUP_PERSONALIZATION_STARTED = 1;
+
+        /**
+         * Indicates that the user has snoozed personalization and will complete it later.
+         * One of the possible states for {@link #USER_SETUP_PERSONALIZATION_STATE}.
+         *
+         * @hide
+         */
+        @SystemApi
+        public static final int USER_SETUP_PERSONALIZATION_PAUSED = 2;
+
+        /**
+         * Indicates that the user has completed setup personalization.
+         * One of the possible states for {@link #USER_SETUP_PERSONALIZATION_STATE}.
+         *
+         * @hide
+         */
+        @SystemApi
+        public static final int USER_SETUP_PERSONALIZATION_COMPLETE = 10;
+
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef({
+                USER_SETUP_PERSONALIZATION_NOT_STARTED,
+                USER_SETUP_PERSONALIZATION_STARTED,
+                USER_SETUP_PERSONALIZATION_PAUSED,
+                USER_SETUP_PERSONALIZATION_COMPLETE
+        })
+        public @interface UserSetupPersonalization {}
+
+        /**
          * Defines the user's current state of device personalization.
          * The possible states are defined in {@link UserSetupPersonalization}.
          *
@@ -10325,6 +10324,18 @@
         private static final Validator WIFI_PNO_FREQUENCY_CULLING_ENABLED_VALIDATOR =
                 BOOLEAN_VALIDATOR;
 
+        /**
+         * Setting to enable including recency information when determining pno network priorities.
+         * Disabled by default, and setting it to 1 will enable it.
+         * The value is boolean (0 or 1).
+         * @hide
+         */
+        public static final String WIFI_PNO_RECENCY_SORTING_ENABLED =
+                "wifi_pno_recency_sorting_enabled";
+
+        private static final Validator WIFI_PNO_RECENCY_SORTING_ENABLED_VALIDATOR =
+                BOOLEAN_VALIDATOR;
+
        /**
         * The maximum number of times we will retry a connection to an access
         * point for which we have failed in acquiring an IP address from DHCP.
@@ -12800,6 +12811,8 @@
             VALIDATORS.put(DEVICE_DEMO_MODE, BOOLEAN_VALIDATOR);
             VALIDATORS.put(WIFI_PNO_FREQUENCY_CULLING_ENABLED,
                     WIFI_PNO_FREQUENCY_CULLING_ENABLED_VALIDATOR);
+            VALIDATORS.put(WIFI_PNO_RECENCY_SORTING_ENABLED,
+                    WIFI_PNO_RECENCY_SORTING_ENABLED_VALIDATOR);
         }
 
         /**
diff --git a/core/java/android/rolecontrollerservice/IRoleControllerService.aidl b/core/java/android/rolecontrollerservice/IRoleControllerService.aidl
index 0000b9f..ac5be06 100644
--- a/core/java/android/rolecontrollerservice/IRoleControllerService.aidl
+++ b/core/java/android/rolecontrollerservice/IRoleControllerService.aidl
@@ -30,4 +30,6 @@
                            in IRoleManagerCallback callback);
 
     void onClearRoleHolders(in String roleName, in IRoleManagerCallback callback);
+
+    void onGrantDefaultRoles(in IRoleManagerCallback callback);
 }
diff --git a/core/java/android/rolecontrollerservice/RoleControllerService.java b/core/java/android/rolecontrollerservice/RoleControllerService.java
index da11bca..44c45bb 100644
--- a/core/java/android/rolecontrollerservice/RoleControllerService.java
+++ b/core/java/android/rolecontrollerservice/RoleControllerService.java
@@ -89,6 +89,13 @@
                 RoleControllerService.this.onClearRoleHolders(roleName,
                         new RoleManagerCallbackDelegate(callback));
             }
+
+            @Override
+            public void onGrantDefaultRoles(IRoleManagerCallback callback) {
+                Preconditions.checkNotNull(callback, "callback cannot be null");
+                RoleControllerService.this.onGrantDefaultRoles(
+                        new RoleManagerCallbackDelegate(callback));
+            }
         };
     }
 
@@ -133,6 +140,16 @@
     public abstract void onClearRoleHolders(@NonNull String roleName,
             @NonNull RoleManagerCallback callback);
 
+    /**
+     * Called by system to grant default permissions and roles.
+     * <p>
+     * This is typically when creating a new user or upgrading either system or
+     * permission controller package
+     *
+     * @param callback the callback for whether this call is successful
+     */
+    public abstract void onGrantDefaultRoles(@NonNull RoleManagerCallback callback);
+
     private static class RoleManagerCallbackDelegate implements RoleManagerCallback {
 
         private IRoleManagerCallback mCallback;
diff --git a/core/java/android/security/keymaster/ExportResult.java b/core/java/android/security/keymaster/ExportResult.java
index c104671..1ab79fb 100644
--- a/core/java/android/security/keymaster/ExportResult.java
+++ b/core/java/android/security/keymaster/ExportResult.java
@@ -28,6 +28,11 @@
     public final int resultCode;
     public final byte[] exportData;
 
+    public ExportResult(int resultCode) {
+        this.resultCode = resultCode;
+        this.exportData = new byte[0];
+    }
+
     @UnsupportedAppUsage
     public static final Parcelable.Creator<ExportResult> CREATOR = new
             Parcelable.Creator<ExportResult>() {
diff --git a/core/java/android/security/keymaster/KeyCharacteristics.java b/core/java/android/security/keymaster/KeyCharacteristics.java
index 555863e..a4fe75d 100644
--- a/core/java/android/security/keymaster/KeyCharacteristics.java
+++ b/core/java/android/security/keymaster/KeyCharacteristics.java
@@ -52,6 +52,14 @@
         readFromParcel(in);
     }
 
+    /**
+     * Makes a shallow copy of other by copying the other's references to the KeymasterArguments
+     */
+    public void shallowCopyFrom(KeyCharacteristics other) {
+        this.swEnforced = other.swEnforced;
+        this.hwEnforced = other.hwEnforced;
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/android/security/keymaster/KeymasterCertificateChain.java b/core/java/android/security/keymaster/KeymasterCertificateChain.java
index 243b9fe..00a1a1c 100644
--- a/core/java/android/security/keymaster/KeymasterCertificateChain.java
+++ b/core/java/android/security/keymaster/KeymasterCertificateChain.java
@@ -54,6 +54,14 @@
         readFromParcel(in);
     }
 
+    /**
+     * Makes a shallow copy of other by copying the reference to the certificate chain list.
+     * @param other
+     */
+    public void shallowCopyFrom(KeymasterCertificateChain other) {
+        this.mCertificates = other.mCertificates;
+    }
+
     public List<byte[]> getCertificates() {
         return mCertificates;
     }
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/security/keymaster/OperationResult.java b/core/java/android/security/keymaster/OperationResult.java
index 2943211..bc4f360 100644
--- a/core/java/android/security/keymaster/OperationResult.java
+++ b/core/java/android/security/keymaster/OperationResult.java
@@ -59,6 +59,10 @@
         this.outParams = outParams;
     }
 
+    public OperationResult(int resultCode) {
+        this(resultCode, null, 0, 0, null, null);
+    }
+
     protected OperationResult(Parcel in) {
         resultCode = in.readInt();
         token = in.readStrongBinder();
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/text/Layout.java b/core/java/android/text/Layout.java
index e5fd292..2d5f3bf 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -1270,7 +1270,13 @@
      */
     public float getLineLeft(int line) {
         final int dir = getParagraphDirection(line);
-        final Alignment align = getParagraphAlignment(line);
+        Alignment align = getParagraphAlignment(line);
+        // Before Q, StaticLayout.Builder.setAlignment didn't check whether the input alignment
+        // is null. And when it is null, the old behavior is the same as ALIGN_CENTER.
+        // To keep consistency, we convert a null alignment to ALIGN_CENTER.
+        if (align == null) {
+            align = Alignment.ALIGN_CENTER;
+        }
 
         // First convert combinations of alignment and direction settings to
         // three basic cases: ALIGN_LEFT, ALIGN_RIGHT and ALIGN_CENTER.
@@ -1319,7 +1325,13 @@
      */
     public float getLineRight(int line) {
         final int dir = getParagraphDirection(line);
-        final Alignment align = getParagraphAlignment(line);
+        Alignment align = getParagraphAlignment(line);
+        // Before Q, StaticLayout.Builder.setAlignment didn't check whether the input alignment
+        // is null. And when it is null, the old behavior is the same as ALIGN_CENTER.
+        // To keep consistency, we convert a null alignment to ALIGN_CENTER.
+        if (align == null) {
+            align = Alignment.ALIGN_CENTER;
+        }
 
         final Alignment resultAlign;
         switch(align) {
@@ -2360,6 +2372,52 @@
         public Directions(int[] dirs) {
             mDirections = dirs;
         }
+
+        /**
+         * Returns number of BiDi runs.
+         *
+         * @hide
+         */
+        public @IntRange(from = 0) int getRunCount() {
+            return mDirections.length / 2;
+        }
+
+        /**
+         * Returns the start offset of the BiDi run.
+         *
+         * @param runIndex the index of the BiDi run
+         * @return the start offset of the BiDi run.
+         * @hide
+         */
+        public @IntRange(from = 0) int getRunStart(@IntRange(from = 0) int runIndex) {
+            return mDirections[runIndex * 2];
+        }
+
+        /**
+         * Returns the length of the BiDi run.
+         *
+         * Note that this method may return too large number due to reducing the number of object
+         * allocations. The too large number means the remaining part is assigned to this run. The
+         * caller must clamp the returned value.
+         *
+         * @param runIndex the index of the BiDi run
+         * @return the length of the BiDi run.
+         * @hide
+         */
+        public @IntRange(from = 0) int getRunLength(@IntRange(from = 0) int runIndex) {
+            return mDirections[runIndex * 2 + 1] & RUN_LENGTH_MASK;
+        }
+
+        /**
+         * Returns true if the BiDi run is RTL.
+         *
+         * @param runIndex the index of the BiDi run
+         * @return true if the BiDi run is RTL.
+         * @hide
+         */
+        public boolean isRunRtl(int runIndex) {
+            return (mDirections[runIndex * 2 + 1] & RUN_RTL_FLAG) != 0;
+        }
     }
 
     /**
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 44dfd11..6eb433a 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -16,6 +16,7 @@
 
 package android.text;
 
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
@@ -51,6 +52,8 @@
 public class TextLine {
     private static final boolean DEBUG = false;
 
+    private static final char TAB_CHAR = '\t';
+
     private TextPaint mPaint;
     @UnsupportedAppUsage
     private CharSequence mText;
@@ -198,7 +201,7 @@
             }
         }
 
-        mCharsValid = hasReplacement || hasTabs || directions != Layout.DIRS_ALL_LEFT_TO_RIGHT;
+        mCharsValid = hasReplacement;
 
         if (mCharsValid) {
             if (mChars == null || mChars.length < mLen) {
@@ -232,6 +235,10 @@
         mEllipsisEnd = ellipsisStart != ellipsisEnd ? ellipsisEnd : 0;
     }
 
+    private char charAt(int i) {
+        return mCharsValid ? mChars[i] : mText.charAt(i + mStart);
+    }
+
     /**
      * Justify the line to the given width.
      */
@@ -261,51 +268,23 @@
      * @param bottom the bottom of the line
      */
     void draw(Canvas c, float x, int top, int y, int bottom) {
-        if (!mHasTabs) {
-            if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
-                drawRun(c, 0, mLen, false, x, top, y, bottom, false);
-                return;
-            }
-            if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
-                drawRun(c, 0, mLen, true, x, top, y, bottom, false);
-                return;
-            }
-        }
-
         float h = 0;
-        int[] runs = mDirections.mDirections;
+        final int runCount = mDirections.getRunCount();
+        for (int runIndex = 0; runIndex < runCount; runIndex++) {
+            final int runStart = mDirections.getRunStart(runIndex);
+            final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
+            final boolean runIsRtl = mDirections.isRunRtl(runIndex);
 
-        int lastRunIndex = runs.length - 2;
-        for (int i = 0; i < runs.length; i += 2) {
-            int runStart = runs[i];
-            int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
-            if (runLimit > mLen) {
-                runLimit = mLen;
-            }
-            boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
-
-            int segstart = runStart;
+            int segStart = runStart;
             for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
-                int codept = 0;
-                if (mHasTabs && j < runLimit) {
-                    codept = mChars[j];
-                    if (codept >= 0xD800 && codept < 0xDC00 && j + 1 < runLimit) {
-                        codept = Character.codePointAt(mChars, j);
-                        if (codept > 0xFFFF) {
-                            ++j;
-                            continue;
-                        }
-                    }
-                }
+                if (j == runLimit || charAt(j) == TAB_CHAR) {
+                    h += drawRun(c, segStart, j, runIsRtl, x + h, top, y, bottom,
+                            runIndex != (runCount - 1) || j != mLen);
 
-                if (j == runLimit || codept == '\t') {
-                    h += drawRun(c, segstart, j, runIsRtl, x+h, top, y, bottom,
-                            i != lastRunIndex || j != mLen);
-
-                    if (codept == '\t') {
+                    if (j != runLimit) {  // charAt(j) == TAB_CHAR
                         h = mDir * nextTab(h * mDir);
                     }
-                    segstart = j + 1;
+                    segStart = j + 1;
                 }
             }
         }
@@ -323,75 +302,81 @@
     }
 
     /**
-     * Returns information about a position on the line.
+     * Returns the signed graphical offset from the leading margin.
      *
-     * @param offset the line-relative character offset, between 0 and the
-     * line length, inclusive
-     * @param trailing true to measure the trailing edge of the character
-     * before offset, false to measure the leading edge of the character
-     * at offset.
-     * @param fmi receives metrics information about the requested
-     * character, can be null.
-     * @return the signed offset from the leading margin to the requested
-     * character edge.
+     * Following examples are all for measuring offset=3. LX(e.g. L0, L1, ...) denotes a
+     * character which has LTR BiDi property. On the other hand, RX(e.g. R0, R1, ...) denotes a
+     * character which has RTL BiDi property. Assuming all character has 1em width.
+     *
+     * Example 1: All LTR chars within LTR context
+     *   Input Text (logical)  :   L0 L1 L2 L3 L4 L5 L6 L7 L8
+     *   Input Text (visual)   :   L0 L1 L2 L3 L4 L5 L6 L7 L8
+     *   Output(trailing=true) :  |--------| (Returns 3em)
+     *   Output(trailing=false):  |--------| (Returns 3em)
+     *
+     * Example 2: All RTL chars within RTL context.
+     *   Input Text (logical)  :   R0 R1 R2 R3 R4 R5 R6 R7 R8
+     *   Input Text (visual)   :   R8 R7 R6 R5 R4 R3 R2 R1 R0
+     *   Output(trailing=true) :                    |--------| (Returns -3em)
+     *   Output(trailing=false):                    |--------| (Returns -3em)
+     *
+     * Example 3: BiDi chars within LTR context.
+     *   Input Text (logical)  :   L0 L1 L2 R3 R4 R5 L6 L7 L8
+     *   Input Text (visual)   :   L0 L1 L2 R5 R4 R3 L6 L7 L8
+     *   Output(trailing=true) :  |-----------------| (Returns 6em)
+     *   Output(trailing=false):  |--------| (Returns 3em)
+     *
+     * Example 4: BiDi chars within RTL context.
+     *   Input Text (logical)  :   L0 L1 L2 R3 R4 R5 L6 L7 L8
+     *   Input Text (visual)   :   L6 L7 L8 R5 R4 R3 L0 L1 L2
+     *   Output(trailing=true) :           |-----------------| (Returns -6em)
+     *   Output(trailing=false):                    |--------| (Returns -3em)
+     *
+     * @param offset the line-relative character offset, between 0 and the line length, inclusive
+     * @param trailing no effect if the offset is not on the BiDi transition offset. If the offset
+     *                 is on the BiDi transition offset and true is passed, the offset is regarded
+     *                 as the edge of the trailing run's edge. If false, the offset is regarded as
+     *                 the edge of the preceding run's edge. See example above.
+     * @param fmi receives metrics information about the requested character, can be null
+     * @return the signed graphical offset from the leading margin to the requested character edge.
+     *         The positive value means the offset is right from the leading edge. The negative
+     *         value means the offset is left from the leading edge.
      */
-    public float measure(int offset, boolean trailing, FontMetricsInt fmi) {
-        int target = trailing ? offset - 1 : offset;
+    public float measure(@IntRange(from = 0) int offset, boolean trailing,
+            @NonNull FontMetricsInt fmi) {
+        if (offset > mLen) {
+            throw new IndexOutOfBoundsException(
+                    "offset(" + offset + ") should be less than line limit(" + mLen + ")");
+        }
+        final int target = trailing ? offset - 1 : offset;
         if (target < 0) {
             return 0;
         }
 
         float h = 0;
+        for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
+            final int runStart = mDirections.getRunStart(runIndex);
+            final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
+            final boolean runIsRtl = mDirections.isRunRtl(runIndex);
 
-        if (!mHasTabs) {
-            if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
-                return measureRun(0, offset, mLen, false, fmi);
-            }
-            if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
-                return measureRun(0, offset, mLen, true, fmi);
-            }
-        }
-
-        char[] chars = mChars;
-        int[] runs = mDirections.mDirections;
-        for (int i = 0; i < runs.length; i += 2) {
-            int runStart = runs[i];
-            int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
-            if (runLimit > mLen) {
-                runLimit = mLen;
-            }
-            boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
-
-            int segstart = runStart;
+            int segStart = runStart;
             for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
-                int codept = 0;
-                if (mHasTabs && j < runLimit) {
-                    codept = chars[j];
-                    if (codept >= 0xD800 && codept < 0xDC00 && j + 1 < runLimit) {
-                        codept = Character.codePointAt(chars, j);
-                        if (codept > 0xFFFF) {
-                            ++j;
-                            continue;
-                        }
-                    }
-                }
+                if (j == runLimit || charAt(j) == TAB_CHAR) {
+                    final boolean targetIsInThisSegment = target >= segStart && target < j;
+                    final boolean sameDirection = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
 
-                if (j == runLimit || codept == '\t') {
-                    boolean inSegment = target >= segstart && target < j;
-
-                    boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
-                    if (inSegment && advance) {
-                        return h + measureRun(segstart, offset, j, runIsRtl, fmi);
+                    if (targetIsInThisSegment && sameDirection) {
+                        return h + measureRun(segStart, offset, j, runIsRtl, fmi);
                     }
 
-                    float w = measureRun(segstart, j, j, runIsRtl, fmi);
-                    h += advance ? w : -w;
+                    final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi);
+                    h += sameDirection ? segmentWidth : -segmentWidth;
 
-                    if (inSegment) {
-                        return h + measureRun(segstart, offset, j, runIsRtl, null);
+                    if (targetIsInThisSegment) {
+                        return h + measureRun(segStart, offset, j, runIsRtl, null);
                     }
 
-                    if (codept == '\t') {
+                    if (j != runLimit) {  // charAt(j) == TAB_CHAR
                         if (offset == j) {
                             return h;
                         }
@@ -401,7 +386,7 @@
                         }
                     }
 
-                    segstart = j + 1;
+                    segStart = j + 1;
                 }
             }
         }
@@ -426,62 +411,29 @@
         }
 
         float h = 0;
+        for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
+            final int runStart = mDirections.getRunStart(runIndex);
+            final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
+            final boolean runIsRtl = mDirections.isRunRtl(runIndex);
 
-        if (!mHasTabs) {
-            if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
-                for (int offset = 0; offset <= mLen; ++offset) {
-                    measurement[offset] = measureRun(0, offset, mLen, false, fmi);
-                }
-                return measurement;
-            }
-            if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
-                for (int offset = 0; offset <= mLen; ++offset) {
-                    measurement[offset] = measureRun(0, offset, mLen, true, fmi);
-                }
-                return measurement;
-            }
-        }
-
-        char[] chars = mChars;
-        int[] runs = mDirections.mDirections;
-        for (int i = 0; i < runs.length; i += 2) {
-            int runStart = runs[i];
-            int runLimit = runStart + (runs[i + 1] & Layout.RUN_LENGTH_MASK);
-            if (runLimit > mLen) {
-                runLimit = mLen;
-            }
-            boolean runIsRtl = (runs[i + 1] & Layout.RUN_RTL_FLAG) != 0;
-
-            int segstart = runStart;
+            int segStart = runStart;
             for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; ++j) {
-                int codept = 0;
-                if (mHasTabs && j < runLimit) {
-                    codept = chars[j];
-                    if (codept >= 0xD800 && codept < 0xDC00 && j + 1 < runLimit) {
-                        codept = Character.codePointAt(chars, j);
-                        if (codept > 0xFFFF) {
-                            ++j;
-                            continue;
-                        }
-                    }
-                }
-
-                if (j == runLimit || codept == '\t') {
-                    float oldh = h;
-                    boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
-                    float w = measureRun(segstart, j, j, runIsRtl, fmi);
+                if (j == runLimit || charAt(j) == TAB_CHAR) {
+                    final  float oldh = h;
+                    final boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
+                    final float w = measureRun(segStart, j, j, runIsRtl, fmi);
                     h += advance ? w : -w;
 
-                    float baseh = advance ? oldh : h;
+                    final float baseh = advance ? oldh : h;
                     FontMetricsInt crtfmi = advance ? fmi : null;
-                    for (int offset = segstart; offset <= j && offset <= mLen; ++offset) {
-                        if (target[offset] >= segstart && target[offset] < j) {
+                    for (int offset = segStart; offset <= j && offset <= mLen; ++offset) {
+                        if (target[offset] >= segStart && target[offset] < j) {
                             measurement[offset] =
-                                    baseh + measureRun(segstart, offset, j, runIsRtl, crtfmi);
+                                    baseh + measureRun(segStart, offset, j, runIsRtl, crtfmi);
                         }
                     }
 
-                    if (codept == '\t') {
+                    if (j != runLimit) {  // charAt(j) == TAB_CHAR
                         if (target[j] == j) {
                             measurement[j] = h;
                         }
@@ -491,7 +443,7 @@
                         }
                     }
 
-                    segstart = j + 1;
+                    segStart = j + 1;
                 }
             }
         }
@@ -863,7 +815,6 @@
         } else {
             final int delta = mStart;
             if (mComputed == null) {
-                // TODO: Enable measured getRunAdvance for ReplacementSpan and RTL text.
                 return wp.getRunAdvance(mText, delta + start, delta + end,
                         delta + contextStart, delta + contextEnd, runIsRtl, delta + offset);
             } else {
diff --git a/core/java/android/view/DisplayListCanvas.java b/core/java/android/view/DisplayListCanvas.java
new file mode 100644
index 0000000..03d9955
--- /dev/null
+++ b/core/java/android/view/DisplayListCanvas.java
@@ -0,0 +1,49 @@
+/*
+ * 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.view;
+
+import android.annotation.UnsupportedAppUsage;
+import android.graphics.BaseRecordingCanvas;
+import android.graphics.CanvasProperty;
+import android.graphics.Paint;
+import android.os.Build;
+
+/**
+ * This class exists temporarily to workaround broken apps
+ *
+ * b/119066174
+ *
+ * @hide
+ */
+public abstract class DisplayListCanvas extends BaseRecordingCanvas {
+
+    /** @hide */
+    protected DisplayListCanvas(long nativeCanvas) {
+        super(nativeCanvas);
+    }
+
+    /** @hide */
+    @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(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/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 07a57fb..4b8b7f3 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -58,10 +58,33 @@
     void dispatchGetNewSurface();
 
     /**
-     * Tell the window that it is either gaining or losing focus.  Keep it up
-     * to date on the current state showing navigational focus (touch mode) too.
+     * Tell the window that it is either gaining or losing focus.
+     *
+     * @param hasFocus       {@code true} if window has focus, {@code false} otherwise.
+     * @param inTouchMode    {@code true} if screen is in touch mode, {@code false} otherwise.
+     * @param reportToClient {@code true} when need to report to child view with
+     *                       {@link View#onWindowFocusChanged(boolean)}, {@code false} otherwise.
+     * <p>
+     * Note: In the previous design, there is only one window focus state tracked by
+     * WindowManagerService.
+     * For multi-display, the window focus state is tracked by each display independently.
+     * <p>
+     * It will introduce a problem if the window was already focused on one display and then
+     * switched to another display, since the window focus state on each display is independent,
+     * there is no global window focus state in WindowManagerService, so the window focus state of
+     * the former display remains unchanged.
+     * <p>
+     * When switched back to former display, some flows that rely on the global window focus state
+     * in view root will be missed due to the window focus state remaining unchanged.
+     * (i.e: Showing single IME window when switching between displays.)
+     * <p>
+     * To solve the problem, WindowManagerService tracks the top focused display change and then
+     * callbacks to the client via this method to make sure that the client side will request the
+     * IME on the top focused display, and then set {@param reportToClient} as {@code false} to
+     * ignore reporting to the application, since its focus remains unchanged on its display.
+     *
      */
-    void windowFocusChanged(boolean hasFocus, boolean inTouchMode);
+    void windowFocusChanged(boolean hasFocus, boolean inTouchMode, boolean reportToClient);
     
     void closeSystemDialogs(String reason);
     
diff --git a/services/core/java/com/android/server/input/InputApplicationHandle.java b/core/java/android/view/InputApplicationHandle.java
similarity index 97%
rename from services/core/java/com/android/server/input/InputApplicationHandle.java
rename to core/java/android/view/InputApplicationHandle.java
index 3cf7edc..dc1e505 100644
--- a/services/core/java/com/android/server/input/InputApplicationHandle.java
+++ b/core/java/android/view/InputApplicationHandle.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.input;
+package android.view;
 
 /**
  * Functions as a handle for an application that can receive input.
diff --git a/core/java/android/view/InputChannel.java b/core/java/android/view/InputChannel.java
index b2dd6ac..84c8e7a 100644
--- a/core/java/android/view/InputChannel.java
+++ b/core/java/android/view/InputChannel.java
@@ -18,6 +18,7 @@
 
 import android.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
+import android.os.IBinder;
 import android.os.Parcelable;
 import android.util.Slog;
 
@@ -50,15 +51,17 @@
     @SuppressWarnings("unused")
     @UnsupportedAppUsage
     private long mPtr; // used by native code
-    
+
     private static native InputChannel[] nativeOpenInputChannelPair(String name);
-    
+
     private native void nativeDispose(boolean finalized);
     private native void nativeTransferTo(InputChannel other);
     private native void nativeReadFromParcel(Parcel parcel);
     private native void nativeWriteToParcel(Parcel parcel);
     private native void nativeDup(InputChannel target);
-    
+    private native IBinder nativeGetToken();
+    private native void nativeSetToken(IBinder token);
+
     private native String nativeGetName();
 
     /**
@@ -159,14 +162,22 @@
         }
         
         nativeWriteToParcel(out);
-        
+
         if ((flags & PARCELABLE_WRITE_RETURN_VALUE) != 0) {
             dispose();
         }
     }
-    
+
     @Override
     public String toString() {
         return getName();
     }
+
+    public IBinder getToken() {
+        return nativeGetToken();
+    }
+
+    public void setToken(IBinder token) {
+        nativeSetToken(token);
+    }
 }
diff --git a/services/core/java/com/android/server/input/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
similarity index 93%
rename from services/core/java/com/android/server/input/InputWindowHandle.java
rename to core/java/android/view/InputWindowHandle.java
index bb29bf8..621ee89 100644
--- a/services/core/java/com/android/server/input/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.input;
+package android.view;
 
 import android.graphics.Region;
 import android.view.IWindow;
@@ -34,9 +34,6 @@
     // The input application handle.
     public final InputApplicationHandle inputApplicationHandle;
 
-    // The window manager's window state.
-    public final Object windowState;
-
     // The client window.
     public final IWindow clientWindow;
 
@@ -97,9 +94,8 @@
     private native void nativeDispose();
 
     public InputWindowHandle(InputApplicationHandle inputApplicationHandle,
-            Object windowState, IWindow clientWindow, int displayId) {
+            IWindow clientWindow, int displayId) {
         this.inputApplicationHandle = inputApplicationHandle;
-        this.windowState = windowState;
         this.clientWindow = clientWindow;
         this.displayId = displayId;
     }
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/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java
index 9d31bd1..78ad0da 100644
--- a/core/java/android/view/RenderNodeAnimator.java
+++ b/core/java/android/view/RenderNodeAnimator.java
@@ -293,6 +293,12 @@
         setTarget(canvas.mNode);
     }
 
+    /** @hide */
+    @UnsupportedAppUsage
+    public void setTarget(DisplayListCanvas canvas) {
+        setTarget((RecordingCanvas) canvas);
+    }
+
     private void setTarget(RenderNode node) {
         checkMutable();
         if (mTarget != null) {
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 3d16eb8..a7a5024 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -153,6 +153,9 @@
 
     private static native Display.HdrCapabilities nativeGetHdrCapabilities(IBinder displayToken);
 
+    private static native void nativeSetInputWindowInfo(long transactionObj, long nativeObject,
+            InputWindowHandle handle);
+
 
     private final CloseGuard mCloseGuard = CloseGuard.get();
     private final String mName;
@@ -1459,6 +1462,12 @@
             return this;
         }
 
+        public Transaction setInputWindowInfo(SurfaceControl sc, InputWindowHandle handle) {
+            sc.checkNotReleased();
+            nativeSetInputWindowInfo(mNativeObject, sc.mNativeObject, handle);
+            return this;
+        }
+
         @UnsupportedAppUsage
         public Transaction setMatrix(SurfaceControl sc,
                 float dsdx, float dtdx, float dtdy, float dsdy) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ec16828..5f1336f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3406,18 +3406,33 @@
     private static final int PFLAG4_NOTIFIED_CONTENT_CAPTURE_ADDED = 0x20;
     private static final int PFLAG4_LAST_CONTENT_CAPTURE_NOTIFICATION_TYPE = 0x40;
 
+    /* End of masks for mPrivateFlags4 */
+
     private static final int CONTENT_CAPTURE_NOTIFICATION_TYPE_APPEARED = 1;
     private static final int CONTENT_CAPTURE_NOTIFICATION_TYPE_DISAPPEARED = 0;
 
-    /** @hide */
     @IntDef(flag = true, prefix = { "CONTENT_CAPTURE_NOTIFICATION_TYPE_" }, value = {
             CONTENT_CAPTURE_NOTIFICATION_TYPE_APPEARED,
             CONTENT_CAPTURE_NOTIFICATION_TYPE_DISAPPEARED
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface ContentCaptureNotificationType {}
+    private @interface ContentCaptureNotificationType {}
 
-    /* End of masks for mPrivateFlags4 */
+    /** @hide */
+    protected static final int VIEW_STRUCTURE_FOR_ASSIST = 0;
+    /** @hide */
+    protected  static final int VIEW_STRUCTURE_FOR_AUTOFILL = 1;
+    /** @hide */
+    protected  static final int VIEW_STRUCTURE_FOR_CONTENT_CAPTURE = 2;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "VIEW_STRUCTURE_FOR" }, value = {
+            VIEW_STRUCTURE_FOR_ASSIST,
+            VIEW_STRUCTURE_FOR_AUTOFILL,
+            VIEW_STRUCTURE_FOR_CONTENT_CAPTURE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ViewStructureType {}
 
     /**
      * Always allow a user to over-scroll this view, provided it is a
@@ -4717,6 +4732,16 @@
     private TouchDelegate mTouchDelegate = null;
 
     /**
+     * While touch exploration is in use, set to true when hovering across boundaries and
+     * inside the touch area of the delegate at receiving {@link MotionEvent#ACTION_HOVER_ENTER}
+     * or {@link MotionEvent#ACTION_HOVER_MOVE}. False when leaving boundaries or receiving a
+     * {@link MotionEvent#ACTION_HOVER_EXIT}.
+     * Note that children of view group are excluded in the touch area.
+     * @see #dispatchTouchExplorationHoverEvent
+     */
+    private boolean mHoveringTouchDelegate = false;
+
+    /**
      * Solid color to use as a background when creating the drawing cache. Enables
      * the cache to use 16 bit bitmaps instead of 32 bit.
      */
@@ -8043,8 +8068,7 @@
      * fills in all data that can be inferred from the view itself.
      */
     public void onProvideStructure(ViewStructure structure) {
-        onProvideStructureForAssistOrAutofillOrViewCapture(structure, /* forAutofill = */ false,
-                /* forViewCapture= */ false, /* flags= */ 0);
+        onProvideStructure(structure, VIEW_STRUCTURE_FOR_ASSIST, /* flags= */ 0);
     }
 
     /**
@@ -8117,8 +8141,7 @@
      * @see #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
      */
     public void onProvideAutofillStructure(ViewStructure structure, @AutofillFlags int flags) {
-        onProvideStructureForAssistOrAutofillOrViewCapture(structure, /* forAutofill = */ true,
-                /* forViewCapture= */ false, flags);
+        onProvideStructure(structure, VIEW_STRUCTURE_FOR_AUTOFILL, flags);
     }
 
     /**
@@ -8150,13 +8173,13 @@
      * virtual views are rendered.
      */
     public boolean onProvideContentCaptureStructure(@NonNull ViewStructure structure, int flags) {
-        onProvideStructureForAssistOrAutofillOrViewCapture(structure, /* forAutofill = */ false,
-                /* forViewCapture= */ true, flags);
+        onProvideStructure(structure, VIEW_STRUCTURE_FOR_CONTENT_CAPTURE, flags);
         return true;
     }
 
-    private void onProvideStructureForAssistOrAutofillOrViewCapture(ViewStructure structure,
-            boolean forAutofill, boolean forViewCapture, @AutofillFlags int flags) {
+    /** @hide */
+    protected void onProvideStructure(@NonNull ViewStructure structure,
+            @ViewStructureType int viewFor, int flags) {
         final int id = mID;
         if (id != NO_ID && !isViewIdGenerated(id)) {
             String pkg, type, entry;
@@ -8172,11 +8195,13 @@
         } else {
             structure.setId(id, null, null, null);
         }
-        if (forViewCapture) {
+        if (viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
+            //TODO(b/111276913): STOPSHIP - don't set it if not needed
             structure.setDataIsSensitive(false);
         }
 
-        if (forAutofill || forViewCapture) {
+        if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
+                || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
             final @AutofillType int autofillType = getAutofillType();
             // Don't need to fill autofill info if view does not support it.
             // For example, only TextViews that are editable support autofill
@@ -8190,7 +8215,8 @@
 
         int ignoredParentLeft = 0;
         int ignoredParentTop = 0;
-        if (forAutofill && (flags & AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) == 0) {
+        if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
+                && (flags & AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) == 0) {
             View parentGroup = null;
 
             ViewParent viewParent = getParent();
@@ -8213,7 +8239,8 @@
 
         structure.setDimens(ignoredParentLeft + mLeft, ignoredParentTop + mTop, mScrollX, mScrollY,
                 mRight - mLeft, mBottom - mTop);
-        if (!forAutofill) {
+        if (viewFor == VIEW_STRUCTURE_FOR_ASSIST
+                || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
             if (!hasIdentityMatrix()) {
                 structure.setTransformation(getMatrix());
             }
@@ -8907,10 +8934,9 @@
     }
 
     /**
-     * Helper used to notify the {@link IntelligenceManager}anager when the view is removed or
+     * Helper used to notify the {@link IntelligenceManager} when the view is removed or
      * added, based on whether it's laid out and visible, and without knowing if the parent removed
-     * it from the view
-     * hierarchy.
+     * it from the view hierarchy.
      */
     // TODO(b/111276913): make sure the current algorithm covers all cases. For example, it should
     // probably be called every time notifyEnterOrExitForAutoFillIfNeeded() is called as well.
@@ -9084,7 +9110,7 @@
      * {@link #onProvideVirtualStructure}.
      */
     public void dispatchProvideStructure(ViewStructure structure) {
-        dispatchProvideStructureForAssistOrAutofill(structure, false, 0);
+        dispatchProvideStructure(structure, VIEW_STRUCTURE_FOR_ASSIST, /* flags= */ 0);
     }
 
     /**
@@ -9126,12 +9152,12 @@
      */
     public void dispatchProvideAutofillStructure(@NonNull ViewStructure structure,
             @AutofillFlags int flags) {
-        dispatchProvideStructureForAssistOrAutofill(structure, true, flags);
+        dispatchProvideStructure(structure, VIEW_STRUCTURE_FOR_AUTOFILL, flags);
     }
 
-    private void dispatchProvideStructureForAssistOrAutofill(ViewStructure structure,
-            boolean forAutofill, @AutofillFlags int flags) {
-        if (forAutofill) {
+    private void dispatchProvideStructure(@NonNull ViewStructure structure,
+            @ViewStructureType int viewFor, @AutofillFlags int flags) {
+        if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
             structure.setAutofillId(getAutofillId());
             onProvideAutofillStructure(structure, flags);
             onProvideAutofillVirtualStructure(structure, flags);
@@ -13949,6 +13975,96 @@
     }
 
     /**
+     * Dispatching hover events to {@link TouchDelegate} to improve accessibility.
+     * <p>
+     * This method is dispatching hover events to the delegate target to support explore by touch.
+     * Similar to {@link ViewGroup#dispatchTouchEvent}, this method send proper hover events to
+     * the delegate target according to the pointer and the touch area of the delegate while touch
+     * exploration enabled.
+     * </p>
+     *
+     * @param event The motion event dispatch to the delegate target.
+     * @return True if the event was handled, false otherwise.
+     *
+     * @see #onHoverEvent
+     */
+    private boolean dispatchTouchExplorationHoverEvent(MotionEvent event) {
+        final AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
+        if (!manager.isEnabled() || !manager.isTouchExplorationEnabled()) {
+            return false;
+        }
+
+        final boolean oldHoveringTouchDelegate = mHoveringTouchDelegate;
+        final int action = event.getActionMasked();
+        boolean pointInDelegateRegion = false;
+        boolean handled = false;
+
+        final AccessibilityNodeInfo.TouchDelegateInfo info = mTouchDelegate.getTouchDelegateInfo();
+        for (int i = 0; i < info.getRegionCount(); i++) {
+            Region r = info.getRegionAt(i);
+            if (r.contains((int) event.getX(), (int) event.getY())) {
+                pointInDelegateRegion = true;
+            }
+        }
+
+        // Explore by touch should dispatch events to children under the pointer first if any
+        // before dispatching to TouchDelegate. For non-hoverable views that do not consume
+        // hover events but receive accessibility focus, it should also not delegate to these
+        // views when hovered.
+        if (!oldHoveringTouchDelegate) {
+            if ((action == MotionEvent.ACTION_HOVER_ENTER
+                    || action == MotionEvent.ACTION_HOVER_MOVE)
+                    && !pointInHoveredChild(event)
+                    && pointInDelegateRegion) {
+                mHoveringTouchDelegate = true;
+            }
+        } else {
+            if (action == MotionEvent.ACTION_HOVER_EXIT
+                    || (action == MotionEvent.ACTION_HOVER_MOVE
+                        && (pointInHoveredChild(event) || !pointInDelegateRegion))) {
+                mHoveringTouchDelegate = false;
+            }
+        }
+        switch (action) {
+            case MotionEvent.ACTION_HOVER_MOVE:
+                if (oldHoveringTouchDelegate && mHoveringTouchDelegate) {
+                    // Inside bounds, dispatch as is.
+                    handled = mTouchDelegate.onTouchExplorationHoverEvent(event);
+                } else if (!oldHoveringTouchDelegate && mHoveringTouchDelegate) {
+                    // Moving inbound, synthesize hover enter.
+                    MotionEvent eventNoHistory = (event.getHistorySize() == 0)
+                            ? event : MotionEvent.obtainNoHistory(event);
+                    eventNoHistory.setAction(MotionEvent.ACTION_HOVER_ENTER);
+                    handled = mTouchDelegate.onTouchExplorationHoverEvent(eventNoHistory);
+                    eventNoHistory.setAction(action);
+                    handled |= mTouchDelegate.onTouchExplorationHoverEvent(eventNoHistory);
+                } else if (oldHoveringTouchDelegate && !mHoveringTouchDelegate) {
+                    // Moving outbound, synthesize hover exit.
+                    final boolean hoverExitPending = event.isHoverExitPending();
+                    event.setHoverExitPending(true);
+                    mTouchDelegate.onTouchExplorationHoverEvent(event);
+                    MotionEvent eventNoHistory = (event.getHistorySize() == 0)
+                            ? event : MotionEvent.obtainNoHistory(event);
+                    eventNoHistory.setHoverExitPending(hoverExitPending);
+                    eventNoHistory.setAction(MotionEvent.ACTION_HOVER_EXIT);
+                    mTouchDelegate.onTouchExplorationHoverEvent(eventNoHistory);
+                }  // else: outside bounds, do nothing.
+                break;
+            case MotionEvent.ACTION_HOVER_ENTER:
+                if (!oldHoveringTouchDelegate && mHoveringTouchDelegate) {
+                    handled = mTouchDelegate.onTouchExplorationHoverEvent(event);
+                }
+                break;
+            case MotionEvent.ACTION_HOVER_EXIT:
+                if (oldHoveringTouchDelegate) {
+                    mTouchDelegate.onTouchExplorationHoverEvent(event);
+                }
+                break;
+        }
+        return handled;
+    }
+
+    /**
      * Implement this method to handle hover events.
      * <p>
      * This method is called whenever a pointer is hovering into, over, or out of the
@@ -13985,15 +14101,8 @@
      * @see #onHoverChanged
      */
     public boolean onHoverEvent(MotionEvent event) {
-        // Explore by touch should dispatch events to children under pointer first if any before
-        // dispatching to TouchDelegate. For children non-hoverable that will not consume events,
-        // it should also not delegate when they got the pointer hovered.
-        if (mTouchDelegate != null && !pointInHoveredChild(event)) {
-            final AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
-            if (manager.isEnabled() && manager.isTouchExplorationEnabled()
-                    && mTouchDelegate.onTouchExplorationHoverEvent(event)) {
-                return true;
-            }
+        if (mTouchDelegate != null && dispatchTouchExplorationHoverEvent(event)) {
+            return true;
         }
 
         // The root view may receive hover (or touch) events that are outside the bounds of
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index a23d68b..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
@@ -2695,7 +2702,7 @@
         }
     }
 
-    private void handleWindowFocusChanged() {
+    private void handleWindowFocusChanged(boolean reportToClient) {
         final boolean hasWindowFocus;
         final boolean inTouchMode;
         synchronized (this) {
@@ -2730,8 +2737,9 @@
                         } catch (RemoteException ex) {
                         }
                         // Retry in a bit.
-                        mHandler.sendMessageDelayed(mHandler.obtainMessage(
-                                MSG_WINDOW_FOCUS_CHANGED), 500);
+                        final Message msg = mHandler.obtainMessage(MSG_WINDOW_FOCUS_CHANGED);
+                        msg.arg1 = reportToClient ? 1 : 0;
+                        mHandler.sendMessageDelayed(msg, 500);
                         return;
                     }
                 }
@@ -2748,9 +2756,15 @@
             }
             if (mView != null) {
                 mAttachInfo.mKeyDispatchState.reset();
-                mView.dispatchWindowFocusChanged(hasWindowFocus);
-                mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
-
+                // We dispatch onWindowFocusChanged to child view only when window is gaining /
+                // losing focus.
+                // If the focus is updated from top display change but window focus on the display
+                // remains unchanged, will not callback onWindowFocusChanged again since it may
+                // be redundant & can affect the state when it callbacks.
+                if (reportToClient) {
+                    mView.dispatchWindowFocusChanged(hasWindowFocus);
+                    mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
+                }
                 if (mAttachInfo.mTooltipHost != null) {
                     mAttachInfo.mTooltipHost.hideTooltip();
                 }
@@ -4340,7 +4354,7 @@
                     }
                     break;
                 case MSG_WINDOW_FOCUS_CHANGED: {
-                    handleWindowFocusChanged();
+                    handleWindowFocusChanged(msg.arg1 != 0 /* reportToClient */);
                 } break;
                 case MSG_DIE:
                     doDie();
@@ -7263,7 +7277,7 @@
         }
 
         if (stage != null) {
-            handleWindowFocusChanged();
+            handleWindowFocusChanged(true /* reportToClient */);
             stage.deliver(q);
         } else {
             finishInputEvent(q);
@@ -7580,6 +7594,11 @@
     }
 
     public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
+        windowFocusChanged(hasFocus, inTouchMode, true /* reportToClient */);
+    }
+
+    public void windowFocusChanged(boolean hasFocus, boolean inTouchMode,
+            boolean reportToClient) {
         synchronized (this) {
             mWindowFocusChanged = true;
             mUpcomingWindowFocus = hasFocus;
@@ -7587,6 +7606,7 @@
         }
         Message msg = Message.obtain();
         msg.what = MSG_WINDOW_FOCUS_CHANGED;
+        msg.arg1 = reportToClient ? 1 : 0;
         mHandler.sendMessage(msg);
     }
 
@@ -8131,10 +8151,11 @@
         }
 
         @Override
-        public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
+        public void windowFocusChanged(boolean hasFocus, boolean inTouchMode,
+                boolean reportToClient) {
             final ViewRootImpl viewAncestor = mViewAncestor.get();
             if (viewAncestor != null) {
-                viewAncestor.windowFocusChanged(hasFocus, inTouchMode);
+                viewAncestor.windowFocusChanged(hasFocus, inTouchMode, reportToClient);
             }
         }
 
diff --git a/core/java/android/view/intelligence/IntelligenceManager.java b/core/java/android/view/intelligence/IntelligenceManager.java
index c02fb32..242bf6a 100644
--- a/core/java/android/view/intelligence/IntelligenceManager.java
+++ b/core/java/android/view/intelligence/IntelligenceManager.java
@@ -86,6 +86,13 @@
      */
     public static final int STATE_ACTIVE = 2;
 
+    /**
+     * Session is disabled.
+     *
+     * @hide
+     */
+    public static final int STATE_DISABLED = 3;
+
     private static final String BG_THREAD_NAME = "intel_svc_streamer_thread";
 
     /**
@@ -166,13 +173,7 @@
                             public void send(int resultCode, Bundle resultData)
                                     throws RemoteException {
                                 synchronized (mLock) {
-                                    if (resultCode > 0) {
-                                        mState = STATE_ACTIVE;
-                                    } else {
-                                        // TODO(b/111276913): handle other cases like disabled by
-                                        // service
-                                        resetStateLocked();
-                                    }
+                                    mState = resultCode;
                                     if (VERBOSE) {
                                         Log.v(TAG, "onActivityStarted() result: code=" + resultCode
                                                 + ", id=" + mId
@@ -195,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();
@@ -203,9 +205,13 @@
                     // Typically happens on system apps that are started before the system service
                     // is ready (like com.android.settings/.FallbackHome)
                     //TODO(b/111276913): try to ignore session while system is not ready / boot
-                    // not complete instead.
-                    Log.w(TAG, "Closing session for " + getActivityDebugNameLocked()
-                            + " after " + numberEvents + " delayed events");
+                    // not complete instead. Similarly, the manager service should return right away
+                    // when the user does not have a service set
+                    if (VERBOSE) {
+                        Log.v(TAG, "Closing session for " + getActivityDebugNameLocked()
+                                + " after " + numberEvents + " delayed events and state "
+                                + getStateAsString(mState));
+                    }
                     // TODO(b/111276913): blacklist activity / use special flag to indicate that
                     // when it's launched again
                     resetStateLocked();
@@ -380,7 +386,7 @@
         //TODO(b/111276913): properly implement by checking if it was explicitly disabled by
         // service, or if service is not set
         // (and probably renamign to isEnabledLocked()
-        return mService != null;
+        return mService != null && mState != STATE_DISABLED;
     }
 
     /**
@@ -509,6 +515,8 @@
                 return "WAITING_FOR_SERVER";
             case STATE_ACTIVE:
                 return "ACTIVE";
+            case STATE_DISABLED:
+                return "DISABLED";
             default:
                 return "INVALID:" + state;
         }
diff --git a/core/java/android/view/textclassifier/ModelFileManager.java b/core/java/android/view/textclassifier/ModelFileManager.java
index 896b516..8558a46 100644
--- a/core/java/android/view/textclassifier/ModelFileManager.java
+++ b/core/java/android/view/textclassifier/ModelFileManager.java
@@ -251,6 +251,9 @@
             if (!mLanguageIndependent && model.mLanguageIndependent) {
                 return true;
             }
+            if (mLanguageIndependent && !model.mLanguageIndependent) {
+                return false;
+            }
 
             // A higher-version model is preferred.
             if (mVersion > model.getVersion()) {
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index f3fe16e..ddff858 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -1309,13 +1309,23 @@
     @Override
     public void onProvideAutofillStructure(ViewStructure structure, int flags) {
         super.onProvideAutofillStructure(structure, flags);
+    }
 
-        final Adapter adapter = getAdapter();
-        if (adapter == null) return;
+    /** @hide */
+    @Override
+    protected void onProvideStructure(@NonNull ViewStructure structure,
+            @ViewStructureType int viewFor, int flags) {
+        super.onProvideStructure(structure, viewFor, flags);
 
-        final CharSequence[] options = adapter.getAutofillOptions();
-        if (options != null) {
-            structure.setAutofillOptions(options);
+        if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
+                || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
+            final Adapter adapter = getAdapter();
+            if (adapter == null) return;
+
+            final CharSequence[] options = adapter.getAutofillOptions();
+            if (options != null) {
+                structure.setAutofillOptions(options);
+            }
         }
     }
 }
diff --git a/core/java/android/widget/AppSecurityPermissions.java b/core/java/android/widget/AppSecurityPermissions.java
index b754d84..eb35587 100644
--- a/core/java/android/widget/AppSecurityPermissions.java
+++ b/core/java/android/widget/AppSecurityPermissions.java
@@ -16,302 +16,22 @@
 */
 package android.widget;
 
-import android.annotation.UnsupportedAppUsage;
-import android.app.AlertDialog;
 import android.content.Context;
-import android.content.DialogInterface;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageItemInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PermissionGroupInfo;
-import android.content.pm.PermissionInfo;
 import android.graphics.drawable.Drawable;
-import android.os.Parcel;
-import android.os.UserHandle;
-import android.text.SpannableStringBuilder;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
 
 import com.android.internal.R;
 
-import java.text.Collator;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
 /**
- * This class contains the SecurityPermissions view implementation.
- * Initially the package's advanced or dangerous security permissions
- * are displayed under categorized
- * groups. Clicking on the additional permissions presents
- * extended information consisting of all groups and permissions.
- * To use this view define a LinearLayout or any ViewGroup and add this
- * view by instantiating AppSecurityPermissions and invoking getPermissionsView.
+ * Allows the device admin to show certain dialogs. Should be integrated into settings.
  *
+ * @deprecated
  * {@hide}
  */
+@Deprecated
 public class AppSecurityPermissions {
 
-    public static final int WHICH_NEW = 1<<2;
-    public static final int WHICH_ALL = 0xffff;
-
-    private final static String TAG = "AppSecurityPermissions";
-    private final static boolean localLOGV = false;
-    private final Context mContext;
-    private final LayoutInflater mInflater;
-    private final PackageManager mPm;
-    private final Map<String, MyPermissionGroupInfo> mPermGroups
-            = new HashMap<String, MyPermissionGroupInfo>();
-    private final List<MyPermissionGroupInfo> mPermGroupsList
-            = new ArrayList<MyPermissionGroupInfo>();
-    private final PermissionGroupInfoComparator mPermGroupComparator =
-            new PermissionGroupInfoComparator();
-    private final PermissionInfoComparator mPermComparator = new PermissionInfoComparator();
-    private final List<MyPermissionInfo> mPermsList = new ArrayList<MyPermissionInfo>();
-    private final CharSequence mNewPermPrefix;
-    private String mPackageName;
-
-    /** @hide */
-    static class MyPermissionGroupInfo extends PermissionGroupInfo {
-        CharSequence mLabel;
-
-        final ArrayList<MyPermissionInfo> mNewPermissions = new ArrayList<MyPermissionInfo>();
-        final ArrayList<MyPermissionInfo> mAllPermissions = new ArrayList<MyPermissionInfo>();
-
-        MyPermissionGroupInfo(PermissionInfo perm) {
-            name = perm.packageName;
-            packageName = perm.packageName;
-        }
-
-        MyPermissionGroupInfo(PermissionGroupInfo info) {
-            super(info);
-        }
-
-        public Drawable loadGroupIcon(Context context, PackageManager pm) {
-            if (icon != 0) {
-                return loadUnbadgedIcon(pm);
-            } else {
-                return context.getDrawable(R.drawable.ic_perm_device_info);
-            }
-        }
-    }
-
-    /** @hide */
-    private static class MyPermissionInfo extends PermissionInfo {
-        CharSequence mLabel;
-
-        /**
-         * PackageInfo.requestedPermissionsFlags for the new package being installed.
-         */
-        int mNewReqFlags;
-
-        /**
-         * PackageInfo.requestedPermissionsFlags for the currently installed
-         * package, if it is installed.
-         */
-        int mExistingReqFlags;
-
-        /**
-         * True if this should be considered a new permission.
-         */
-        boolean mNew;
-
-        MyPermissionInfo(PermissionInfo info) {
-            super(info);
-        }
-    }
-
-    /** @hide */
-    public static class PermissionItemView extends LinearLayout implements View.OnClickListener {
-        MyPermissionGroupInfo mGroup;
-        MyPermissionInfo mPerm;
-        AlertDialog mDialog;
-        private boolean mShowRevokeUI = false;
-        private String mPackageName;
-
-        public PermissionItemView(Context context, AttributeSet attrs) {
-            super(context, attrs);
-            setClickable(true);
-        }
-
-        public void setPermission(MyPermissionGroupInfo grp, MyPermissionInfo perm,
-                boolean first, CharSequence newPermPrefix, String packageName,
-                boolean showRevokeUI) {
-            mGroup = grp;
-            mPerm = perm;
-            mShowRevokeUI = showRevokeUI;
-            mPackageName = packageName;
-
-            ImageView permGrpIcon = findViewById(R.id.perm_icon);
-            TextView permNameView = findViewById(R.id.perm_name);
-
-            PackageManager pm = getContext().getPackageManager();
-            Drawable icon = null;
-            if (first) {
-                icon = grp.loadGroupIcon(getContext(), pm);
-            }
-            CharSequence label = perm.mLabel;
-            if (perm.mNew && newPermPrefix != null) {
-                // If this is a new permission, format it appropriately.
-                SpannableStringBuilder builder = new SpannableStringBuilder();
-                Parcel parcel = Parcel.obtain();
-                TextUtils.writeToParcel(newPermPrefix, parcel, 0);
-                parcel.setDataPosition(0);
-                CharSequence newStr = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
-                parcel.recycle();
-                builder.append(newStr);
-                builder.append(label);
-                label = builder;
-            }
-
-            permGrpIcon.setImageDrawable(icon);
-            permNameView.setText(label);
-            setOnClickListener(this);
-            if (localLOGV) Log.i(TAG, "Made perm item " + perm.name
-                    + ": " + label + " in group " + grp.name);
-        }
-
-        @Override
-        public void onClick(View v) {
-            if (mGroup != null && mPerm != null) {
-                if (mDialog != null) {
-                    mDialog.dismiss();
-                }
-                PackageManager pm = getContext().getPackageManager();
-                AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
-                builder.setTitle(mGroup.mLabel);
-                if (mPerm.descriptionRes != 0) {
-                    builder.setMessage(mPerm.loadDescription(pm));
-                } else {
-                    CharSequence appName;
-                    try {
-                        ApplicationInfo app = pm.getApplicationInfo(mPerm.packageName, 0);
-                        appName = app.loadLabel(pm);
-                    } catch (NameNotFoundException e) {
-                        appName = mPerm.packageName;
-                    }
-                    StringBuilder sbuilder = new StringBuilder(128);
-                    sbuilder.append(getContext().getString(
-                            R.string.perms_description_app, appName));
-                    sbuilder.append("\n\n");
-                    sbuilder.append(mPerm.name);
-                    builder.setMessage(sbuilder.toString());
-                }
-                builder.setCancelable(true);
-                builder.setIcon(mGroup.loadGroupIcon(getContext(), pm));
-                addRevokeUIIfNecessary(builder);
-                mDialog = builder.show();
-                mDialog.setCanceledOnTouchOutside(true);
-            }
-        }
-
-        @Override
-        protected void onDetachedFromWindow() {
-            super.onDetachedFromWindow();
-            if (mDialog != null) {
-                mDialog.dismiss();
-            }
-        }
-
-        private void addRevokeUIIfNecessary(AlertDialog.Builder builder) {
-            if (!mShowRevokeUI) {
-                return;
-            }
-
-            final boolean isRequired =
-                    ((mPerm.mExistingReqFlags & PackageInfo.REQUESTED_PERMISSION_REQUIRED) != 0);
-
-            if (isRequired) {
-                return;
-            }
-
-            DialogInterface.OnClickListener ocl = new DialogInterface.OnClickListener() {
-                @Override
-                public void onClick(DialogInterface dialog, int which) {
-                    PackageManager pm = getContext().getPackageManager();
-                    pm.revokeRuntimePermission(mPackageName, mPerm.name,
-                            new UserHandle(mContext.getUserId()));
-                    PermissionItemView.this.setVisibility(View.GONE);
-                }
-            };
-            builder.setNegativeButton(R.string.revoke, ocl);
-            builder.setPositiveButton(R.string.ok, null);
-        }
-    }
-
-    private AppSecurityPermissions(Context context) {
-        mContext = context;
-        mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        mPm = mContext.getPackageManager();
-        // Pick up from framework resources instead.
-        mNewPermPrefix = mContext.getText(R.string.perms_new_perm_prefix);
-    }
-
-    @UnsupportedAppUsage
-    public AppSecurityPermissions(Context context, String packageName) {
-        this(context);
-        mPackageName = packageName;
-        Set<MyPermissionInfo> permSet = new HashSet<MyPermissionInfo>();
-        PackageInfo pkgInfo;
-        try {
-            pkgInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
-        } catch (NameNotFoundException e) {
-            Log.w(TAG, "Couldn't retrieve permissions for package:"+packageName);
-            return;
-        }
-        // Extract all user permissions
-        if((pkgInfo.applicationInfo != null) && (pkgInfo.applicationInfo.uid != -1)) {
-            getAllUsedPermissions(pkgInfo.applicationInfo.uid, permSet);
-        }
-        mPermsList.addAll(permSet);
-        setPermissions(mPermsList);
-    }
-
-    public AppSecurityPermissions(Context context, PackageInfo info) {
-        this(context);
-        Set<MyPermissionInfo> permSet = new HashSet<MyPermissionInfo>();
-        if(info == null) {
-            return;
-        }
-        mPackageName = info.packageName;
-
-        // Convert to a PackageInfo
-        PackageInfo installedPkgInfo = null;
-        // Get requested permissions
-        if (info.requestedPermissions != null) {
-            try {
-                installedPkgInfo = mPm.getPackageInfo(info.packageName,
-                        PackageManager.GET_PERMISSIONS);
-            } catch (NameNotFoundException e) {
-            }
-            extractPerms(info, permSet, installedPkgInfo);
-        }
-        // Get permissions related to shared user if any
-        if (info.sharedUserId != null) {
-            int sharedUid;
-            try {
-                sharedUid = mPm.getUidForSharedUser(info.sharedUserId);
-                getAllUsedPermissions(sharedUid, permSet);
-            } catch (NameNotFoundException e) {
-                Log.w(TAG, "Couldn't retrieve shared user id for: " + info.packageName);
-            }
-        }
-        // Retrieve list of permissions
-        mPermsList.addAll(permSet);
-        setPermissions(mPermsList);
-    }
-
     /**
      * Utility to retrieve a view displaying a single permission.  This provides
      * the old UI layout for permissions; it is only here for the device admin
@@ -327,197 +47,6 @@
                 description, dangerous, icon);
     }
 
-    private void getAllUsedPermissions(int sharedUid, Set<MyPermissionInfo> permSet) {
-        String sharedPkgList[] = mPm.getPackagesForUid(sharedUid);
-        if(sharedPkgList == null || (sharedPkgList.length == 0)) {
-            return;
-        }
-        for(String sharedPkg : sharedPkgList) {
-            getPermissionsForPackage(sharedPkg, permSet);
-        }
-    }
-
-    private void getPermissionsForPackage(String packageName, Set<MyPermissionInfo> permSet) {
-        try {
-            PackageInfo pkgInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
-            extractPerms(pkgInfo, permSet, pkgInfo);
-        } catch (NameNotFoundException e) {
-            Log.w(TAG, "Couldn't retrieve permissions for package: " + packageName);
-        }
-    }
-
-    private void extractPerms(PackageInfo info, Set<MyPermissionInfo> permSet,
-            PackageInfo installedPkgInfo) {
-        String[] strList = info.requestedPermissions;
-        int[] flagsList = info.requestedPermissionsFlags;
-        if ((strList == null) || (strList.length == 0)) {
-            return;
-        }
-        for (int i=0; i<strList.length; i++) {
-            String permName = strList[i];
-            try {
-                PermissionInfo tmpPermInfo = mPm.getPermissionInfo(permName, 0);
-                if (tmpPermInfo == null) {
-                    continue;
-                }
-                int existingIndex = -1;
-                if (installedPkgInfo != null
-                        && installedPkgInfo.requestedPermissions != null) {
-                    for (int j=0; j<installedPkgInfo.requestedPermissions.length; j++) {
-                        if (permName.equals(installedPkgInfo.requestedPermissions[j])) {
-                            existingIndex = j;
-                            break;
-                        }
-                    }
-                }
-                final int existingFlags = existingIndex >= 0 ?
-                        installedPkgInfo.requestedPermissionsFlags[existingIndex] : 0;
-                if (!isDisplayablePermission(tmpPermInfo, flagsList[i], existingFlags)) {
-                    // This is not a permission that is interesting for the user
-                    // to see, so skip it.
-                    continue;
-                }
-                final String origGroupName = tmpPermInfo.group;
-                String groupName = origGroupName;
-                if (groupName == null) {
-                    groupName = tmpPermInfo.packageName;
-                    tmpPermInfo.group = groupName;
-                }
-                MyPermissionGroupInfo group = mPermGroups.get(groupName);
-                if (group == null) {
-                    PermissionGroupInfo grp = null;
-                    if (origGroupName != null) {
-                        grp = mPm.getPermissionGroupInfo(origGroupName, 0);
-                    }
-                    if (grp != null) {
-                        group = new MyPermissionGroupInfo(grp);
-                    } else {
-                        // We could be here either because the permission
-                        // didn't originally specify a group or the group it
-                        // gave couldn't be found.  In either case, we consider
-                        // its group to be the permission's package name.
-                        tmpPermInfo.group = tmpPermInfo.packageName;
-                        group = mPermGroups.get(tmpPermInfo.group);
-                        if (group == null) {
-                            group = new MyPermissionGroupInfo(tmpPermInfo);
-                        }
-                        group = new MyPermissionGroupInfo(tmpPermInfo);
-                    }
-                    mPermGroups.put(tmpPermInfo.group, group);
-                }
-                final boolean newPerm = installedPkgInfo != null
-                        && (existingFlags&PackageInfo.REQUESTED_PERMISSION_GRANTED) == 0;
-                MyPermissionInfo myPerm = new MyPermissionInfo(tmpPermInfo);
-                myPerm.mNewReqFlags = flagsList[i];
-                myPerm.mExistingReqFlags = existingFlags;
-                // This is a new permission if the app is already installed and
-                // doesn't currently hold this permission.
-                myPerm.mNew = newPerm;
-                permSet.add(myPerm);
-            } catch (NameNotFoundException e) {
-                Log.i(TAG, "Ignoring unknown permission:"+permName);
-            }
-        }
-    }
-
-    @UnsupportedAppUsage
-    public int getPermissionCount() {
-        return getPermissionCount(WHICH_ALL);
-    }
-
-    private List<MyPermissionInfo> getPermissionList(MyPermissionGroupInfo grp, int which) {
-        if (which == WHICH_NEW) {
-            return grp.mNewPermissions;
-        } else {
-            return grp.mAllPermissions;
-        }
-    }
-
-    public int getPermissionCount(int which) {
-        int N = 0;
-        for (int i=0; i<mPermGroupsList.size(); i++) {
-            N += getPermissionList(mPermGroupsList.get(i), which).size();
-        }
-        return N;
-    }
-
-    @UnsupportedAppUsage
-    public View getPermissionsView() {
-        return getPermissionsView(WHICH_ALL, false);
-    }
-
-    public View getPermissionsViewWithRevokeButtons() {
-        return getPermissionsView(WHICH_ALL, true);
-    }
-
-    public View getPermissionsView(int which) {
-        return getPermissionsView(which, false);
-    }
-
-    private View getPermissionsView(int which, boolean showRevokeUI) {
-        LinearLayout permsView = (LinearLayout) mInflater.inflate(R.layout.app_perms_summary, null);
-        LinearLayout displayList = permsView.findViewById(R.id.perms_list);
-        View noPermsView = permsView.findViewById(R.id.no_permissions);
-
-        displayPermissions(mPermGroupsList, displayList, which, showRevokeUI);
-        if (displayList.getChildCount() <= 0) {
-            noPermsView.setVisibility(View.VISIBLE);
-        }
-
-        return permsView;
-    }
-
-    /**
-     * Utility method that displays permissions from a map containing group name and
-     * list of permission descriptions.
-     */
-    private void displayPermissions(List<MyPermissionGroupInfo> groups,
-            LinearLayout permListView, int which, boolean showRevokeUI) {
-        permListView.removeAllViews();
-
-        int spacing = (int)(8*mContext.getResources().getDisplayMetrics().density);
-
-        for (int i=0; i<groups.size(); i++) {
-            MyPermissionGroupInfo grp = groups.get(i);
-            final List<MyPermissionInfo> perms = getPermissionList(grp, which);
-            for (int j=0; j<perms.size(); j++) {
-                MyPermissionInfo perm = perms.get(j);
-                View view = getPermissionItemView(grp, perm, j == 0,
-                        which != WHICH_NEW ? mNewPermPrefix : null, showRevokeUI);
-                LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
-                        ViewGroup.LayoutParams.MATCH_PARENT,
-                        ViewGroup.LayoutParams.WRAP_CONTENT);
-                if (j == 0) {
-                    lp.topMargin = spacing;
-                }
-                if (j == grp.mAllPermissions.size()-1) {
-                    lp.bottomMargin = spacing;
-                }
-                if (permListView.getChildCount() == 0) {
-                    lp.topMargin *= 2;
-                }
-                permListView.addView(view, lp);
-            }
-        }
-    }
-
-    private PermissionItemView getPermissionItemView(MyPermissionGroupInfo grp,
-            MyPermissionInfo perm, boolean first, CharSequence newPermPrefix, boolean showRevokeUI) {
-        return getPermissionItemView(mContext, mInflater, grp, perm, first, newPermPrefix,
-                mPackageName, showRevokeUI);
-    }
-
-    private static PermissionItemView getPermissionItemView(Context context, LayoutInflater inflater,
-            MyPermissionGroupInfo grp, MyPermissionInfo perm, boolean first,
-            CharSequence newPermPrefix, String packageName, boolean showRevokeUI) {
-            PermissionItemView permView = (PermissionItemView)inflater.inflate(
-                (perm.flags & PermissionInfo.FLAG_COSTS_MONEY) != 0
-                        ? R.layout.app_permission_item_money : R.layout.app_permission_item,
-                null);
-        permView.setPermission(grp, perm, first, newPermPrefix, packageName, showRevokeUI);
-        return permView;
-    }
-
     private static View getPermissionItemViewOld(Context context, LayoutInflater inflater,
             CharSequence grpName, CharSequence permList, boolean dangerous, Drawable icon) {
         View permView = inflater.inflate(R.layout.app_permission_item_old, null);
@@ -536,116 +65,4 @@
         }
         return permView;
     }
-
-    private boolean isDisplayablePermission(PermissionInfo pInfo, int newReqFlags,
-            int existingReqFlags) {
-        final int base = pInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
-        final boolean isNormal = (base == PermissionInfo.PROTECTION_NORMAL);
-
-        // We do not show normal permissions in the UI.
-        if (isNormal) {
-            return false;
-        }
-
-        final boolean isDangerous = (base == PermissionInfo.PROTECTION_DANGEROUS)
-                || ((pInfo.protectionLevel&PermissionInfo.PROTECTION_FLAG_PRE23) != 0);
-        final boolean isRequired =
-                ((newReqFlags&PackageInfo.REQUESTED_PERMISSION_REQUIRED) != 0);
-        final boolean isDevelopment =
-                ((pInfo.protectionLevel&PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0);
-        final boolean wasGranted =
-                ((existingReqFlags&PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0);
-        final boolean isGranted =
-                ((newReqFlags&PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0);
-
-        // Dangerous and normal permissions are always shown to the user if the permission
-        // is required, or it was previously granted
-        if (isDangerous && (isRequired || wasGranted || isGranted)) {
-            return true;
-        }
-
-        // Development permissions are only shown to the user if they are already
-        // granted to the app -- if we are installing an app and they are not
-        // already granted, they will not be granted as part of the install.
-        if (isDevelopment && wasGranted) {
-            if (localLOGV) Log.i(TAG, "Special perm " + pInfo.name
-                    + ": protlevel=0x" + Integer.toHexString(pInfo.protectionLevel));
-            return true;
-        }
-        return false;
-    }
-
-    private static class PermissionGroupInfoComparator implements Comparator<MyPermissionGroupInfo> {
-        private final Collator sCollator = Collator.getInstance();
-        @Override
-        public final int compare(MyPermissionGroupInfo a, MyPermissionGroupInfo b) {
-            return sCollator.compare(a.mLabel, b.mLabel);
-        }
-    }
-
-    private static class PermissionInfoComparator implements Comparator<MyPermissionInfo> {
-        private final Collator sCollator = Collator.getInstance();
-        PermissionInfoComparator() {
-        }
-        public final int compare(MyPermissionInfo a, MyPermissionInfo b) {
-            return sCollator.compare(a.mLabel, b.mLabel);
-        }
-    }
-
-    private void addPermToList(List<MyPermissionInfo> permList,
-            MyPermissionInfo pInfo) {
-        if (pInfo.mLabel == null) {
-            pInfo.mLabel = pInfo.loadSafeLabel(mPm, 20000, PackageItemInfo.SAFE_LABEL_FLAG_TRIM
-                    | PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE);
-        }
-        int idx = Collections.binarySearch(permList, pInfo, mPermComparator);
-        if(localLOGV) Log.i(TAG, "idx="+idx+", list.size="+permList.size());
-        if (idx < 0) {
-            idx = -idx-1;
-            permList.add(idx, pInfo);
-        }
-    }
-
-    private void setPermissions(List<MyPermissionInfo> permList) {
-        if (permList != null) {
-            // First pass to group permissions
-            for (MyPermissionInfo pInfo : permList) {
-                if(localLOGV) Log.i(TAG, "Processing permission:"+pInfo.name);
-                if(!isDisplayablePermission(pInfo, pInfo.mNewReqFlags, pInfo.mExistingReqFlags)) {
-                    if(localLOGV) Log.i(TAG, "Permission:"+pInfo.name+" is not displayable");
-                    continue;
-                }
-                MyPermissionGroupInfo group = mPermGroups.get(pInfo.group);
-                if (group != null) {
-                    pInfo.mLabel = pInfo.loadSafeLabel(mPm, 20000,
-                            PackageItemInfo.SAFE_LABEL_FLAG_TRIM
-                            | PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE);
-                    addPermToList(group.mAllPermissions, pInfo);
-                    if (pInfo.mNew) {
-                        addPermToList(group.mNewPermissions, pInfo);
-                    }
-                }
-            }
-        }
-
-        for (MyPermissionGroupInfo pgrp : mPermGroups.values()) {
-            if (pgrp.labelRes != 0 || pgrp.nonLocalizedLabel != null) {
-                pgrp.mLabel = pgrp.loadSafeLabel(mPm, 20000, PackageItemInfo.SAFE_LABEL_FLAG_TRIM
-                        | PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE);
-            } else {
-                ApplicationInfo app;
-                try {
-                    app = mPm.getApplicationInfo(pgrp.packageName, 0);
-                    pgrp.mLabel = app.loadSafeLabel(mPm, 20000, PackageItemInfo.SAFE_LABEL_FLAG_TRIM
-                            | PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE);
-                } catch (NameNotFoundException e) {
-                    pgrp.mLabel = pgrp.loadSafeLabel(mPm, 20000,
-                            PackageItemInfo.SAFE_LABEL_FLAG_TRIM
-                            | PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE);
-                }
-            }
-            mPermGroupsList.add(pgrp);
-        }
-        Collections.sort(mPermGroupsList, mPermGroupComparator);
-    }
 }
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index 8d09489..d35bec8 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -578,11 +578,16 @@
         stream.addProperty("checked", isChecked());
     }
 
-    @Override
-    public void onProvideAutofillStructure(ViewStructure structure, int flags) {
-        super.onProvideAutofillStructure(structure, flags);
 
-        structure.setDataIsSensitive(!mCheckedFromResource);
+    /** @hide */
+    @Override
+    protected void onProvideStructure(@NonNull ViewStructure structure,
+            @ViewStructureType int viewFor, int flags) {
+        super.onProvideStructure(structure, viewFor, flags);
+
+        if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
+            structure.setDataIsSensitive(!mCheckedFromResource);
+        }
     }
 
     @Override
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/RadioGroup.java b/core/java/android/widget/RadioGroup.java
index 327a5c1..ab12eac 100644
--- a/core/java/android/widget/RadioGroup.java
+++ b/core/java/android/widget/RadioGroup.java
@@ -17,6 +17,7 @@
 package android.widget;
 
 import android.annotation.IdRes;
+import android.annotation.NonNull;
 import android.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.TypedArray;
@@ -424,10 +425,15 @@
         }
     }
 
+    /** @hide */
     @Override
-    public void onProvideAutofillStructure(ViewStructure structure, int flags) {
-        super.onProvideAutofillStructure(structure, flags);
-        structure.setDataIsSensitive(mCheckedId != mInitialCheckedId);
+    protected void onProvideStructure(@NonNull ViewStructure structure,
+            @ViewStructureType int viewFor, int flags) {
+        super.onProvideStructure(structure, viewFor, flags);
+
+        if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
+            structure.setDataIsSensitive(mCheckedId != mInitialCheckedId);
+        }
     }
 
     @Override
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 3bdd7b8..35be766 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -6076,7 +6076,7 @@
         if (needEditableForNotification) {
             sendAfterTextChanged((Editable) text);
         } else {
-            notifyManagersAfterTextChanged();
+            notifyListeningManagersAfterTextChanged();
         }
 
         // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
@@ -10124,13 +10124,16 @@
             }
         }
 
-        // Always notify AutoFillManager - it will return right away if autofill is disabled.
-        notifyManagersAfterTextChanged();
+        notifyListeningManagersAfterTextChanged();
 
         hideErrorIfUnchanged();
     }
 
-    private void notifyManagersAfterTextChanged() {
+    /**
+     * Notify managers (such as {@link AutofillManager} and {@link IntelligenceManager}) that are
+     * interested on text changes.
+     */
+    private void notifyListeningManagersAfterTextChanged() {
 
         // Autofill
         if (isAutofillable()) {
@@ -10911,34 +10914,17 @@
         return TextView.class.getName();
     }
 
+    /** @hide */
     @Override
-    public void onProvideStructure(ViewStructure structure) {
-        super.onProvideStructure(structure);
-        onProvideStructureForAssistOrAutofillOrViewCapture(structure, /* forAutofill = */ false,
-                /* forViewCapture= */ false);
-    }
+    protected void onProvideStructure(@NonNull ViewStructure structure,
+            @ViewStructureType int viewFor, int flags) {
+        super.onProvideStructure(structure, viewFor, flags);
 
-    @Override
-    public void onProvideAutofillStructure(ViewStructure structure, int flags) {
-        super.onProvideAutofillStructure(structure, flags);
-        onProvideStructureForAssistOrAutofillOrViewCapture(structure, /* forAutofill = */ true,
-                /* forViewCapture= */ false);
-    }
-
-    @Override
-    public boolean onProvideContentCaptureStructure(ViewStructure structure, int flags) {
-        final boolean notifyManager = super.onProvideContentCaptureStructure(structure, flags);
-        onProvideStructureForAssistOrAutofillOrViewCapture(structure, /* forAutofill = */ false,
-                /* forViewCapture= */ true);
-        return notifyManager;
-    }
-
-    private void onProvideStructureForAssistOrAutofillOrViewCapture(ViewStructure structure,
-            boolean forAutofill, boolean forViewCapture) {
         final boolean isPassword = hasPasswordTransformationMethod()
                 || isPasswordInputType(getInputType());
-        if (forAutofill || forViewCapture) {
-            if (forAutofill) {
+        if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
+                || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
+            if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
                 structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId);
             }
             if (mTextId != ResourceId.ID_NULL) {
@@ -10953,7 +10939,8 @@
             }
         }
 
-        if (!isPassword || forAutofill || forViewCapture) {
+        if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
+                || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
             if (mLayout == null) {
                 assumeLayout();
             }
@@ -10962,7 +10949,7 @@
             if (lineCount <= 1) {
                 // Simple case: this is a single line.
                 final CharSequence text = getText();
-                if (forAutofill) {
+                if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
                     structure.setText(text);
                 } else {
                     structure.setText(text, getSelectionStart(), getSelectionEnd());
@@ -11026,7 +11013,7 @@
                     text = text.subSequence(expandedTopChar, expandedBottomChar);
                 }
 
-                if (forAutofill) {
+                if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
                     structure.setText(text);
                 } else {
                     structure.setText(text, selStart - expandedTopChar, selEnd - expandedTopChar);
@@ -11042,7 +11029,8 @@
                 }
             }
 
-            if (!forAutofill) {
+            if (viewFor == VIEW_STRUCTURE_FOR_ASSIST
+                    || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
                 // Extract style information that applies to the TextView as a whole.
                 int style = 0;
                 int typefaceStyle = getTypefaceStyle();
@@ -11070,7 +11058,8 @@
                 structure.setTextStyle(getTextSize(), getCurrentTextColor(),
                         AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
             }
-            if (forAutofill || forViewCapture) {
+            if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
+                    || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
                 structure.setMinTextEms(getMinEms());
                 structure.setMaxTextEms(getMaxEms());
                 int maxLength = -1;
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/app/ColorDisplayController.java b/core/java/com/android/internal/app/ColorDisplayController.java
index 7515180..213bb75 100644
--- a/core/java/com/android/internal/app/ColorDisplayController.java
+++ b/core/java/com/android/internal/app/ColorDisplayController.java
@@ -560,13 +560,6 @@
     }
 
     /**
-     * Returns {@code true} if Night display is supported by the device.
-     */
-    public static boolean isAvailable(Context context) {
-        return context.getResources().getBoolean(R.bool.config_nightDisplayAvailable);
-    }
-
-    /**
      * Callback invoked whenever the Night display settings are changed.
      */
     public interface Callback {
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index 81dab2f..8bc90a8 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -389,14 +389,18 @@
      * @param query the search condition used to match file names
      * @param projection projection of the returned cursor
      * @param exclusion absolute file paths to exclude from result
-     * @return cursor containing search result
+     * @param queryArgs the query arguments for search
+     * @return cursor containing search result. Include
+     *         {@link ContentResolver#EXTRA_HONORED_ARGS} in {@link Cursor}
+     *         extras {@link Bundle} when any QUERY_ARG_* value was honored
+     *         during the preparation of the results.
      * @throws FileNotFoundException when root folder doesn't exist or search fails
+     *
+     * @see ContentResolver#EXTRA_HONORED_ARGS
      */
     protected final Cursor querySearchDocuments(
-            File folder, String query, String[] projection, Set<String> exclusion)
+            File folder, String[] projection, Set<String> exclusion, Bundle queryArgs)
             throws FileNotFoundException {
-
-        query = query.toLowerCase();
         final MatrixCursor result = new MatrixCursor(resolveProjection(projection));
         final LinkedList<File> pending = new LinkedList<>();
         pending.add(folder);
@@ -407,11 +411,18 @@
                     pending.add(child);
                 }
             }
-            if (file.getName().toLowerCase().contains(query)
-                    && !exclusion.contains(file.getAbsolutePath())) {
+            if (!exclusion.contains(file.getAbsolutePath()) && matchSearchQueryArguments(file,
+                    queryArgs)) {
                 includeFile(result, null, file);
             }
         }
+
+        final String[] handledQueryArgs = DocumentsContract.getHandledQueryArguments(queryArgs);
+        if (handledQueryArgs.length > 0) {
+            final Bundle extras = new Bundle();
+            extras.putStringArray(ContentResolver.EXTRA_HONORED_ARGS, handledQueryArgs);
+            result.setExtras(extras);
+        }
         return result;
     }
 
@@ -457,6 +468,34 @@
         }
     }
 
+    /**
+     * Test if the file matches the query arguments.
+     *
+     * @param file the file to test
+     * @param queryArgs the query arguments
+     */
+    private boolean matchSearchQueryArguments(File file, Bundle queryArgs) {
+        if (file == null) {
+            return false;
+        }
+
+        final String fileMimeType;
+        final String fileName = file.getName();
+
+        if (file.isDirectory()) {
+            fileMimeType = DocumentsContract.Document.MIME_TYPE_DIR;
+        } else {
+            int dotPos = fileName.lastIndexOf('.');
+            if (dotPos < 0) {
+                return false;
+            }
+            final String extension = fileName.substring(dotPos + 1);
+            fileMimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+        }
+        return DocumentsContract.matchSearchQueryArguments(queryArgs, fileName, fileMimeType,
+                file.lastModified(), file.length());
+    }
+
     private void scanFile(File visibleFile) {
         final Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
         intent.setData(Uri.fromFile(visibleFile));
diff --git a/core/java/com/android/internal/os/BatterySipper.java b/core/java/com/android/internal/os/BatterySipper.java
index 0baf73c..02c9542 100644
--- a/core/java/com/android/internal/os/BatterySipper.java
+++ b/core/java/com/android/internal/os/BatterySipper.java
@@ -130,6 +130,10 @@
     public double wakeLockPowerMah;
     public double wifiPowerMah;
 
+    //                           ****************
+    // This list must be kept current with atoms.proto (frameworks/base/cmds/statsd/src/atoms.proto)
+    // so the ordinal values (and therefore the order) must never change.
+    //                           ****************
     public enum DrainType {
         AMBIENT_DISPLAY,
         @UnsupportedAppUsage
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/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 8f87f91..8bdb000 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -173,12 +173,13 @@
     }
 
     native private static void nativePreloadAppProcessHALs();
+    native private static void nativePreloadOpenGL();
 
     private static void preloadOpenGL() {
         String driverPackageName = SystemProperties.get(PROPERTY_GFX_DRIVER);
         if (!SystemProperties.getBoolean(PROPERTY_DISABLE_OPENGL_PRELOADING, false) &&
                 (driverPackageName == null || driverPackageName.isEmpty())) {
-            EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
+            nativePreloadOpenGL();
         }
     }
 
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index e3490f1..137ca7f 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.view;
 
-import android.annotation.UnsupportedAppUsage;
 import android.graphics.Rect;
 import android.hardware.input.InputManager;
 import android.os.Bundle;
@@ -66,7 +65,7 @@
     }
 
     @Override
-    public void windowFocusChanged(boolean hasFocus, boolean touchEnabled) {
+    public void windowFocusChanged(boolean hasFocus, boolean touchEnabled, boolean reportToClient) {
     }
 
     @Override
diff --git a/core/java/com/android/internal/widget/SubtitleView.java b/core/java/com/android/internal/widget/SubtitleView.java
index 1107828..21e63c5 100644
--- a/core/java/com/android/internal/widget/SubtitleView.java
+++ b/core/java/com/android/internal/widget/SubtitleView.java
@@ -58,7 +58,7 @@
     /** Reusable spannable string builder used for holding text. */
     private final SpannableStringBuilder mText = new SpannableStringBuilder();
 
-    private Alignment mAlignment;
+    private Alignment mAlignment = Alignment.ALIGN_CENTER;
     private TextPaint mTextPaint;
     private Paint mPaint;
 
diff --git a/core/jni/android_hardware_input_InputApplicationHandle.cpp b/core/jni/android_hardware_input_InputApplicationHandle.cpp
index 8ace8da..5887fa7 100644
--- a/core/jni/android_hardware_input_InputApplicationHandle.cpp
+++ b/core/jni/android_hardware_input_InputApplicationHandle.cpp
@@ -135,13 +135,13 @@
         LOG_FATAL_IF(! (var), "Unable to find field " fieldName);
 
 int register_android_server_InputApplicationHandle(JNIEnv* env) {
-    int res = jniRegisterNativeMethods(env, "com/android/server/input/InputApplicationHandle",
+    int res = jniRegisterNativeMethods(env, "android/view/InputApplicationHandle",
             gInputApplicationHandleMethods, NELEM(gInputApplicationHandleMethods));
     (void) res;  // Faked use when LOG_NDEBUG.
     LOG_FATAL_IF(res < 0, "Unable to register native methods.");
 
     jclass clazz;
-    FIND_CLASS(clazz, "com/android/server/input/InputApplicationHandle");
+    FIND_CLASS(clazz, "android/view/InputApplicationHandle");
 
     GET_FIELD_ID(gInputApplicationHandleClassInfo.ptr, clazz,
             "ptr", "J");
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index f4829ad..6ecb5de 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -80,51 +80,47 @@
     JNIEnv* env = AndroidRuntime::getJNIEnv();
     jobject obj = env->NewLocalRef(mObjWeak);
     if (!obj) {
-        releaseInfo();
+        releaseChannel();
         return false;
     }
 
-    if (!mInfo) {
-        mInfo = new InputWindowInfo();
-    } else {
-        mInfo->touchableRegion.clear();
-    }
+    mInfo.touchableRegion.clear();
 
     jobject inputChannelObj = env->GetObjectField(obj,
             gInputWindowHandleClassInfo.inputChannel);
     if (inputChannelObj) {
-        mInfo->inputChannel = android_view_InputChannel_getInputChannel(env, inputChannelObj);
+        mInfo.inputChannel = android_view_InputChannel_getInputChannel(env, inputChannelObj);
         env->DeleteLocalRef(inputChannelObj);
     } else {
-        mInfo->inputChannel.clear();
+        mInfo.inputChannel.clear();
     }
 
     jstring nameObj = jstring(env->GetObjectField(obj,
             gInputWindowHandleClassInfo.name));
     if (nameObj) {
         const char* nameStr = env->GetStringUTFChars(nameObj, NULL);
-        mInfo->name = nameStr;
+        mInfo.name = nameStr;
         env->ReleaseStringUTFChars(nameObj, nameStr);
         env->DeleteLocalRef(nameObj);
     } else {
-        mInfo->name = "<null>";
+        mInfo.name = "<null>";
     }
 
-    mInfo->layoutParamsFlags = env->GetIntField(obj,
+    mInfo.layoutParamsFlags = env->GetIntField(obj,
             gInputWindowHandleClassInfo.layoutParamsFlags);
-    mInfo->layoutParamsType = env->GetIntField(obj,
+    mInfo.layoutParamsType = env->GetIntField(obj,
             gInputWindowHandleClassInfo.layoutParamsType);
-    mInfo->dispatchingTimeout = env->GetLongField(obj,
+    mInfo.dispatchingTimeout = env->GetLongField(obj,
             gInputWindowHandleClassInfo.dispatchingTimeoutNanos);
-    mInfo->frameLeft = env->GetIntField(obj,
+    mInfo.frameLeft = env->GetIntField(obj,
             gInputWindowHandleClassInfo.frameLeft);
-    mInfo->frameTop = env->GetIntField(obj,
+    mInfo.frameTop = env->GetIntField(obj,
             gInputWindowHandleClassInfo.frameTop);
-    mInfo->frameRight = env->GetIntField(obj,
+    mInfo.frameRight = env->GetIntField(obj,
             gInputWindowHandleClassInfo.frameRight);
-    mInfo->frameBottom = env->GetIntField(obj,
+    mInfo.frameBottom = env->GetIntField(obj,
             gInputWindowHandleClassInfo.frameBottom);
-    mInfo->scaleFactor = env->GetFloatField(obj,
+    mInfo.scaleFactor = env->GetFloatField(obj,
             gInputWindowHandleClassInfo.scaleFactor);
 
     jobject regionObj = env->GetObjectField(obj,
@@ -133,30 +129,30 @@
         SkRegion* region = android_graphics_Region_getSkRegion(env, regionObj);
         for (SkRegion::Iterator it(*region); !it.done(); it.next()) {
             const SkIRect& rect = it.rect();
-            mInfo->addTouchableRegion(Rect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom));
+            mInfo.addTouchableRegion(Rect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom));
         }
         env->DeleteLocalRef(regionObj);
     }
 
-    mInfo->visible = env->GetBooleanField(obj,
+    mInfo.visible = env->GetBooleanField(obj,
             gInputWindowHandleClassInfo.visible);
-    mInfo->canReceiveKeys = env->GetBooleanField(obj,
+    mInfo.canReceiveKeys = env->GetBooleanField(obj,
             gInputWindowHandleClassInfo.canReceiveKeys);
-    mInfo->hasFocus = env->GetBooleanField(obj,
+    mInfo.hasFocus = env->GetBooleanField(obj,
             gInputWindowHandleClassInfo.hasFocus);
-    mInfo->hasWallpaper = env->GetBooleanField(obj,
+    mInfo.hasWallpaper = env->GetBooleanField(obj,
             gInputWindowHandleClassInfo.hasWallpaper);
-    mInfo->paused = env->GetBooleanField(obj,
+    mInfo.paused = env->GetBooleanField(obj,
             gInputWindowHandleClassInfo.paused);
-    mInfo->layer = env->GetIntField(obj,
+    mInfo.layer = env->GetIntField(obj,
             gInputWindowHandleClassInfo.layer);
-    mInfo->ownerPid = env->GetIntField(obj,
+    mInfo.ownerPid = env->GetIntField(obj,
             gInputWindowHandleClassInfo.ownerPid);
-    mInfo->ownerUid = env->GetIntField(obj,
+    mInfo.ownerUid = env->GetIntField(obj,
             gInputWindowHandleClassInfo.ownerUid);
-    mInfo->inputFeatures = env->GetIntField(obj,
+    mInfo.inputFeatures = env->GetIntField(obj,
             gInputWindowHandleClassInfo.inputFeatures);
-    mInfo->displayId = env->GetIntField(obj,
+    mInfo.displayId = env->GetIntField(obj,
             gInputWindowHandleClassInfo.displayId);
 
     env->DeleteLocalRef(obj);
@@ -225,20 +221,20 @@
         LOG_FATAL_IF(! (var), "Unable to find field " fieldName);
 
 int register_android_server_InputWindowHandle(JNIEnv* env) {
-    int res = jniRegisterNativeMethods(env, "com/android/server/input/InputWindowHandle",
+    int res = jniRegisterNativeMethods(env, "android/view/InputWindowHandle",
             gInputWindowHandleMethods, NELEM(gInputWindowHandleMethods));
     (void) res;  // Faked use when LOG_NDEBUG.
     LOG_FATAL_IF(res < 0, "Unable to register native methods.");
 
     jclass clazz;
-    FIND_CLASS(clazz, "com/android/server/input/InputWindowHandle");
+    FIND_CLASS(clazz, "android/view/InputWindowHandle");
 
     GET_FIELD_ID(gInputWindowHandleClassInfo.ptr, clazz,
             "ptr", "J");
 
     GET_FIELD_ID(gInputWindowHandleClassInfo.inputApplicationHandle,
             clazz,
-            "inputApplicationHandle", "Lcom/android/server/input/InputApplicationHandle;");
+            "inputApplicationHandle", "Landroid/view/InputApplicationHandle;");
 
     GET_FIELD_ID(gInputWindowHandleClassInfo.inputChannel, clazz,
             "inputChannel", "Landroid/view/InputChannel;");
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index b2d44e7..7b564ae 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -24,9 +24,13 @@
 #include <sys/system_properties.h>
 #include <sys/types.h>
 #include <sys/wait.h>
+#include <unistd.h>
 
 #include <private/android_filesystem_config.h> // for AID_SYSTEM
 
+#include <sstream>
+#include <string>
+
 #include "android-base/logging.h"
 #include "android-base/properties.h"
 #include "android-base/stringprintf.h"
@@ -38,6 +42,7 @@
 #include "androidfw/AssetManager2.h"
 #include "androidfw/AttributeResolution.h"
 #include "androidfw/MutexGuard.h"
+#include "androidfw/PosixUtils.h"
 #include "androidfw/ResourceTypes.h"
 #include "core_jni_helpers.h"
 #include "jni.h"
@@ -54,6 +59,7 @@
 extern "C" int capset(cap_user_header_t hdrp, const cap_user_data_t datap);
 
 using ::android::base::StringPrintf;
+using ::android::util::ExecuteBinary;
 
 namespace android {
 
@@ -161,18 +167,20 @@
       argv[argc++] = AssetManager::IDMAP_DIR;
 
       // Directories to scan for overlays: if OVERLAY_THEME_DIR_PROPERTY is defined,
-      // use OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in addition to OVERLAY_DIR.
+      // use VENDOR_OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in
+      // addition to VENDOR_OVERLAY_DIR.
       std::string overlay_theme_path = base::GetProperty(AssetManager::OVERLAY_THEME_DIR_PROPERTY,
                                                          "");
       if (!overlay_theme_path.empty()) {
-        overlay_theme_path = std::string(AssetManager::OVERLAY_DIR) + "/" + overlay_theme_path;
+        overlay_theme_path =
+          std::string(AssetManager::VENDOR_OVERLAY_DIR) + "/" + overlay_theme_path;
         if (stat(overlay_theme_path.c_str(), &st) == 0) {
           argv[argc++] = overlay_theme_path.c_str();
         }
       }
 
-      if (stat(AssetManager::OVERLAY_DIR, &st) == 0) {
-        argv[argc++] = AssetManager::OVERLAY_DIR;
+      if (stat(AssetManager::VENDOR_OVERLAY_DIR, &st) == 0) {
+        argv[argc++] = AssetManager::VENDOR_OVERLAY_DIR;
       }
 
       if (stat(AssetManager::PRODUCT_OVERLAY_DIR, &st) == 0) {
@@ -200,6 +208,75 @@
   }
 }
 
+static jobjectArray NativeCreateIdmapsForStaticOverlaysTargetingAndroid(JNIEnv* env,
+                                                                        jclass /*clazz*/) {
+  // --input-directory can be given multiple times, but idmap2 expects the directory to exist
+  std::vector<std::string> input_dirs;
+  struct stat st;
+  if (stat(AssetManager::VENDOR_OVERLAY_DIR, &st) == 0) {
+    input_dirs.push_back(AssetManager::VENDOR_OVERLAY_DIR);
+  }
+
+  if (stat(AssetManager::PRODUCT_OVERLAY_DIR, &st) == 0) {
+    input_dirs.push_back(AssetManager::PRODUCT_OVERLAY_DIR);
+  }
+
+  if (stat(AssetManager::PRODUCT_SERVICES_OVERLAY_DIR, &st) == 0) {
+    input_dirs.push_back(AssetManager::PRODUCT_SERVICES_OVERLAY_DIR);
+  }
+
+  if (input_dirs.empty()) {
+    LOG(WARNING) << "no directories for idmap2 to scan";
+    return env->NewObjectArray(0, g_stringClass, nullptr);
+  }
+
+  std::vector<std::string> argv{"/system/bin/idmap2",
+    "scan",
+    "--recursive",
+    "--target-package-name", "android",
+    "--target-apk-path", "/system/framework/framework-res.apk",
+    "--output-directory", "/data/resource-cache"};
+
+  for (const auto& dir : input_dirs) {
+    argv.push_back("--input-directory");
+    argv.push_back(dir);
+  }
+
+  const auto result = ExecuteBinary(argv);
+
+  if (!result) {
+      LOG(ERROR) << "failed to execute idmap2";
+      return nullptr;
+  }
+
+  if (result->status != 0) {
+    LOG(ERROR) << "idmap2: " << result->stderr;
+    return nullptr;
+  }
+
+  std::vector<std::string> idmap_paths;
+  std::istringstream input(result->stdout);
+  std::string path;
+  while (std::getline(input, path)) {
+    idmap_paths.push_back(path);
+  }
+
+  jobjectArray array = env->NewObjectArray(idmap_paths.size(), g_stringClass, nullptr);
+  if (array == nullptr) {
+    return nullptr;
+  }
+  for (size_t i = 0; i < idmap_paths.size(); i++) {
+    const std::string path = idmap_paths[i];
+    jstring java_string = env->NewStringUTF(path.c_str());
+    if (env->ExceptionCheck()) {
+      return nullptr;
+    }
+    env->SetObjectArrayElement(array, i, java_string);
+    env->DeleteLocalRef(java_string);
+  }
+  return array;
+}
+
 static jint CopyValue(JNIEnv* env, ApkAssetsCookie cookie, const Res_value& value, uint32_t ref,
                       uint32_t type_spec_flags, ResTable_config* config, jobject out_typed_value) {
   env->SetIntField(out_typed_value, gTypedValueOffsets.mType, value.dataType);
@@ -1405,6 +1482,8 @@
 
     // System/idmap related methods.
     {"nativeVerifySystemIdmaps", "()V", (void*)NativeVerifySystemIdmaps},
+    {"nativeCreateIdmapsForStaticOverlaysTargetingAndroid", "()[Ljava/lang/String;",
+     (void*)NativeCreateIdmapsForStaticOverlaysTargetingAndroid},
 
     // Global management/debug methods.
     {"getGlobalAssetCount", "()I", (void*)NativeGetGlobalAssetCount},
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/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp
index 2f17907..fb6be6b 100644
--- a/core/jni/android_view_InputChannel.cpp
+++ b/core/jni/android_view_InputChannel.cpp
@@ -249,6 +249,24 @@
     }
 }
 
+static jobject android_view_InputChannel_nativeGetToken(JNIEnv* env, jobject obj) {
+    NativeInputChannel* nativeInputChannel =
+        android_view_InputChannel_getNativeInputChannel(env, obj);
+    if (nativeInputChannel) {
+        return javaObjectForIBinder(env, nativeInputChannel->getInputChannel()->getToken());
+    }
+    return 0;
+}
+
+static void android_view_InputChannel_nativeSetToken(JNIEnv* env, jobject obj, jobject tokenObj) {
+    NativeInputChannel* nativeInputChannel =
+        android_view_InputChannel_getNativeInputChannel(env, obj);
+    sp<IBinder> token = ibinderForJavaObject(env, tokenObj);
+    if (nativeInputChannel != nullptr) {
+        nativeInputChannel->getInputChannel()->setToken(token);
+    }
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gInputChannelMethods[] = {
@@ -267,6 +285,10 @@
             (void*)android_view_InputChannel_nativeGetName },
     { "nativeDup", "(Landroid/view/InputChannel;)V",
             (void*)android_view_InputChannel_nativeDup },
+    { "nativeGetToken", "()Landroid/os/IBinder;",
+            (void*)android_view_InputChannel_nativeGetToken },
+    { "nativeSetToken", "(Landroid/os/IBinder;)V",
+            (void*)android_view_InputChannel_nativeSetToken }
 };
 
 int register_android_view_InputChannel(JNIEnv* env) {
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 4eda3ab..ec9c860 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -19,6 +19,7 @@
 
 #include "android_os_Parcel.h"
 #include "android_util_Binder.h"
+#include "android_hardware_input_InputWindowHandle.h"
 #include "android/graphics/Bitmap.h"
 #include "android/graphics/GraphicsJNI.h"
 #include "android/graphics/Region.h"
@@ -324,6 +325,18 @@
     transaction->setAlpha(ctrl, alpha);
 }
 
+static void nativeSetInputWindowInfo(JNIEnv* env, jclass clazz, jlong transactionObj,
+        jlong nativeObject, jobject inputWindow) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+    sp<NativeInputWindowHandle> handle = android_server_InputWindowHandle_getHandle(
+            env, inputWindow);
+    handle->updateInfo();
+
+    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+    transaction->setInputWindowInfo(ctrl, *handle->getInfo());
+}
+
 static void nativeSetColor(JNIEnv* env, jclass clazz, jlong transactionObj,
         jlong nativeObject, jfloatArray fColor) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -930,6 +943,8 @@
             (void*)nativeScreenshot },
     {"nativeCaptureLayers", "(Landroid/os/IBinder;Landroid/graphics/Rect;F)Landroid/graphics/GraphicBuffer;",
             (void*)nativeCaptureLayers },
+    {"nativeSetInputWindowInfo", "(JJLandroid/view/InputWindowHandle;)V",
+     (void*)nativeSetInputWindowInfo },
 };
 
 int register_android_view_SurfaceControl(JNIEnv* env)
diff --git a/core/jni/com_android_internal_os_ZygoteInit.cpp b/core/jni/com_android_internal_os_ZygoteInit.cpp
index 258a55c..ac0e600 100644
--- a/core/jni/com_android_internal_os_ZygoteInit.cpp
+++ b/core/jni/com_android_internal_os_ZygoteInit.cpp
@@ -16,21 +16,58 @@
 
 #define LOG_TAG "Zygote"
 
+#include <EGL/egl.h>
 #include <ui/GraphicBufferMapper.h>
 
 #include "core_jni_helpers.h"
 
 namespace {
 
+// Shadow call stack (SCS) is a security mitigation that uses a separate stack
+// (the SCS) for return addresses. In versions of Android newer than P, the
+// compiler cooperates with the system to ensure that the SCS address is always
+// stored in register x18, as long as the app was compiled with a new enough
+// compiler and does not use features that rely on SP-HALs (this restriction is
+// because the SP-HALs might not preserve x18 due to potentially having been
+// compiled with an old compiler as a consequence of Treble; it generally means
+// that the app must be a system app without a UI). This struct is used to
+// temporarily store the address on the stack while preloading the SP-HALs, so
+// that such apps can use the same zygote as everything else.
+struct ScopedSCSExit {
+#ifdef __aarch64__
+    void* scs;
+
+    ScopedSCSExit() {
+        __asm__ __volatile__("str x18, [%0]" ::"r"(&scs));
+    }
+
+    ~ScopedSCSExit() {
+        __asm__ __volatile__("ldr x18, [%0]; str xzr, [%0]" ::"r"(&scs));
+    }
+#else
+    // Silence unused variable warnings in non-SCS builds.
+    ScopedSCSExit() {}
+    ~ScopedSCSExit() {}
+#endif
+};
+
 void android_internal_os_ZygoteInit_nativePreloadAppProcessHALs(JNIEnv* env, jclass) {
+    ScopedSCSExit x;
     android::GraphicBufferMapper::preloadHal();
     // Add preloading here for other HALs that are (a) always passthrough, and
     // (b) loaded by most app processes.
 }
 
+void android_internal_os_ZygoteInit_nativePreloadOpenGL(JNIEnv* env, jclass) {
+    ScopedSCSExit x;
+    eglGetDisplay(EGL_DEFAULT_DISPLAY);
+}
+
 const JNINativeMethod gMethods[] = {
     { "nativePreloadAppProcessHALs", "()V",
       (void*)android_internal_os_ZygoteInit_nativePreloadAppProcessHALs },
+    { "nativePreloadOpenGL", "()V",
+      (void*)android_internal_os_ZygoteInit_nativePreloadOpenGL },
 };
 
 }  // anonymous namespace
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 2465759..a398e49 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -85,7 +85,7 @@
   // See AssetManager.cpp for more details on overlay-subdir.
   static const char* kOverlayDir = "/system/vendor/overlay/";
   static const char* kVendorOverlayDir = "/vendor/overlay";
-  static const char* kOverlaySubdir = "/system/vendor/overlay-subdir/";
+  static const char* kVendorOverlaySubdir = "/system/vendor/overlay-subdir/";
   static const char* kSystemProductOverlayDir = "/system/product/overlay/";
   static const char* kProductOverlayDir = "/product/overlay";
   static const char* kSystemProductServicesOverlayDir = "/system/product_services/overlay/";
@@ -93,7 +93,7 @@
   static const char* kApkSuffix = ".apk";
 
   if ((android::base::StartsWith(path, kOverlayDir)
-       || android::base::StartsWith(path, kOverlaySubdir)
+       || android::base::StartsWith(path, kVendorOverlaySubdir)
        || android::base::StartsWith(path, kVendorOverlayDir)
        || android::base::StartsWith(path, kSystemProductOverlayDir)
        || android::base::StartsWith(path, kProductOverlayDir)
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 88f4ae4..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,12 +734,11 @@
         <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" />
 
-    <!-- @TestApi Allows an application to read previously received cell broadcast
+    <!-- @SystemApi @TestApi Allows an application to read previously received cell broadcast
          messages and to register a content observer to get notifications when
          a cell broadcast has been received and added to the database. For
          emergency alerts, the database is updated immediately after the
@@ -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" />
@@ -1729,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_car.xml b/core/res/res/values/colors_car.xml
index 32671ac8..ea7c009 100644
--- a/core/res/res/values/colors_car.xml
+++ b/core/res/res/values/colors_car.xml
@@ -284,4 +284,8 @@
 
     <color name="car_red_500a">#ffd50000</color>
     <color name="car_red_a700">#ffd50000</color>
+
+    <color name="car_keyboard_divider_line">#38ffffff</color>
+    <color name="car_keyboard_text_primary_color">@color/car_grey_50</color>
+    <color name="car_keyboard_text_secondary_color">#8af8f9fa</color>
 </resources>
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/config.xml b/core/res/res/values/config.xml
index 26f3370..829d6f5 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -55,6 +55,8 @@
         <item><xliff:g id="id">@string/status_bar_hotspot</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_mobile</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_airplane</xliff:g></item>
+        <item><xliff:g id="id">@string/status_bar_microphone</xliff:g></item>
+        <item><xliff:g id="id">@string/status_bar_camera</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_battery</xliff:g></item>
     </string-array>
 
@@ -87,6 +89,8 @@
     <string translatable="false" name="status_bar_mobile">mobile</string>
     <string translatable="false" name="status_bar_vpn">vpn</string>
     <string translatable="false" name="status_bar_ethernet">ethernet</string>
+    <string translatable="false" name="status_bar_microphone">microphone</string>
+    <string translatable="false" name="status_bar_camera">camera</string>
     <string translatable="false" name="status_bar_airplane">airplane</string>
 
     <!-- Flag indicating whether the surface flinger has limited
diff --git a/core/res/res/values/dimens_car.xml b/core/res/res/values/dimens_car.xml
index c1ca33e..5014a29 100644
--- a/core/res/res/values/dimens_car.xml
+++ b/core/res/res/values/dimens_car.xml
@@ -34,7 +34,6 @@
     <!-- The diff between keyline 1 and keyline 3. -->
     <dimen name="car_keyline_1_keyline_3_diff">88dp</dimen>
     <dimen name="car_dialog_action_bar_height">@dimen/car_card_action_bar_height</dimen>
-    <dimen name="car_primary_icon_size">44dp</dimen>
 
     <!-- Text size for car -->
     <dimen name="car_title_size">32sp</dimen>
@@ -56,16 +55,19 @@
 
     <!-- Common icon size for car app -->
     <dimen name="car_icon_size">56dp</dimen>
+    <dimen name="car_primary_icon_size">44dp</dimen>
+    <dimen name="car_secondary_icon_size">36dp</dimen>
 
-    <dimen name="car_card_header_height">96dp</dimen>
-    <dimen name="car_card_action_bar_height">96dp</dimen>
+    <dimen name="car_card_header_height">76dp</dimen>
+    <dimen name="car_card_action_bar_height">76dp</dimen>
 
     <!-- Paddings -->
-    <dimen name="car_padding_1">4dp</dimen>
-    <dimen name="car_padding_2">10dp</dimen>
-    <dimen name="car_padding_3">16dp</dimen>
-    <dimen name="car_padding_4">28dp</dimen>
-    <dimen name="car_padding_5">32dp</dimen>
+    <dimen name="car_padding_0">4dp</dimen>
+    <dimen name="car_padding_1">8dp</dimen>
+    <dimen name="car_padding_2">16dp</dimen>
+    <dimen name="car_padding_3">28dp</dimen>
+    <dimen name="car_padding_4">32dp</dimen>
+    <dimen name="car_padding_5">64dp</dimen>
 
     <!-- Radius -->
     <dimen name="car_radius_1">4dp</dimen>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4eb723e..626206b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2808,6 +2808,8 @@
   <java-symbol type="string" name="status_bar_mobile" />
   <java-symbol type="string" name="status_bar_ethernet" />
   <java-symbol type="string" name="status_bar_vpn" />
+  <java-symbol type="string" name="status_bar_microphone" />
+  <java-symbol type="string" name="status_bar_camera" />
 
   <!-- Locale picker -->
   <java-symbol type="id" name="locale_search_menu" />
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/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 8c91c37..002b6a8 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -494,6 +494,7 @@
                     Settings.Global.WIFI_IS_UNUSABLE_EVENT_METRICS_ENABLED,
                     Settings.Global.WIFI_LINK_SPEED_METRICS_ENABLED,
                     Settings.Global.WIFI_PNO_FREQUENCY_CULLING_ENABLED,
+                    Settings.Global.WIFI_PNO_RECENCY_SORTING_ENABLED,
                     Settings.Global.WIFI_MAX_DHCP_RETRY_COUNT,
                     Settings.Global.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS,
                     Settings.Global.WIFI_NETWORK_SHOW_RSSI,
diff --git a/core/tests/coretests/src/android/view/textclassifier/ModelFileManagerTest.java b/core/tests/coretests/src/android/view/textclassifier/ModelFileManagerTest.java
index f2efabf..88d162b 100644
--- a/core/tests/coretests/src/android/view/textclassifier/ModelFileManagerTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/ModelFileManagerTest.java
@@ -203,6 +203,28 @@
     }
 
     @Test
+    public void findBestModel_languageIsMoreImportantThanVersion_bestModelComesFirst() {
+        ModelFileManager.ModelFile matchLocaleModel =
+                new ModelFileManager.ModelFile(
+                        new File("/path/b"), 1,
+                        Collections.singletonList(Locale.forLanguageTag("ja")), false);
+
+        ModelFileManager.ModelFile languageIndependentModel =
+                new ModelFileManager.ModelFile(
+                        new File("/path/a"), 2,
+                        Collections.emptyList(), true);
+        when(mModelFileSupplier.get())
+                .thenReturn(
+                        Arrays.asList(matchLocaleModel, languageIndependentModel));
+
+        ModelFileManager.ModelFile bestModelFile =
+                mModelFileManager.findBestModelFile(
+                        LocaleList.forLanguageTags("ja"));
+
+        assertThat(bestModelFile).isEqualTo(matchLocaleModel);
+    }
+
+    @Test
     public void modelFileEquals() {
         ModelFileManager.ModelFile modelA =
                 new ModelFileManager.ModelFile(
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java
index e81f678..7467114 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java
@@ -41,6 +41,7 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.text.format.DateUtils;
+import android.util.StatsLog;
 
 import junit.framework.TestCase;
 
@@ -258,6 +259,36 @@
         assertThat(time).isEqualTo(TIME_STATE_FOREGROUND_MS);
     }
 
+    @Test
+    public void testDrainTypesSyncedWithProto() {
+        assertEquals(BatterySipper.DrainType.AMBIENT_DISPLAY.ordinal(),
+                StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__AMBIENT_DISPLAY);
+        // AtomsProto has no "APP"
+        assertEquals(BatterySipper.DrainType.BLUETOOTH.ordinal(),
+                StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__BLUETOOTH);
+        assertEquals(BatterySipper.DrainType.CAMERA.ordinal(),
+                StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__CAMERA);
+        assertEquals(BatterySipper.DrainType.CELL.ordinal(),
+                StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__CELL);
+        assertEquals(BatterySipper.DrainType.FLASHLIGHT.ordinal(),
+                StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__FLASHLIGHT);
+        assertEquals(BatterySipper.DrainType.IDLE.ordinal(),
+                StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__IDLE);
+        assertEquals(BatterySipper.DrainType.MEMORY.ordinal(),
+                StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__MEMORY);
+        assertEquals(BatterySipper.DrainType.OVERCOUNTED.ordinal(),
+                StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__OVERCOUNTED);
+        assertEquals(BatterySipper.DrainType.PHONE.ordinal(),
+                StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__PHONE);
+        assertEquals(BatterySipper.DrainType.SCREEN.ordinal(),
+                StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__SCREEN);
+        assertEquals(BatterySipper.DrainType.UNACCOUNTED.ordinal(),
+                StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__UNACCOUNTED);
+        // AtomsProto has no "USER"
+        assertEquals(BatterySipper.DrainType.WIFI.ordinal(),
+                StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__WIFI);
+    }
+
     private BatterySipper createTestSmearBatterySipper(long activityTime, double totalPowerMah,
             int uidCode, boolean isUidNull) {
         final BatterySipper sipper = mock(BatterySipper.class);
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/Android.mk b/data/etc/Android.mk
index 936ad22..d24c140a 100644
--- a/data/etc/Android.mk
+++ b/data/etc/Android.mk
@@ -47,3 +47,11 @@
 LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/sysconfig
 LOCAL_SRC_FILES := $(LOCAL_MODULE)
 include $(BUILD_PREBUILT)
+
+########################
+include $(CLEAR_VARS)
+LOCAL_MODULE := com.android.timezone.updater.xml
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_RELATIVE_PATH := permissions
+LOCAL_SRC_FILES := $(LOCAL_MODULE)
+include $(BUILD_PREBUILT)
diff --git a/data/etc/com.android.timezone.updater.xml b/data/etc/com.android.timezone.updater.xml
new file mode 100644
index 0000000..60a66e2
--- /dev/null
+++ b/data/etc/com.android.timezone.updater.xml
@@ -0,0 +1,22 @@
+<?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
+  -->
+<permissions>
+    <privapp-permissions package="com.android.timezone.updater">
+        <permission name="android.permission.QUERY_TIME_ZONE_RULES" />
+        <permission name="android.permission.UPDATE_TIME_ZONE_RULES" />
+    </privapp-permissions>
+</permissions>
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 68f24fb..a4c5ed2 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -173,6 +173,7 @@
     <assign-permission name="android.permission.ACCESS_LOWPAN_STATE" uid="lowpan" />
     <assign-permission name="android.permission.MANAGE_LOWPAN_INTERFACES" uid="lowpan" />
 
+    <assign-permission name="android.permission.BATTERY_STATS" uid="statsd" />
     <assign-permission name="android.permission.DUMP" uid="statsd" />
     <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="statsd" />
     <assign-permission name="android.permission.STATSCOMPANION" uid="statsd" />
diff --git a/graphics/java/android/graphics/RecordingCanvas.java b/graphics/java/android/graphics/RecordingCanvas.java
index fd5d624..67ad404 100644
--- a/graphics/java/android/graphics/RecordingCanvas.java
+++ b/graphics/java/android/graphics/RecordingCanvas.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.util.Pools.SynchronizedPool;
+import android.view.DisplayListCanvas;
 import android.view.TextureLayer;
 
 import dalvik.annotation.optimization.CriticalNative;
@@ -34,7 +35,7 @@
  * {@link RenderNode#endRecording()} is called. It must not be retained beyond that as it is
  * internally reused.
  */
-public final class RecordingCanvas extends BaseRecordingCanvas {
+public final class RecordingCanvas extends DisplayListCanvas {
     // The recording canvas pool should be large enough to handle a deeply nested
     // view hierarchy because display lists are generated recursively.
     private static final int POOL_LIMIT = 25;
@@ -89,7 +90,8 @@
     // Constructors
     ///////////////////////////////////////////////////////////////////////////
 
-    private RecordingCanvas(@NonNull RenderNode node, int width, int height) {
+    /** @hide */
+    protected RecordingCanvas(@NonNull RenderNode node, int width, int height) {
         super(nCreateDisplayListCanvas(node.mNativeRenderNode, width, height));
         mDensity = 0; // disable bitmap density scaling
     }
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index c10e482..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;
@@ -38,11 +39,13 @@
 import android.security.keymaster.KeymasterCertificateChain;
 import android.security.keymaster.KeymasterDefs;
 import android.security.keymaster.OperationResult;
+import android.security.keystore.IKeystoreService;
 import android.security.keystore.KeyExpiredException;
 import android.security.keystore.KeyNotYetValidException;
 import android.security.keystore.KeyPermanentlyInvalidatedException;
 import android.security.keystore.KeyProperties;
 import android.security.keystore.KeyProtection;
+import android.security.keystore.KeystoreResponse;
 import android.security.keystore.StrongBoxUnavailableException;
 import android.security.keystore.UserNotAuthenticatedException;
 import android.util.Log;
@@ -52,8 +55,12 @@
 import java.io.ByteArrayInputStream;
 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;
+import java.util.concurrent.ExecutionException;
 import sun.security.util.ObjectIdentifier;
 import sun.security.x509.AlgorithmId;
 
@@ -303,6 +310,31 @@
         }
     }
 
+    /**
+     * List uids of all keys that are auth bound to the current user. 
+     * Only system is allowed to call this method.
+     */
+    @UnsupportedAppUsage
+    public int[] listUidsOfAuthBoundKeys() {
+        final int MAX_RESULT_SIZE = 100;
+        int[] uidsOut = new int[MAX_RESULT_SIZE];
+        try {
+            int rc = mBinder.listUidsOfAuthBoundKeys(uidsOut);
+            if (rc != NO_ERROR) {
+                Log.w(TAG, String.format("listUidsOfAuthBoundKeys failed with error code %d", rc));
+                return null;
+            }
+        } catch (RemoteException e) {
+            Log.w(TAG, "Cannot connect to keystore", e);
+            return null;
+        } catch (android.os.ServiceSpecificException e) {
+            Log.w(TAG, "KeyStore exception", e);
+            return null;
+        }
+        // Remove any 0 entries
+        return Arrays.stream(uidsOut).filter(x -> x > 0).toArray();
+   }
+
     public String[] list(String prefix) {
         return list(prefix, UID_SELF);
     }
@@ -451,27 +483,107 @@
 
     public boolean addRngEntropy(byte[] data, int flags) {
         try {
-            return mBinder.addRngEntropy(data, flags) == NO_ERROR;
+            KeystoreResultPromise promise = new KeystoreResultPromise();
+            int errorCode = mBinder.addRngEntropy(promise, data, flags);
+            if (errorCode == NO_ERROR) {
+                return promise.getFuture().get().getErrorCode() == NO_ERROR;
+            } else {
+                return false;
+            }
         } catch (RemoteException e) {
             Log.w(TAG, "Cannot connect to keystore", e);
             return false;
+        } catch (ExecutionException | InterruptedException e) {
+            Log.e(TAG, "AddRngEntropy completed with exception", e);
+            return false;
         }
     }
 
+    private class KeyCharacteristicsCallbackResult {
+        private KeystoreResponse keystoreResponse;
+        private KeyCharacteristics keyCharacteristics;
+
+        public KeyCharacteristicsCallbackResult(KeystoreResponse keystoreResponse,
+                                                KeyCharacteristics keyCharacteristics) {
+            this.keystoreResponse = keystoreResponse;
+            this.keyCharacteristics = keyCharacteristics;
+        }
+
+        public KeystoreResponse getKeystoreResponse() {
+            return keystoreResponse;
+        }
+
+        public void setKeystoreResponse(KeystoreResponse keystoreResponse) {
+            this.keystoreResponse = keystoreResponse;
+        }
+
+        public KeyCharacteristics getKeyCharacteristics() {
+            return keyCharacteristics;
+        }
+
+        public void setKeyCharacteristics(KeyCharacteristics keyCharacteristics) {
+            this.keyCharacteristics = keyCharacteristics;
+        }
+    }
+
+    private class KeyCharacteristicsPromise
+    extends android.security.keystore.IKeystoreKeyCharacteristicsCallback.Stub {
+        final private CompletableFuture<KeyCharacteristicsCallbackResult> future =
+                new CompletableFuture<KeyCharacteristicsCallbackResult>();
+        @Override
+        public void onFinished(KeystoreResponse keystoreResponse,
+                               KeyCharacteristics keyCharacteristics)
+                                       throws android.os.RemoteException {
+            future.complete(
+                    new KeyCharacteristicsCallbackResult(keystoreResponse, keyCharacteristics));
+        }
+        public final CompletableFuture<KeyCharacteristicsCallbackResult> getFuture() {
+            return future;
+        }
+    };
+
+    private int generateKeyInternal(String alias, KeymasterArguments args, byte[] entropy, int uid,
+            int flags, KeyCharacteristics outCharacteristics)
+                    throws RemoteException, ExecutionException, InterruptedException {
+        KeyCharacteristicsPromise promise = new KeyCharacteristicsPromise();
+        int error = mBinder.generateKey(promise, alias, args, entropy, uid, flags);
+        if (error != NO_ERROR) {
+            Log.e(TAG, "generateKeyInternal failed on request " + error);
+            return error;
+        }
+
+        KeyCharacteristicsCallbackResult result = promise.getFuture().get();
+        error = result.getKeystoreResponse().getErrorCode();
+        if (error != NO_ERROR) {
+            Log.e(TAG, "generateKeyInternal failed on response " + error);
+            return error;
+        }
+        KeyCharacteristics characteristics = result.getKeyCharacteristics();
+        if (characteristics == null) {
+            Log.e(TAG, "generateKeyInternal got empty key cheractariestics " + error);
+            return SYSTEM_ERROR;
+        }
+        outCharacteristics.shallowCopyFrom(characteristics);
+        return NO_ERROR;
+    }
+
     public int generateKey(String alias, KeymasterArguments args, byte[] entropy, int uid,
             int flags, KeyCharacteristics outCharacteristics) {
         try {
             entropy = entropy != null ? entropy : new byte[0];
             args = args != null ? args : new KeymasterArguments();
-            int error = mBinder.generateKey(alias, args, entropy, uid, flags, outCharacteristics);
+            int error = generateKeyInternal(alias, args, entropy, uid, flags, outCharacteristics);
             if (error == KEY_ALREADY_EXISTS) {
                 mBinder.del(alias, uid);
-                error = mBinder.generateKey(alias, args, entropy, uid, flags, outCharacteristics);
+                error = generateKeyInternal(alias, args, entropy, uid, flags, outCharacteristics);
             }
             return error;
         } catch (RemoteException e) {
             Log.w(TAG, "Cannot connect to keystore", e);
             return SYSTEM_ERROR;
+        } catch (ExecutionException | InterruptedException e) {
+            Log.e(TAG, "generateKey completed with exception", e);
+            return SYSTEM_ERROR;
         }
     }
 
@@ -485,10 +597,24 @@
         try {
             clientId = clientId != null ? clientId : new KeymasterBlob(new byte[0]);
             appId = appId != null ? appId : new KeymasterBlob(new byte[0]);
-            return mBinder.getKeyCharacteristics(alias, clientId, appId, uid, outCharacteristics);
+            KeyCharacteristicsPromise promise = new KeyCharacteristicsPromise();
+            int error = mBinder.getKeyCharacteristics(promise, alias, clientId, appId, uid);
+            if (error != NO_ERROR) return error;
+
+            KeyCharacteristicsCallbackResult result = promise.getFuture().get();
+            error = result.getKeystoreResponse().getErrorCode();
+            if (error != NO_ERROR) return error;
+
+            KeyCharacteristics characteristics = result.getKeyCharacteristics();
+            if (characteristics == null) return SYSTEM_ERROR;
+            outCharacteristics.shallowCopyFrom(characteristics);
+            return NO_ERROR;
         } catch (RemoteException e) {
             Log.w(TAG, "Cannot connect to keystore", e);
             return SYSTEM_ERROR;
+        } catch (ExecutionException | InterruptedException e) {
+            Log.e(TAG, "GetKeyCharacteristics completed with exception", e);
+            return SYSTEM_ERROR;
         }
     }
 
@@ -497,20 +623,40 @@
         return getKeyCharacteristics(alias, clientId, appId, UID_SELF, outCharacteristics);
     }
 
+    private int importKeyInternal(String alias, KeymasterArguments args, int format, byte[] keyData,
+            int uid, int flags, KeyCharacteristics outCharacteristics)
+                    throws RemoteException, ExecutionException, InterruptedException {
+        KeyCharacteristicsPromise promise = new KeyCharacteristicsPromise();
+        int error = mBinder.importKey(promise, alias, args, format, keyData, uid, flags);
+        if (error != NO_ERROR) return error;
+
+        KeyCharacteristicsCallbackResult result = promise.getFuture().get();
+        error = result.getKeystoreResponse().getErrorCode();
+        if (error != NO_ERROR) return error;
+
+        KeyCharacteristics characteristics = result.getKeyCharacteristics();
+        if (characteristics == null) return SYSTEM_ERROR;
+        outCharacteristics.shallowCopyFrom(characteristics);
+        return NO_ERROR;
+    }
+
     public int importKey(String alias, KeymasterArguments args, int format, byte[] keyData,
             int uid, int flags, KeyCharacteristics outCharacteristics) {
         try {
-            int error = mBinder.importKey(alias, args, format, keyData, uid, flags,
+            int error = importKeyInternal(alias, args, format, keyData, uid, flags,
                     outCharacteristics);
             if (error == KEY_ALREADY_EXISTS) {
                 mBinder.del(alias, uid);
-                error = mBinder.importKey(alias, args, format, keyData, uid, flags,
+                error = importKeyInternal(alias, args, format, keyData, uid, flags,
                         outCharacteristics);
             }
             return error;
         } catch (RemoteException e) {
             Log.w(TAG, "Cannot connect to keystore", e);
             return SYSTEM_ERROR;
+        } catch (ExecutionException | InterruptedException e) {
+            Log.e(TAG, "ImportKey completed with exception", e);
+            return SYSTEM_ERROR;
         }
     }
 
@@ -555,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;
     }
 
@@ -578,34 +722,79 @@
         return true;
     }
 
+    private int importWrappedKeyInternal(String wrappedKeyAlias, byte[] wrappedKey,
+            String wrappingKeyAlias,
+            byte[] maskingKey, KeymasterArguments args, long rootSid, long fingerprintSid,
+            KeyCharacteristics outCharacteristics)
+                    throws RemoteException, ExecutionException, InterruptedException {
+        KeyCharacteristicsPromise promise = new KeyCharacteristicsPromise();
+        int error = mBinder.importWrappedKey(promise, wrappedKeyAlias, wrappedKey, wrappingKeyAlias,
+                maskingKey, args, rootSid, fingerprintSid);
+        if (error != NO_ERROR) return error;
+
+        KeyCharacteristicsCallbackResult result = promise.getFuture().get();
+        error = result.getKeystoreResponse().getErrorCode();
+        if (error != NO_ERROR) return error;
+
+        KeyCharacteristics characteristics = result.getKeyCharacteristics();
+        if (characteristics == null) return SYSTEM_ERROR;
+        outCharacteristics.shallowCopyFrom(characteristics);
+        return NO_ERROR;
+    }
+
     public int importWrappedKey(String wrappedKeyAlias, byte[] wrappedKey,
             String wrappingKeyAlias,
             byte[] maskingKey, KeymasterArguments args, long rootSid, long fingerprintSid, int uid,
             KeyCharacteristics outCharacteristics) {
+        // TODO b/119217337 uid parameter gets silently ignored.
         try {
-            int error = mBinder.importWrappedKey(wrappedKeyAlias, wrappedKey, wrappingKeyAlias,
+            int error = importWrappedKeyInternal(wrappedKeyAlias, wrappedKey, wrappingKeyAlias,
                     maskingKey, args, rootSid, fingerprintSid, outCharacteristics);
             if (error == KEY_ALREADY_EXISTS) {
-                mBinder.del(wrappedKeyAlias, -1);
-                error = mBinder.importWrappedKey(wrappedKeyAlias, wrappedKey, wrappingKeyAlias,
+                mBinder.del(wrappedKeyAlias, UID_SELF);
+                error = importWrappedKeyInternal(wrappedKeyAlias, wrappedKey, wrappingKeyAlias,
                         maskingKey, args, rootSid, fingerprintSid, outCharacteristics);
             }
             return error;
         } catch (RemoteException e) {
             Log.w(TAG, "Cannot connect to keystore", e);
             return SYSTEM_ERROR;
+        } catch (ExecutionException | InterruptedException e) {
+            Log.e(TAG, "ImportWrappedKey completed with exception", e);
+            return SYSTEM_ERROR;
         }
     }
 
+    private class ExportKeyPromise
+    extends android.security.keystore.IKeystoreExportKeyCallback.Stub {
+        final private CompletableFuture<ExportResult> future = new CompletableFuture<ExportResult>();
+        @Override
+        public void onFinished(ExportResult exportKeyResult) throws android.os.RemoteException {
+            future.complete(exportKeyResult);
+        }
+        public final CompletableFuture<ExportResult> getFuture() {
+            return future;
+        }
+    };
+
     public ExportResult exportKey(String alias, int format, KeymasterBlob clientId,
             KeymasterBlob appId, int uid) {
         try {
             clientId = clientId != null ? clientId : new KeymasterBlob(new byte[0]);
             appId = appId != null ? appId : new KeymasterBlob(new byte[0]);
-            return mBinder.exportKey(alias, format, clientId, appId, uid);
+            ExportKeyPromise promise = new ExportKeyPromise();
+            int error = mBinder.exportKey(promise, alias, format, clientId, appId, uid);
+            if (error == NO_ERROR) {
+                return promise.getFuture().get();
+            } else {
+                return new ExportResult(error);
+            }
         } catch (RemoteException e) {
             Log.w(TAG, "Cannot connect to keystore", e);
             return null;
+        } catch (ExecutionException | InterruptedException e) {
+            Log.e(TAG, "ExportKey completed with exception", e);
+            return null;
         }
     }
     public ExportResult exportKey(String alias, int format, KeymasterBlob clientId,
@@ -613,15 +802,37 @@
         return exportKey(alias, format, clientId, appId, UID_SELF);
     }
 
+    private class OperationPromise
+    extends android.security.keystore.IKeystoreOperationResultCallback.Stub {
+        final private CompletableFuture<OperationResult> future = new CompletableFuture<OperationResult>();
+        @Override
+        public void onFinished(OperationResult operationResult) throws android.os.RemoteException {
+            future.complete(operationResult);
+        }
+        public final CompletableFuture<OperationResult> getFuture() {
+            return future;
+        }
+    };
+
     public OperationResult begin(String alias, int purpose, boolean pruneable,
             KeymasterArguments args, byte[] entropy, int uid) {
         try {
             args = args != null ? args : new KeymasterArguments();
             entropy = entropy != null ? entropy : new byte[0];
-            return mBinder.begin(getToken(), alias, purpose, pruneable, args, entropy, uid);
+            OperationPromise promise = new OperationPromise();
+            int errorCode =  mBinder.begin(promise, getToken(), alias, purpose, pruneable, args,
+                                           entropy, uid);
+            if (errorCode == NO_ERROR) {
+                return promise.getFuture().get();
+            } else {
+                return new OperationResult(errorCode);
+            }
         } catch (RemoteException e) {
             Log.w(TAG, "Cannot connect to keystore", e);
             return null;
+        } catch (ExecutionException | InterruptedException e) {
+            Log.e(TAG, "Begin completed with exception", e);
+            return null;
         }
     }
 
@@ -636,10 +847,19 @@
         try {
             arguments = arguments != null ? arguments : new KeymasterArguments();
             input = input != null ? input : new byte[0];
-            return mBinder.update(token, arguments, input);
+            OperationPromise promise = new OperationPromise();
+            int errorCode =  mBinder.update(promise, token, arguments, input);
+            if (errorCode == NO_ERROR) {
+                return promise.getFuture().get();
+            } else {
+                return new OperationResult(errorCode);
+            }
         } catch (RemoteException e) {
             Log.w(TAG, "Cannot connect to keystore", e);
             return null;
+        } catch (ExecutionException | InterruptedException e) {
+            Log.e(TAG, "Update completed with exception", e);
+            return null;
         }
     }
 
@@ -649,10 +869,19 @@
             arguments = arguments != null ? arguments : new KeymasterArguments();
             entropy = entropy != null ? entropy : new byte[0];
             signature = signature != null ? signature : new byte[0];
-            return mBinder.finish(token, arguments, signature, entropy);
+            OperationPromise promise = new OperationPromise();
+            int errorCode = mBinder.finish(promise, token, arguments, signature, entropy);
+            if (errorCode == NO_ERROR) {
+                return promise.getFuture().get();
+            } else {
+                return new OperationResult(errorCode);
+            }
         } catch (RemoteException e) {
             Log.w(TAG, "Cannot connect to keystore", e);
             return null;
+        } catch (ExecutionException | InterruptedException e) {
+            Log.e(TAG, "Finish completed with exception", e);
+            return null;
         }
     }
 
@@ -660,12 +889,33 @@
         return finish(token, arguments, signature, null);
     }
 
+    private class KeystoreResultPromise
+    extends android.security.keystore.IKeystoreResponseCallback.Stub {
+        final private CompletableFuture<KeystoreResponse> future = new CompletableFuture<KeystoreResponse>();
+        @Override
+        public void onFinished(KeystoreResponse keystoreResponse) throws android.os.RemoteException {
+            future.complete(keystoreResponse);
+        }
+        public final CompletableFuture<KeystoreResponse> getFuture() {
+            return future;
+        }
+    };
+
     public int abort(IBinder token) {
         try {
-            return mBinder.abort(token);
+            KeystoreResultPromise promise = new KeystoreResultPromise();
+            int errorCode = mBinder.abort(promise, token);
+            if (errorCode == NO_ERROR) {
+                return promise.getFuture().get().getErrorCode();
+            } else {
+                return errorCode;
+            }
         } catch (RemoteException e) {
             Log.w(TAG, "Cannot connect to keystore", e);
             return SYSTEM_ERROR;
+        } catch (ExecutionException | InterruptedException e) {
+            Log.e(TAG, "Abort completed with exception", e);
+            return SYSTEM_ERROR;
         }
     }
 
@@ -747,6 +997,47 @@
         return onUserPasswordChanged(UserHandle.getUserId(Process.myUid()), newPassword);
     }
 
+    private class KeyAttestationCallbackResult {
+        private KeystoreResponse keystoreResponse;
+        private KeymasterCertificateChain certificateChain;
+
+        public KeyAttestationCallbackResult(KeystoreResponse keystoreResponse,
+                KeymasterCertificateChain certificateChain) {
+            this.keystoreResponse = keystoreResponse;
+            this.certificateChain = certificateChain;
+        }
+
+        public KeystoreResponse getKeystoreResponse() {
+            return keystoreResponse;
+        }
+
+        public void setKeystoreResponse(KeystoreResponse keystoreResponse) {
+            this.keystoreResponse = keystoreResponse;
+        }
+
+        public KeymasterCertificateChain getCertificateChain() {
+            return certificateChain;
+        }
+
+        public void setCertificateChain(KeymasterCertificateChain certificateChain) {
+            this.certificateChain = certificateChain;
+        }
+    }
+
+    private class CertificateChainPromise
+    extends android.security.keystore.IKeystoreCertificateChainCallback.Stub {
+        final private CompletableFuture<KeyAttestationCallbackResult> future = new CompletableFuture<KeyAttestationCallbackResult>();
+        @Override
+        public void onFinished(KeystoreResponse keystoreResponse,
+                KeymasterCertificateChain certificateChain) throws android.os.RemoteException {
+            future.complete(new KeyAttestationCallbackResult(keystoreResponse, certificateChain));
+        }
+        public final CompletableFuture<KeyAttestationCallbackResult> getFuture() {
+            return future;
+        }
+    };
+
+
     public int attestKey(
             String alias, KeymasterArguments params, KeymasterCertificateChain outChain) {
         try {
@@ -756,10 +1047,21 @@
             if (outChain == null) {
                 outChain = new KeymasterCertificateChain();
             }
-            return mBinder.attestKey(alias, params, outChain);
+            CertificateChainPromise promise = new CertificateChainPromise();
+            int error = mBinder.attestKey(promise, alias, params);
+            if (error != NO_ERROR) return error;
+            KeyAttestationCallbackResult result = promise.getFuture().get();
+            error = result.getKeystoreResponse().getErrorCode();
+            if (error == NO_ERROR) {
+                outChain.shallowCopyFrom(result.getCertificateChain());
+            }
+            return error;
         } catch (RemoteException e) {
             Log.w(TAG, "Cannot connect to keystore", e);
             return SYSTEM_ERROR;
+        } catch (ExecutionException | InterruptedException e) {
+            Log.e(TAG, "AttestKey completed with exception", e);
+            return SYSTEM_ERROR;
         }
     }
 
@@ -771,10 +1073,21 @@
             if (outChain == null) {
                 outChain = new KeymasterCertificateChain();
             }
-            return mBinder.attestDeviceIds(params, outChain);
+            CertificateChainPromise promise = new CertificateChainPromise();
+            int error = mBinder.attestDeviceIds(promise, params);
+            if (error != NO_ERROR) return error;
+            KeyAttestationCallbackResult result = promise.getFuture().get();
+            error = result.getKeystoreResponse().getErrorCode();
+            if (error == NO_ERROR) {
+                outChain.shallowCopyFrom(result.getCertificateChain());
+            }
+            return error;
         } catch (RemoteException e) {
             Log.w(TAG, "Cannot connect to keystore", e);
             return SYSTEM_ERROR;
+        } catch (ExecutionException | InterruptedException e) {
+            Log.e(TAG, "AttestDevicdeIds completed with exception", e);
+            return SYSTEM_ERROR;
         }
     }
 
@@ -941,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
@@ -949,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();
             }
@@ -959,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/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/keystore/java/android/security/keystore/KeystoreResponse.java b/keystore/java/android/security/keystore/KeystoreResponse.java
new file mode 100644
index 0000000..3a229cb
--- /dev/null
+++ b/keystore/java/android/security/keystore/KeystoreResponse.java
@@ -0,0 +1,78 @@
+/*
+ * 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.security.keystore;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ParcelFormatException;
+
+/**
+ * The Java side of the KeystoreResponse.
+ * <p>
+ * Serialization code for this and subclasses must be kept in sync with system/security/keystore.
+ * @hide
+ */
+public class KeystoreResponse implements Parcelable {
+    public final int error_code_;
+    public final String error_msg_;
+
+    public static final Parcelable.Creator<KeystoreResponse> CREATOR = new
+            Parcelable.Creator<KeystoreResponse>() {
+                @Override
+                public KeystoreResponse createFromParcel(Parcel in) {
+                    final int error_code = in.readInt();
+                    final String error_msg = in.readString();
+                    return new KeystoreResponse(error_code, error_msg);
+                }
+
+                @Override
+                public KeystoreResponse[] newArray(int size) {
+                    return new KeystoreResponse[size];
+                }
+            };
+
+    protected KeystoreResponse(int error_code, String error_msg) {
+        this.error_code_ = error_code;
+        this.error_msg_ = error_msg;
+    }
+
+    /**
+     * @return the error_code_
+     */
+    public final int getErrorCode() {
+        return error_code_;
+    }
+
+    /**
+     * @return the error_msg_
+     */
+    public final String getErrorMessage() {
+        return error_msg_;
+    }
+
+    
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(error_code_);
+        out.writeString(error_msg_);
+    }
+}
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
index 843c146..1cb0d25 100644
--- a/libs/androidfw/AssetManager.cpp
+++ b/libs/androidfw/AssetManager.cpp
@@ -72,7 +72,7 @@
 
 const char* AssetManager::RESOURCES_FILENAME = "resources.arsc";
 const char* AssetManager::IDMAP_BIN = "/system/bin/idmap";
-const char* AssetManager::OVERLAY_DIR = "/vendor/overlay";
+const char* AssetManager::VENDOR_OVERLAY_DIR = "/vendor/overlay";
 const char* AssetManager::PRODUCT_OVERLAY_DIR = "/product/overlay";
 const char* AssetManager::PRODUCT_SERVICES_OVERLAY_DIR = "/product_services/overlay";
 const char* AssetManager::OVERLAY_THEME_DIR_PROPERTY = "ro.boot.vendor.overlay.theme";
diff --git a/libs/androidfw/include/androidfw/AssetManager.h b/libs/androidfw/include/androidfw/AssetManager.h
index cdb87bc..e22e2d2 100644
--- a/libs/androidfw/include/androidfw/AssetManager.h
+++ b/libs/androidfw/include/androidfw/AssetManager.h
@@ -59,13 +59,13 @@
 public:
     static const char* RESOURCES_FILENAME;
     static const char* IDMAP_BIN;
-    static const char* OVERLAY_DIR;
+    static const char* VENDOR_OVERLAY_DIR;
     static const char* PRODUCT_OVERLAY_DIR;
     static const char* PRODUCT_SERVICES_OVERLAY_DIR;
     /*
      * If OVERLAY_THEME_DIR_PROPERTY is set, search for runtime resource overlay
-     * APKs in OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in addition to
-     * OVERLAY_DIR.
+     * APKs in VENDOR_OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in
+     * addition to VENDOR_OVERLAY_DIR.
      */
     static const char* OVERLAY_THEME_DIR_PROPERTY;
     static const char* TARGET_PACKAGE_NAME;
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/libs/services/src/os/StatsLogEventWrapper.cpp b/libs/services/src/os/StatsLogEventWrapper.cpp
index a1a6d9f..04c4629 100644
--- a/libs/services/src/os/StatsLogEventWrapper.cpp
+++ b/libs/services/src/os/StatsLogEventWrapper.cpp
@@ -85,9 +85,6 @@
       case StatsLogValue::FLOAT:
         mElements.push_back(StatsLogValue(in->readFloat()));
         break;
-      case StatsLogValue::DOUBLE:
-        mElements.push_back(StatsLogValue(in->readDouble()));
-        break;
       case StatsLogValue::STORAGE:
         mElements.push_back(StatsLogValue());
         mElements.back().setType(StatsLogValue::STORAGE);
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/AudioPresentation.java b/media/java/android/media/AudioPresentation.java
index 1cc650b..823af65 100644
--- a/media/java/android/media/AudioPresentation.java
+++ b/media/java/android/media/AudioPresentation.java
@@ -18,7 +18,6 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.TestApi;
 import android.icu.util.ULocale;
 
 import java.lang.annotation.Retention;
@@ -172,6 +171,10 @@
         return localeLabels;
     }
 
+    private Map<ULocale, String> getULabels() {
+        return mLabels;
+    }
+
     /**
      * @return the locale corresponding to audio presentation's ISO 639-1/639-2 language code.
      */
@@ -231,17 +234,24 @@
         AudioPresentation obj = (AudioPresentation) o;
         return mPresentationId == obj.getPresentationId()
                 && mProgramId == obj.getProgramId()
-                && mLanguage == obj.getULocale()
+                && mLanguage.equals(obj.getULocale())
                 && mMasteringIndication == obj.getMasteringIndication()
                 && mAudioDescriptionAvailable == obj.hasAudioDescription()
                 && mSpokenSubtitlesAvailable == obj.hasSpokenSubtitles()
                 && mDialogueEnhancementAvailable == obj.hasDialogueEnhancement()
-                && mLabels.equals(obj.getLabels());
+                && mLabels.equals(obj.getULabels());
     }
 
     @Override
     public int hashCode() {
-        return Objects.hashCode(mPresentationId);
+        return Objects.hash(mPresentationId,
+                mProgramId,
+                mLanguage.hashCode(),
+                mMasteringIndication,
+                mAudioDescriptionAvailable,
+                mSpokenSubtitlesAvailable,
+                mDialogueEnhancementAvailable,
+                mLabels.hashCode());
     }
 
     /**
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/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 24b7f36..cdbc7b44 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -418,9 +418,6 @@
 
         /**
          * Returns the status code for the key
-         * @return one of {@link #STATUS_USABLE}, {@link #STATUS_EXPIRED},
-         * {@link #STATUS_OUTPUT_NOT_ALLOWED}, {@link #STATUS_PENDING}
-         * or {@link #STATUS_INTERNAL_ERROR}.
          */
         @KeyStatusCode
         public int getStatusCode() { return mStatusCode; }
@@ -654,13 +651,7 @@
      * can be queried using {@link #getSecurityLevel}. A session
      * ID is returned.
      *
-     * @param level the new security level, one of
-     * {@link #SECURITY_LEVEL_SW_SECURE_CRYPTO},
-     * {@link #SECURITY_LEVEL_SW_SECURE_DECODE},
-     * {@link #SECURITY_LEVEL_HW_SECURE_CRYPTO},
-     * {@link #SECURITY_LEVEL_HW_SECURE_DECODE} or
-     * {@link #SECURITY_LEVEL_HW_SECURE_ALL}.
-     *
+     * @param level the new security level
      * @throws NotProvisionedException if provisioning is needed
      * @throws ResourceBusyException if required resources are in use
      * @throws IllegalArgumentException if the requested security level is
@@ -790,9 +781,6 @@
 
         /**
          * Get the type of the request
-         * @return one of {@link #REQUEST_TYPE_INITIAL},
-         * {@link #REQUEST_TYPE_RENEWAL}, {@link #REQUEST_TYPE_RELEASE},
-         * {@link #REQUEST_TYPE_NONE} or {@link #REQUEST_TYPE_UPDATE}
          */
         @RequestType
         public int getRequestType() { return mRequestType; }
@@ -1051,8 +1039,7 @@
      * an inactive offline license are not usable for decryption.
      *
      * @param keySetId selects the offline license
-     * @return the offline license state, one of {@link #OFFLINE_LICENSE_USABLE},
-     * {@link #OFFLINE_LICENSE_INACTIVE} or {@link #OFFLINE_LICENSE_STATE_UNKNOWN}.
+     * @return the offline license state
      * @throws IllegalArgumentException if the keySetId does not refer to an
      * offline license.
      */
@@ -1191,9 +1178,7 @@
      * enforcing compliance with HDCP requirements. Trusted enforcement of
      * HDCP policies must be handled by the DRM system.
      * <p>
-     * @return one of {@link #HDCP_LEVEL_UNKNOWN}, {@link #HDCP_NONE},
-     * {@link #HDCP_V1}, {@link #HDCP_V2}, {@link #HDCP_V2_1}, {@link #HDCP_V2_2}
-     * or {@link #HDCP_NO_DIGITAL_OUTPUT}.
+     * @return the connected HDCP level
      */
     @HdcpLevel
     public native int getConnectedHdcpLevel();
@@ -1204,9 +1189,7 @@
      * that may be connected. If multiple HDCP-capable interfaces are present,
      * it indicates the highest of the maximum HDCP levels of all interfaces.
      * <p>
-     * @return one of {@link #HDCP_LEVEL_UNKNOWN}, {@link #HDCP_NONE},
-     * {@link #HDCP_V1}, {@link #HDCP_V2}, {@link #HDCP_V2_1}, {@link #HDCP_V2_2}
-     * or {@link #HDCP_NO_DIGITAL_OUTPUT}.
+     * @return the maximum supported HDCP level
      */
     @HdcpLevel
     public native int getMaxHdcpLevel();
@@ -1296,10 +1279,7 @@
      * time a session is opened using {@link #openSession}.
      * @param sessionId the session to query.
      * <p>
-     * @return one of {@link #SECURITY_LEVEL_UNKNOWN},
-     * {@link #SECURITY_LEVEL_SW_SECURE_CRYPTO}, {@link #SECURITY_LEVEL_SW_SECURE_DECODE},
-     * {@link #SECURITY_LEVEL_HW_SECURE_CRYPTO}, {@link #SECURITY_LEVEL_HW_SECURE_DECODE} or
-     * {@link #SECURITY_LEVEL_HW_SECURE_ALL}.
+     * @return the security level of the session
      */
     @SecurityLevel
     public native int getSecurityLevel(@NonNull byte[] sessionId);
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 4e90162..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,25 +1728,25 @@
      * @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.
      *
      * @return true if the MediaPlayer2 is currently looping, false otherwise
-     * @hide
      */
-    public boolean isLooping() {
-        return false;
-    }
+    // This is a synchronous call.
+    public native boolean isLooping();
 
     /**
      * Sets the audio session ID.
@@ -875,19 +1761,32 @@
      * When created, a MediaPlayer2 instance automatically generates its own audio session ID.
      * However, it is possible to force this player to be part of an already existing audio session
      * by calling this method.
-     * This method must be called before one of the overloaded <code> setDataSource </code> methods.
+     * This method must be called when player is in {@link #PLAYER_STATE_IDLE} or
+     * {@link #PLAYER_STATE_PREPARED} state in order to have sessionId take effect.
      * @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.
      *
      * @return the audio session ID. {@see #setAudioSessionId(int)}
-     * Note that the audio session ID is 0 only if a problem occured when the MediaPlayer2 was contructed.
+     * Note that the audio session ID is 0 only if a problem occured when the MediaPlayer2 was
+     * contructed.
      */
-    public abstract int getAudioSessionId();
+    // This is a synchronous call.
+    public native int getAudioSessionId();
 
     /**
      * Attaches an auxiliary effect to the player. A typical auxiliary effect is a reverberation
@@ -905,8 +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();
+        }
     };
 
     /**
@@ -972,35 +1988,32 @@
      * @return List of track info. The total number of tracks is the array length.
      * Must be called again if an external timed text source has been added after
      * 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);
+    }
 
-    /* Do not change these values without updating their counterparts
-     * in include/media/stagefright/MediaDefs.h and media/libstagefright/MediaDefs.cpp!
-     */
-    /**
-     * MIME type for SubRip (SRT) container. Used in addTimedTextSource APIs.
-     * @hide
-     */
-    public static final String MEDIA_MIMETYPE_TEXT_SUBRIP = "application/x-subrip";
-
-    /**
-     * MIME type for WebVTT subtitle data.
-     * @hide
-     */
-    public static final String MEDIA_MIMETYPE_TEXT_VTT = "text/vtt";
-
-    /**
-     * MIME type for CEA-608 closed caption data.
-     * @hide
-     */
-    public static final String MEDIA_MIMETYPE_TEXT_CEA_608 = "text/cea-608";
-
-    /**
-     * MIME type for CEA-708 closed caption data.
-     * @hide
-     */
-    public static final String MEDIA_MIMETYPE_TEXT_CEA_708 = "text/cea-708";
+    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,
@@ -1019,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.
@@ -1050,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.
@@ -1067,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
          *
@@ -1183,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}.
      *
@@ -1190,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}.
@@ -1199,10 +2681,58 @@
      * @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/mediaplayer2.h!
+     * in include/media/MediaPlayer2Types.h!
      */
     /** Unspecified media player error.
      * @see EventCallback#onError
@@ -1541,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
      */
@@ -1619,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;
@@ -1648,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
          *
@@ -1666,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
          *
@@ -1689,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
@@ -1700,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.
      *
@@ -1707,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}.
@@ -1716,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.
@@ -1764,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
@@ -1791,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
@@ -1851,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
      *
@@ -1877,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 {
@@ -2041,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 9b97b10..0000000
--- a/media/java/android/media/MediaPlayer2Impl.java
+++ /dev/null
@@ -1,3155 +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.Collections;
-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.WeakHashMap;
-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);
-
-    /**
-     * Get current playback position as a {@link MediaTimestamp}.
-     * <p>
-     * The MediaTimestamp represents how the media time correlates to the system time in
-     * a linear fashion using an anchor and a clock rate. During regular playback, the media
-     * time moves fairly constantly (though the anchor frame may be rebased to a current
-     * system time, the linear correlation stays steady). Therefore, this method does not
-     * need to be called often.
-     * <p>
-     * To help users get current playback position, this method always anchors the timestamp
-     * to the current {@link System#nanoTime system time}, so
-     * {@link MediaTimestamp#getAnchorMediaTimeUs} can be used as current playback position.
-     *
-     * @return a MediaTimestamp object if a timestamp is available, or {@code null} if no timestamp
-     *         is available, e.g. because the media player has not been initialized.
-     *
-     * @see MediaTimestamp
-     */
-    @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;
-        }
-    }
-
-    /**
-     * 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().
-     */
-    @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;
-    /**
-     * Sets the audio attributes.
-     * @param value value of the parameter to be set.
-     * @return true if the parameter is set successfully, false otherwise
-     */
-    private native boolean native_setAudioAttributes(AudioAttributes audioAttributes);
-    private native AudioAttributes native_getAudioAttributes();
-
-
-    /**
-     * Checks whether the MediaPlayer2 is looping or non-looping.
-     *
-     * @return true if the MediaPlayer2 is currently looping, false otherwise
-     * @hide
-     */
-    @Override
-    public native boolean isLooping();
-
-    /**
-     * Sets the audio session ID.
-     *
-     * @param sessionId the audio session ID.
-     * The audio session ID is a system wide unique identifier for the audio stream played by
-     * this MediaPlayer2 instance.
-     * The primary use of the audio session ID  is to associate audio effects to a particular
-     * instance of MediaPlayer2: if an audio session ID is provided when creating an audio effect,
-     * this effect will be applied only to the audio content of media players within the same
-     * audio session and not to the output mix.
-     * When created, a MediaPlayer2 instance automatically generates its own audio session ID.
-     * However, it is possible to force this player to be part of an already existing audio session
-     * by calling this method.
-     * This method must be called before one of the overloaded <code> setDataSource </code> methods.
-     * @throws IllegalStateException if it is called in an invalid state
-     * @throws IllegalArgumentException if the sessionId is invalid.
-     */
-    @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);
-
-    /**
-     * Returns the audio session ID.
-     *
-     * @return the audio session ID. {@see #setAudioSessionId(int)}
-     * Note that the audio session ID is 0 only if a problem occured when the MediaPlayer2 was contructed.
-     */
-    @Override
-    public native int getAudioSessionId();
-
-    /**
-     * Attaches an auxiliary effect to the player. A typical auxiliary effect is a reverberation
-     * effect which can be applied on any sound source that directs a certain amount of its
-     * energy to this effect. This amount is defined by setAuxEffectSendLevel().
-     * See {@link #setAuxEffectSendLevel(float)}.
-     * <p>After creating an auxiliary effect (e.g.
-     * {@link android.media.audiofx.EnvironmentalReverb}), retrieve its ID with
-     * {@link android.media.audiofx.AudioEffect#getId()} and use it when calling this method
-     * to attach the player to the effect.
-     * <p>To detach the effect from the player, call this method with a null effect id.
-     * <p>This method must be called after one of the overloaded <code> setDataSource </code>
-     * methods.
-     * @param effectId system wide unique id of the effect to attach
-     */
-    @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);
-
-    /**
-     * Sets the send level of the player to the attached auxiliary effect.
-     * See {@link #attachAuxEffect(int)}. The level value range is 0 to 1.0.
-     * <p>By default the send level is 0, so even if an effect is attached to the player
-     * this method must be called for the effect to be applied.
-     * <p>Note that the passed level value is a raw scalar. UI controls should be scaled
-     * logarithmically: the gain applied by audio framework ranges from -72dB to 0dB,
-     * so an appropriate conversion from linear UI input x to level is:
-     * x == 0 -> level = 0
-     * 0 < x <= R -> level = 10^(72*(x-R)/20/R)
-     * @param level send level scalar
-     */
-    @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 {
-        /**
-         * Gets the track type.
-         * @return TrackType which indicates if the track is video, audio, timed text.
-         */
-        @Override
-        public int getTrackType() {
-            return mTrackType;
-        }
-
-        /**
-         * Gets the language code of the track.
-         * @return a language code in either way of ISO-639-1 or ISO-639-2.
-         * When the language is unknown or could not be determined,
-         * ISO-639-2 language code, "und", is returned.
-         */
-        @Override
-        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.
-         */
-        @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();
-        }
-    };
-
-    /**
-     * Returns a List of track information.
-     *
-     * @return List of track info. The total number of tracks is the array length.
-     * Must be called again if an external timed text source has been added after
-     * addTimedTextSource method is called.
-     * @throws IllegalStateException if it is called in an invalid state.
-     */
-    @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;
-    }
-
-    /*
-     * A helper function to check if the mime type is supported by media framework.
-     */
-    private static boolean availableMimeTypeForExternalSource(String mimeType) {
-        if (MEDIA_MIMETYPE_TEXT_SUBRIP.equals(mimeType)) {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Returns the index of the audio, video, or subtitle track currently selected for playback,
-     * The return value is an index into the array returned by {@link #getTrackInfo()}, and can
-     * be used in calls to {@link #selectTrack(int)} or {@link #deselectTrack(int)}.
-     *
-     * @param trackType should be one of {@link TrackInfo#MEDIA_TRACK_TYPE_VIDEO},
-     * {@link TrackInfo#MEDIA_TRACK_TYPE_AUDIO}, or
-     * {@link TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE}
-     * @return index of the audio, video, or subtitle track currently selected for playback;
-     * a negative integer is returned when there is no selected track for {@code trackType} or
-     * when {@code trackType} is not one of audio, video, or subtitle.
-     * @throws IllegalStateException if called after {@link #close()}
-     *
-     * @see #getTrackInfo()
-     * @see #selectTrack(int)
-     * @see #deselectTrack(int)
-     */
-    @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();
-    }
-
-    /**
-     * Selects a track.
-     * <p>
-     * If a MediaPlayer2 is in invalid state, it throws an IllegalStateException exception.
-     * If a MediaPlayer2 is in <em>Started</em> state, the selected track is presented immediately.
-     * If a MediaPlayer2 is not in Started state, it just marks the track to be played.
-     * </p>
-     * <p>
-     * In any valid state, if it is called multiple times on the same type of track (ie. Video,
-     * Audio, Timed Text), the most recent one will be chosen.
-     * </p>
-     * <p>
-     * The first audio and video tracks are selected by default if available, even though
-     * this method is not called. However, no timed text track will be selected until
-     * this function is called.
-     * </p>
-     * <p>
-     * Currently, only timed text tracks or audio tracks can be selected via this method.
-     * In addition, the support for selecting an audio track at runtime is pretty limited
-     * in that an audio track can only be selected in the <em>Prepared</em> state.
-     * </p>
-     * @param index the index of the track to be selected. The valid range of the index
-     * is 0..total number of track - 1. The total number of tracks as well as the type of
-     * each individual track can be found by calling {@link #getTrackInfo()} method.
-     * @throws IllegalStateException if called in an invalid state.
-     *
-     * @see android.media.MediaPlayer2#getTrackInfo
-     */
-    @Override
-    public Object selectTrack(int index) {
-        return addTask(new Task(CALL_COMPLETED_SELECT_TRACK, false) {
-            @Override
-            void process() {
-                selectOrDeselectTrack(index, true /* select */);
-            }
-        });
-    }
-
-    /**
-     * Deselect a track.
-     * <p>
-     * Currently, the track must be a timed text track and no audio or video tracks can be
-     * deselected. If the timed text track identified by index has not been
-     * selected before, it throws an exception.
-     * </p>
-     * @param index the index of the track to be deselected. The valid range of the index
-     * is 0..total number of tracks - 1. The total number of tracks as well as the type of
-     * each individual track can be found by calling {@link #getTrackInfo()} method.
-     * @throws IllegalStateException if called in an invalid state.
-     *
-     * @see android.media.MediaPlayer2#getTrackInfo
-     */
-    @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/MediaPlayer2Utils.java b/media/java/android/media/MediaPlayer2Utils.java
new file mode 100644
index 0000000..c6dee22
--- /dev/null
+++ b/media/java/android/media/MediaPlayer2Utils.java
@@ -0,0 +1,41 @@
+/*
+ * 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.media;
+
+/**
+ * Helper class used by native code to reduce JNI calls from native side.
+ * @hide
+ */
+public class MediaPlayer2Utils {
+    /**
+     * Returns whether audio offloading is supported for the given audio format.
+     *
+     * @param encoding the type of encoding defined in {@link AudioFormat}
+     * @param sampleRate the sampling rate of the stream
+     * @param channelMask the channel mask defined in {@link AudioFormat}
+     */
+    // @CalledByNative
+    public static boolean isOffloadedAudioPlaybackSupported(
+            int encoding, int sampleRate, int channelMask) {
+        final AudioFormat format = new AudioFormat.Builder()
+                .setEncoding(encoding)
+                .setSampleRate(sampleRate)
+                .setChannelMask(channelMask)
+                .build();
+        return AudioManager.isOffloadedPlaybackSupported(format);
+    }
+}
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 0769e5c..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;
@@ -1050,9 +1051,9 @@
     android_media_MediaPlayer2_release(env, thiz);
 }
 
-static void android_media_MediaPlayer2_set_audio_session_id(JNIEnv *env,  jobject thiz,
+static void android_media_MediaPlayer2_setAudioSessionId(JNIEnv *env,  jobject thiz,
         jint sessionId) {
-    ALOGV("set_session_id(): %d", sessionId);
+    ALOGV("setAudioSessionId(): %d", sessionId);
     sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
     if (mp == NULL ) {
         jniThrowException(env, "java/lang/IllegalStateException", NULL);
@@ -1062,8 +1063,8 @@
             NULL);
 }
 
-static jint android_media_MediaPlayer2_get_audio_session_id(JNIEnv *env,  jobject thiz) {
-    ALOGV("get_session_id()");
+static jint android_media_MediaPlayer2_getAudioSessionId(JNIEnv *env,  jobject thiz) {
+    ALOGV("getAudioSessionId()");
     sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
     if (mp == NULL ) {
         jniThrowException(env, "java/lang/IllegalStateException", NULL);
@@ -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_get_audio_session_id},
-    {"_setAudioSessionId",   "(I)V",                             (void *)android_media_MediaPlayer2_set_audio_session_id},
-    {"_setAuxEffectSendLevel", "(F)V",                          (void *)android_media_MediaPlayer2_setAuxEffectSendLevel},
-    {"_attachAuxEffect",     "(I)V",                             (void *)android_media_MediaPlayer2_attachAuxEffect},
+    {"getAudioSessionId",   "()I",                              (void *)android_media_MediaPlayer2_getAudioSessionId},
+    {"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/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 9426148..942eafd 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -37,6 +37,7 @@
             ldflags: ["-Wl,--hash-style=both"],
         },
     },
+    version_script: "libjnigraphics.map.txt",
 }
 
 // The headers module is in frameworks/native/Android.bp.
diff --git a/packages/CaptivePortalLogin/AndroidManifest.xml b/packages/CaptivePortalLogin/AndroidManifest.xml
index 9ecaa03..5ab6632 100644
--- a/packages/CaptivePortalLogin/AndroidManifest.xml
+++ b/packages/CaptivePortalLogin/AndroidManifest.xml
@@ -26,7 +26,8 @@
     <uses-permission android:name="android.permission.NETWORK_SETTINGS" />
 
     <application android:label="@string/app_name"
-                 android:usesCleartextTraffic="true">
+                 android:usesCleartextTraffic="true"
+                 android:supportsRtl="true" >
         <activity
             android:name="com.android.captiveportallogin.CaptivePortalLoginActivity"
             android:label="@string/action_bar_label"
diff --git a/packages/CaptivePortalLogin/res/layout/ssl_error_msg.xml b/packages/CaptivePortalLogin/res/layout/ssl_error_msg.xml
new file mode 100644
index 0000000..d460041
--- /dev/null
+++ b/packages/CaptivePortalLogin/res/layout/ssl_error_msg.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/ssl_error_msg"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:textAppearance="?android:attr/textAppearanceSmall"
+    android:layout_marginStart="20dip"
+    android:layout_marginEnd="20dip"
+    android:gravity="center_vertical"
+    android:layout_marginBottom="4dip"
+    android:layout_marginTop="4dip" />
+
diff --git a/packages/CaptivePortalLogin/res/layout/ssl_warning.xml b/packages/CaptivePortalLogin/res/layout/ssl_warning.xml
new file mode 100644
index 0000000..ffd57a4
--- /dev/null
+++ b/packages/CaptivePortalLogin/res/layout/ssl_warning.xml
@@ -0,0 +1,86 @@
+<?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:orientation="vertical" >
+
+    <!-- ssl error type -->
+    <TextView
+        android:id="@+id/ssl_error_type"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="start"
+        android:text="SSL_UNKNOWN"
+        android:layout_marginStart="24dip"
+        android:layout_marginEnd="24dip"
+        android:layout_marginBottom="0dip"
+        android:layout_marginTop="24dip" />
+
+    <!-- Page info: -->
+    <TextView
+        android:id="@+id/page_info"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/page_info"
+        android:textStyle="bold"
+        android:layout_marginStart="24dip"
+        android:layout_marginEnd="24dip" />
+
+    <!-- Title: -->
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textStyle="bold"
+        android:layout_marginStart="24dip"
+        android:layout_marginEnd="24dip" />
+
+    <!-- Address: -->
+    <TextView
+        android:id="@+id/address_header"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/page_info_address"
+        android:layout_marginStart="24dip"
+        android:layout_marginEnd="24dip" />
+
+    <TextView
+        android:id="@+id/address"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="24dip"
+        android:layout_marginEnd="24dip" />
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingStart="4dip"
+        android:paddingEnd="4dip" >
+
+        <!-- certificate view: -->
+        <LinearLayout
+            android:id="@+id/certificate_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="16dip" >
+        </LinearLayout>
+
+    </ScrollView>
+
+</LinearLayout>
diff --git a/packages/CaptivePortalLogin/res/values-af/strings.xml b/packages/CaptivePortalLogin/res/values-af/strings.xml
index fa6f3fa..cf4dc82 100644
--- a/packages/CaptivePortalLogin/res/values-af/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-af/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"Die netwerk waarby jy probeer aansluit, het sekuriteitkwessies."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Byvoorbeeld, die aanmeldbladsy behoort dalk nie aan die organisasie wat gewys word nie."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Gaan in elk geval deur blaaier voort"</string>
+    <string name="ok" msgid="1509280796718850364">"OK"</string>
+    <string name="page_info" msgid="4048529256302257195">"Bladsy-inligting"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Adres:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Sekuriteitswaarskuwing"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Bekyk sertifikaat"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Hierdie sertifikaat is nie van \'n betroubare owerheid nie."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Die naam van die werf kom nie ooreen met die naam op die sertifikaat nie."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Hierdie sertifikaat het verval."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Hierdie sertifikaat is nog nie geldig nie."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Hierdie sertifikaat het \'n ongeldige datum."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Hierdie sertifikaat is ongeldig."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Onbekende sertifikaatfout."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-am/strings.xml b/packages/CaptivePortalLogin/res/values-am/strings.xml
index 36d5e19..cdcb5a5 100644
--- a/packages/CaptivePortalLogin/res/values-am/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-am/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"ለመቀላቀል እየሞከሩ ያሉት አውታረ መረብ የደህንነት ችግሮች አሉበት።"</string>
     <string name="ssl_error_example" msgid="647898534624078900">"ለምሳሌ፣ የመግቢያ ገጹ የሚታየው ድርጅት ላይሆን ይችላል።"</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"ለማንኛውም በአሳሽ በኩል ይቀጥሉ"</string>
+    <string name="ok" msgid="1509280796718850364">"እሺ"</string>
+    <string name="page_info" msgid="4048529256302257195">"የገፅ መረጃ"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"አድራሻ:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"የደህንነት ቅንብሮች"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"ምስክሮች ይመልከቱ"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"ይህ ምስክር ከታማኝ ቦታ አይደለም።"</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"የጣቢያው ስም ከምስክር ወረቀቱ ስም ጋር አይዛመድም።"</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"ይህ ምስክር ጊዜው አልፏል"</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"ይህ ምስክር ገና ትክክል አይደለም።"</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"ይህ ምስክር ትክክለኛ ቀን አለው።"</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"ይህ ምስክር ትክክል ያልሆነ ነው።"</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"ያልታወቀ የምስክር ስህተት።"</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-ar/strings.xml b/packages/CaptivePortalLogin/res/values-ar/strings.xml
index 8eb259b..7773eeb 100644
--- a/packages/CaptivePortalLogin/res/values-ar/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-ar/strings.xml
@@ -11,4 +11,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"الشبكة التي تحاول الانضمام إليها بها مشاكل أمنية."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"على سبيل المثال، قد لا تنتمي صفحة تسجيل الدخول إلى المنظمة المعروضة."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"المتابعة على أي حال عبر المتصفح"</string>
+    <string name="ok" msgid="1509280796718850364">"موافق"</string>
+    <string name="page_info" msgid="4048529256302257195">"معلومات الصفحة"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"العنوان:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"تحذير أمان"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"عرض الشهادة"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"هذه الشهادة ليست من جهة موثوق بها."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"لا يتطابق اسم الموقع مع الاسم على الشهادة."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"انتهت صلاحية هذه الشهادة."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"هذه الشهادة ليست صالحة بعد."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"تشتمل هذه الشهادة على تاريخ غير صالح."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"هذه الشهادة غير صالحة."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"حدث خطأ غير معروف بالشهادة."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-bg/strings.xml b/packages/CaptivePortalLogin/res/values-bg/strings.xml
index 8ce9deb..4dd8aa0 100644
--- a/packages/CaptivePortalLogin/res/values-bg/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-bg/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"Мрежата, към която опитвате да се присъедините, има проблеми със сигурността."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Например страницата за вход може да не принадлежи на показаната организация."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Продължаване през браузър въпреки това"</string>
+    <string name="ok" msgid="1509280796718850364">"OK"</string>
+    <string name="page_info" msgid="4048529256302257195">"Данни за страницата"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Адрес:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Предупреждение относно защитата"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Преглед на сертификата"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Сертификатът не е от надежден орган."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Името на сайта не съответства на името в сертификата."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Сертификатът е изтекъл."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Сертификатът още не е валиден."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Този сертификат е с невалидна дата."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Този сертификат е невалиден."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Неизвестна грешка в сертификата."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-bn/strings.xml b/packages/CaptivePortalLogin/res/values-bn/strings.xml
index b75d76e..fb703cf 100644
--- a/packages/CaptivePortalLogin/res/values-bn/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-bn/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"আপনি যে নেটওয়ার্কে যোগ দেওয়ার চেষ্টা করছেন তাতে নিরাপত্তার সমস্যা আছে।"</string>
     <string name="ssl_error_example" msgid="647898534624078900">"উদাহরণস্বরূপ, লগ-ইন পৃষ্ঠাটি প্রদর্শিত প্রতিষ্ঠানের অন্তর্গত নাও হতে পারে৷"</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"যাই হোক না কেন ব্রাউজারের মাধ্যমে অবিরত রাখুন"</string>
+    <string name="ok" msgid="1509280796718850364">"OK"</string>
+    <string name="page_info" msgid="4048529256302257195">"Sideinfo"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Adresse:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Sikkerhetsadvarsel"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Vis sertifikat"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Sertifikatet er ikke fra en pålitelig myndighet."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Navnet på nettstedet samsvarer ikke med navnet på sertifikatet."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Sertifikatet er utløpt."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Sertifikatet er ikke gyldig ennå."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Dette sertifikatet har en ugyldig dato."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Dette sertifikatet er ugyldig."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Ukjent sertifikatfeil."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-ca/strings.xml b/packages/CaptivePortalLogin/res/values-ca/strings.xml
index fe189ed..a2c9ed8 100644
--- a/packages/CaptivePortalLogin/res/values-ca/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-ca/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"La xarxa a què et vols connectar té problemes de seguretat."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Per exemple, la pàgina d\'inici de sessió podria no pertànyer a l\'organització que es mostra."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Continua igualment mitjançant el navegador"</string>
+    <string name="ok" msgid="1509280796718850364">"D\'acord"</string>
+    <string name="page_info" msgid="4048529256302257195">"Informació de la pàgina"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Adreça:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Advertiment de seguretat"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Visualitza el certificat"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Aquest certificat no és d\'una autoritat de confiança."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"El nom del lloc no coincideix amb el del certificat."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Aquest certificat ha caducat."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Aquest certificat encara no és vàlid."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Aquest certificat té una data no vàlida."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Aquest certificat no és vàlid."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Error de certificat desconegut."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-cs/strings.xml b/packages/CaptivePortalLogin/res/values-cs/strings.xml
index 09dcc5f..be649a5 100644
--- a/packages/CaptivePortalLogin/res/values-cs/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-cs/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"Síť, ke které se pokoušíte připojit, má bezpečnostní problémy."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Například přihlašovací stránka nemusí patřit do zobrazované organizace."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Přesto pokračovat prostřednictvím prohlížeče"</string>
+    <string name="ok" msgid="1509280796718850364">"OK"</string>
+    <string name="page_info" msgid="4048529256302257195">"Informace o stránce"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Adresa:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Upozornění zabezpečení"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Zobrazit certifikát"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Tento certifikát nepochází od důvěryhodné autority."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Název webu se neshoduje s názvem uvedeným v certifikátu."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Platnost certifikátu vypršela."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Tento certifikát ještě není platný."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Datum tohoto certifikátu není platné."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Tento certifikát je neplatný."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Neznámá chyba certifikátu."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-da/strings.xml b/packages/CaptivePortalLogin/res/values-da/strings.xml
index dc0dd17..8183105 100644
--- a/packages/CaptivePortalLogin/res/values-da/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-da/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"Der er sikkerhedsproblemer på det netværk, du forsøger at logge ind på."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Det er f.eks. ikke sikkert, at loginsiden tilhører den anførte organisation."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Fortsæt alligevel via browseren"</string>
+    <string name="ok" msgid="1509280796718850364">"OK"</string>
+    <string name="page_info" msgid="4048529256302257195">"Sideoplysninger"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Adresse:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Sikkerhedsadvarsel"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Vis certifikat"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Dette certifikat stammer ikke fra en troværdig autoritet."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Navnet på websitet stemmer ikke overens med navnet på certifikatet."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Dette certifikat er udløbet."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Dette certifikat er endnu ikke gyldigt."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Dette certifikat har en ugyldig dato."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Dette certifikat er ugyldigt."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Ukendt fejl i certifikatet."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-de/strings.xml b/packages/CaptivePortalLogin/res/values-de/strings.xml
index d8f7be9..a9b7415 100644
--- a/packages/CaptivePortalLogin/res/values-de/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-de/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"Im Netzwerk, zu dem du eine Verbindung herstellen möchtest, liegen Sicherheitsprobleme vor."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Beispiel: Die Log-in-Seite gehört eventuell nicht zur angezeigten Organisation."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Trotzdem in einem Browser fortfahren"</string>
+    <string name="ok" msgid="1509280796718850364">"OK"</string>
+    <string name="page_info" msgid="4048529256302257195">"Seiteninfo"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Adresse:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Sicherheitswarnung"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Zertifikat ansehen"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Dieses Zertifikat wurde nicht von einer vertrauenswürdigen Stelle ausgegeben."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Name der Website stimmt nicht mit dem Namen auf dem Zertifikat überein."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Dieses Zertifikat ist abgelaufen."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Dieses Zertifikat ist noch nicht gültig."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Dieses Zertifikat weist ein ungültiges Datum auf."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Dieses Zertifikat ist ungültig."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Unbekannter Zertifikatfehler"</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-el/strings.xml b/packages/CaptivePortalLogin/res/values-el/strings.xml
index cb61710..16bf6e2 100644
--- a/packages/CaptivePortalLogin/res/values-el/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-el/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"Παρουσιάζονται προβλήματα ασφάλειας στο δίκτυο στο οποίο προσπαθείτε να συνδεθείτε."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Για παράδειγμα, η σελίδα σύνδεσης ενδέχεται να μην ανήκει στον οργανισμό που εμφανίζεται."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Συνέχεια ούτως ή άλλως μέσω του προγράμματος περιήγησης"</string>
+    <string name="ok" msgid="1509280796718850364">"OK"</string>
+    <string name="page_info" msgid="4048529256302257195">"Πληροφορίες σελίδας"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Διεύθυνση:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Προειδοποίηση ασφαλείας"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Προβολή πιστοποιητικού"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Αυτό το πιστοποιητικό δεν προέρχεται από αξιόπιστη αρχή."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Το όνομα του ιστότοπου δεν αντιστοιχεί με το όνομα στο πιστοποιητικό."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Αυτό το πιστοποιητικό έχει λήξει."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Αυτό το πιστοποιητικό δεν είναι έγκυρο ακόμα."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Αυτό το πιστοποιητικό δεν έχει έγκυρη ημερομηνία."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Αυτό το πιστοποιητικό δεν είναι έγκυρο."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Άγνωστο σφάλμα πιστοποιητικού."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-en-rGB/strings.xml b/packages/CaptivePortalLogin/res/values-en-rGB/strings.xml
index 2e8d1f0..f940299 100644
--- a/packages/CaptivePortalLogin/res/values-en-rGB/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-en-rGB/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"The network that you’re trying to join has security issues."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"For example, the login page might not belong to the organisation shown."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Continue anyway via browser"</string>
+    <string name="ok" msgid="1509280796718850364">"OK"</string>
+    <string name="page_info" msgid="4048529256302257195">"Page info"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Address:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Security warning"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"View certificate"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"This certificate isn\'t from a trusted authority."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"The name of the site doesn\'t match the name on the certificate."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"This certificate has expired."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"This certificate isn\'t valid yet."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"This certificate has an invalid date."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"This certificate is invalid."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Unknown certificate error."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-en-rIN/strings.xml b/packages/CaptivePortalLogin/res/values-en-rIN/strings.xml
index 2e8d1f0..f940299 100644
--- a/packages/CaptivePortalLogin/res/values-en-rIN/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-en-rIN/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"The network that you’re trying to join has security issues."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"For example, the login page might not belong to the organisation shown."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Continue anyway via browser"</string>
+    <string name="ok" msgid="1509280796718850364">"OK"</string>
+    <string name="page_info" msgid="4048529256302257195">"Page info"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Address:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Security warning"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"View certificate"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"This certificate isn\'t from a trusted authority."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"The name of the site doesn\'t match the name on the certificate."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"This certificate has expired."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"This certificate isn\'t valid yet."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"This certificate has an invalid date."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"This certificate is invalid."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Unknown certificate error."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-es-rUS/strings.xml b/packages/CaptivePortalLogin/res/values-es-rUS/strings.xml
index 5d7ba91..c011664 100644
--- a/packages/CaptivePortalLogin/res/values-es-rUS/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-es-rUS/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"La red a la que intentas conectarte tiene problemas de seguridad."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Por ejemplo, es posible que la página de acceso no pertenezca a la organización que aparece."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Continuar de todos modos desde el navegador"</string>
+    <string name="ok" msgid="1509280796718850364">"Aceptar"</string>
+    <string name="page_info" msgid="4048529256302257195">"Información de la página"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Dirección:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Advertencia de seguridad"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Ver certificado"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Este certificado no proviene de una autoridad confiable."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"El nombre del sitio no coincide con el nombre del certificado."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Este certificado ha expirado."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Este certificado aún no es válido."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"La fecha de este certificado no es válida."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Este certificado no es válido."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Error de certificado desconocido"</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-es/strings.xml b/packages/CaptivePortalLogin/res/values-es/strings.xml
index da2eae9..65244e7 100644
--- a/packages/CaptivePortalLogin/res/values-es/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-es/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"La red a la que intentas unirte tiene problemas de seguridad."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Por ejemplo, es posible que la página de inicio de sesión no pertenezca a la organización mostrada."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Continuar de todos modos a través del navegador"</string>
+    <string name="ok" msgid="1509280796718850364">"Aceptar"</string>
+    <string name="page_info" msgid="4048529256302257195">"Información de la página"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Dirección:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Advertencia de seguridad"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Ver certificado"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Este certificado no procede de una entidad de certificación de confianza."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"El nombre del sitio no coincide con el del certificado."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Este certificado ha caducado."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Este certificado aún no es válido."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"La fecha de este certificado no es válida."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Este certificado no es válido."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Error de certificado desconocido"</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-et/strings.xml b/packages/CaptivePortalLogin/res/values-et/strings.xml
index 41fcb9a..e4c4c98 100644
--- a/packages/CaptivePortalLogin/res/values-et/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-et/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"Võrgul, millega üritate ühenduse luua, on turvaprobleeme."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Näiteks ei pruugi sisselogimisleht kuuluda kuvatavale organisatsioonile."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Jätka siiski brauseris"</string>
+    <string name="ok" msgid="1509280796718850364">"OK"</string>
+    <string name="page_info" msgid="4048529256302257195">"Lehe teave"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Aadress:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Turvahoiatus"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Kuva sertifikaat"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"See sertifikaat ei pärine usaldusväärselt asutuselt."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Saidi nimi ei vasta sertifikaadil olevale nimele."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"See sertifikaat on aegunud."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"See sertifikaat pole veel kehtiv."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Sellel sertifikaadil on kehtetu kuupäev."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"See sertifikaat on kehtetu."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Tundmatu sertifikaadiviga."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-fa/strings.xml b/packages/CaptivePortalLogin/res/values-fa/strings.xml
index 2e4cc51..27b9b7f 100644
--- a/packages/CaptivePortalLogin/res/values-fa/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-fa/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"شبکه‌ای که می‌خواهید به آن بپیوندید مشکلات امنیتی دارد."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"به عنوان مثال، صفحه ورود به سیستم ممکن است متعلق به سازمان نشان داده شده نباشد."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"در هر صورت از طریق مرورگر ادامه یابد"</string>
+    <string name="ok" msgid="1509280796718850364">"تأیید"</string>
+    <string name="page_info" msgid="4048529256302257195">"اطلاعات صفحه"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"آدرس:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"اخطار امنیتی"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"مشاهده گواهی"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"این گواهی از یک منبع مورد اطمینان صادر نشده است."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"نام سایت با نام موجود در گواهی مطابقت ندارد."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"این گواهی منقضی شده است."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"این گواهی هنوز معتبر نیست."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"تاریخ این گواهی نامعتبر است."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"این گواهی نامعتبر است."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"خطای ناشناخته در گواهی."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-fi/strings.xml b/packages/CaptivePortalLogin/res/values-fi/strings.xml
index 1976f7d..8086fbf 100644
--- a/packages/CaptivePortalLogin/res/values-fi/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-fi/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"Verkossa, johon yrität muodostaa yhteyttä, on turvallisuusongelmia."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Kirjautumissivu ei välttämättä kuulu näytetylle organisaatiolle."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Jatka silti selaimen kautta."</string>
+    <string name="ok" msgid="1509280796718850364">"OK"</string>
+    <string name="page_info" msgid="4048529256302257195">"Sivun tiedot"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Osoite:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Suojausvaroitus"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Näytä varmenne"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Varmenteen myöntäjä ei ole luotettava taho."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Sivuston nimi ei vastaa varmenteessa olevaa nimeä."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Varmenne ei ole enää voimassa."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Varmenne ei ole vielä voimassa."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Varmenteen päiväys ei kelpaa."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Varmenne on virheellinen."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Tuntematon varmennevirhe."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-fr/strings.xml b/packages/CaptivePortalLogin/res/values-fr/strings.xml
index 8f98bb5..39fc569 100644
--- a/packages/CaptivePortalLogin/res/values-fr/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-fr/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"Le réseau que vous essayez de rejoindre présente des problèmes de sécurité."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Par exemple, la page de connexion peut ne pas appartenir à l\'organisation représentée."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Continuer quand même dans le navigateur"</string>
+    <string name="ok" msgid="1509280796718850364">"OK"</string>
+    <string name="page_info" msgid="4048529256302257195">"Infos sur la page"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Adresse :"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Avertissement de sécurité"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Afficher le certificat"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Ce certificat provient d\'une autorité non approuvée."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Le nom du site ne correspond pas au nom indiqué dans le certificat."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Le certificat a expiré."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Ce certificat n\'est pas encore valide."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"La date de ce certificat n\'est pas valide."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Ce certificat n\'est pas valide."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Erreur : Certificat inconnu."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-hi/strings.xml b/packages/CaptivePortalLogin/res/values-hi/strings.xml
index 1bacc46..d924fff 100644
--- a/packages/CaptivePortalLogin/res/values-hi/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-hi/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"आप जिस नेटवर्क में शामिल होने का प्रयास कर रहे हैं उसमें सुरक्षा समस्‍याएं हैं."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"उदाहरण के लिए, हो सकता है कि लॉगिन पृष्‍ठ दिखाए गए संगठन से संबद्ध ना हो."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"ब्राउज़र के द्वारा फिर जारी रखें"</string>
+    <string name="ok" msgid="1509280796718850364">"ठीक"</string>
+    <string name="page_info" msgid="4048529256302257195">"पृष्ठ जानकारी"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"पता:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"सुरक्षा चेतावनी"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"प्रमाणपत्र देखें"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"यह प्रमाणपत्र किसी विश्वस्त प्राधिकारी का नहीं है."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"साइट का नाम, प्रमाणपत्र के नाम से मिलान नहीं करता."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"इस प्रमाणपत्र की समय सीमा समाप्त हो गई है."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"यह प्रमाणपत्र अभी तक मान्य नहीं है."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"इस प्रमाणपत्र में एक अमान्‍य दिनांक है."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"यह प्रमाणपत्र अमान्य है."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"अज्ञात प्रमाणपत्र त्रुटि."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-hr/strings.xml b/packages/CaptivePortalLogin/res/values-hr/strings.xml
index e44cd3b..11b1dd3 100644
--- a/packages/CaptivePortalLogin/res/values-hr/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-hr/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"Mreža kojoj se pokušavate pridružiti ima sigurnosne poteškoće."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Na primjer, stranica za prijavu možda ne pripada prikazanoj organizaciji."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Ipak nastavi putem preglednika"</string>
+    <string name="ok" msgid="1509280796718850364">"U redu"</string>
+    <string name="page_info" msgid="4048529256302257195">"Informacije o stranici"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Adresa:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Upozorenje o sigurnosti"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Prikaži certifikat"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Ovaj certifikat ne potječe iz pouzdanog izvora."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Naziv web-lokacije ne podudara se s nazivom na certifikatu."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Ovaj je certifikat istekao."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Ovaj certifikat još nije važeći."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Ovaj certifikat ima nevažeći datum."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Ovaj certifikat nije valjan."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Nepoznata pogreška certifikata."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-hu/strings.xml b/packages/CaptivePortalLogin/res/values-hu/strings.xml
index f15fb49..145e2ab 100644
--- a/packages/CaptivePortalLogin/res/values-hu/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-hu/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"Biztonsági problémák vannak azzal a hálózattal, amelyhez csatlakozni szeretne."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Például lehet, hogy a bejelentkezési oldal nem a megjelenített szervezethez tartozik."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Folytatás ennek ellenére böngészőn keresztül"</string>
+    <string name="ok" msgid="1509280796718850364">"OK"</string>
+    <string name="page_info" msgid="4048529256302257195">"Oldaladatok"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Cím:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Biztonsági figyelmeztetés"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Tanúsítvány megtekintése"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Ez a tanúsítvány nem hiteles tanúsítványkibocsátótól származik."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"A webhely neve nem egyezik a tanúsítványon lévő névvel."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"A tanúsítvány lejárt."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"A tanúsítvány még nem érvényes."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"A tanúsítvány dátuma érvénytelen."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Ez a tanúsítvány érvénytelen."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Ismeretlen tanúsítványhiba."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-in/strings.xml b/packages/CaptivePortalLogin/res/values-in/strings.xml
index 10e3de6..4a335dd 100644
--- a/packages/CaptivePortalLogin/res/values-in/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-in/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"Jaringan yang ingin Anda masuki mengalami masalah keamanan."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Misalnya, halaman masuk mungkin bukan milik organisasi yang ditampilkan."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Tetap lanjutkan melalui browser"</string>
+    <string name="ok" msgid="1509280796718850364">"Oke"</string>
+    <string name="page_info" msgid="4048529256302257195">"Info laman"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Alamat:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Peringatan sertifikat"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Lihat sertifikat"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Sertifikat ini tidak berasal dari otoritas tepercaya."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Nama situs tidak cocok dengan nama pada sertifikat."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Sertifikat ini telah kedaluwarsa."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Sertifikat ini belum valid."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Tanggal sertifikat ini tidak valid."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Sertifikat ini tidak valid."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Kesalahan sertifikat tak dikenal."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-it/strings.xml b/packages/CaptivePortalLogin/res/values-it/strings.xml
index a01a553..2cc4038 100644
--- a/packages/CaptivePortalLogin/res/values-it/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-it/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"La rete a cui stai tentando di accedere presenta problemi di sicurezza."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Ad esempio, la pagina di accesso potrebbe non appartenere all\'organizzazione indicata."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Continua comunque dal browser"</string>
+    <string name="ok" msgid="1509280796718850364">"OK"</string>
+    <string name="page_info" msgid="4048529256302257195">"Info pagina"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Indirizzo:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Avviso di sicurezza"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Visualizza certificato"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Questo certificato non proviene da un\'autorità attendibile."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Il nome del sito non corrisponde al nome nel certificato."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Il certificato è scaduto."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Questo certificato non è ancora valido."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Questo certificato presenta una data non valida."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Questo certificato non è valido."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Errore certificato sconosciuto."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-iw/strings.xml b/packages/CaptivePortalLogin/res/values-iw/strings.xml
index 8e7915d..527e692 100644
--- a/packages/CaptivePortalLogin/res/values-iw/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-iw/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"יש בעיות אבטחה ברשת שאליה אתה מנסה להתחבר."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"לדוגמה, ייתכן שדף ההתחברות אינו שייך לארגון המוצג."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"המשך בכל זאת באמצעות דפדפן"</string>
+    <string name="ok" msgid="1509280796718850364">"אישור"</string>
+    <string name="page_info" msgid="4048529256302257195">"פרטי דף"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"כתובת:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"אזהרת אבטחה"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"הצג אישור"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"אישור זה אינו מגיע מרשות אמינה."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"שם האתר לא תואם לשם באישור."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"פג תוקפו של אישור זה."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"אישור זה אינו חוקי עדיין."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"לאישור זה יש תאריך בלתי חוקי."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"אישור זה אינו חוקי."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"שגיאת אישור לא ידועה."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-ja/strings.xml b/packages/CaptivePortalLogin/res/values-ja/strings.xml
index e275b95..bcc8686 100644
--- a/packages/CaptivePortalLogin/res/values-ja/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-ja/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"接続しようとしているネットワークにセキュリティの問題があります。"</string>
     <string name="ssl_error_example" msgid="647898534624078900">"たとえば、ログインページが表示されている組織に属していない可能性があります。"</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"ブラウザから続行"</string>
+    <string name="ok" msgid="1509280796718850364">"OK"</string>
+    <string name="page_info" msgid="4048529256302257195">"ページ情報"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"アドレス:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"セキュリティ警告"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"証明書を表示"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"この証明書は信頼できる認証機関のものではありません。"</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"サイト名と証明書上の名前が一致しません。"</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"この証明書は有効期限切れです。"</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"この証明書はまだ有効ではありません。"</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"この証明書の日付は無効です。"</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"この証明書は無効です。"</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"不明な証明書エラーです。"</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-ko/strings.xml b/packages/CaptivePortalLogin/res/values-ko/strings.xml
index 75f2b48..7a7f7e0 100644
--- a/packages/CaptivePortalLogin/res/values-ko/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-ko/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"가입하려는 네트워크에 보안 문제가 있습니다."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"예를 들어 로그인 페이지가 표시된 조직에 속하지 않을 수 있습니다."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"브라우저를 통해 계속하기"</string>
+    <string name="ok" msgid="1509280796718850364">"확인"</string>
+    <string name="page_info" msgid="4048529256302257195">"페이지 정보"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"주소:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"보안 경고"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"인증서 보기"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"신뢰할 수 있는 인증 기관에서 발급한 인증서가 아닙니다."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"사이트 이름이 인증서에 있는 것과 일치하지 않습니다."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"인증서가 만료되었습니다."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"인증서가 아직 유효하지 않습니다."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"인증서 날짜가 유효하지 않습니다."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"인증서가 잘못되었습니다."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"알 수 없는 인증서 오류입니다."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-lt/strings.xml b/packages/CaptivePortalLogin/res/values-lt/strings.xml
index 17da83f..158f7ce 100644
--- a/packages/CaptivePortalLogin/res/values-lt/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-lt/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"Kilo tinklo, prie kurio bandote prisijungti, problemų."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Pavyzdžiui, prisijungimo puslapis gali nepriklausyti rodomai organizacijai."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Vis tiek tęsti naudojant naršyklę"</string>
+    <string name="ok" msgid="1509280796718850364">"Gerai"</string>
+    <string name="page_info" msgid="4048529256302257195">"Puslapio informacija"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Adresas:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Saugos įspėjimas"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Žiūrėti sertifikatą"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Šį sertifikatą išdavė nepatikima įstaiga."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Svetainės pavadinimas neatitinka sertifikate nurodyto pavadinimo."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Šio sertifikato galiojimo laikas baigėsi."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Šis sertifikatas dar negalioja."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Šio sertifikato data netinkama."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Šis sertifikatas netinkamas."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Nežinoma sertifikato klaida."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-lv/strings.xml b/packages/CaptivePortalLogin/res/values-lv/strings.xml
index 95b8558..a42cb22 100644
--- a/packages/CaptivePortalLogin/res/values-lv/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-lv/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"Tīklam, kuram mēģināt pievienoties, ir drošības problēmas."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Piemēram, pieteikšanās lapa, iespējams, nepieder norādītajai organizācijai."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Tik un tā turpināt, izmantojot pārlūkprogrammu"</string>
+    <string name="ok" msgid="1509280796718850364">"Labi"</string>
+    <string name="page_info" msgid="4048529256302257195">"Lapas informācija"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Adrese:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Drošības brīdinājums"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Skatīt sertifikātu"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Šo sertifikātu nav izsniegusi uzticama iestāde."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Vietnes nosaukums neatbilst nosaukumam sertifikātā."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Šī sertifikāta derīguma termiņš ir beidzies."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Šis sertifikāts vēl nav derīgs."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Šī sertifikāta datums nav derīgs."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Šis sertifikāts nav derīgs."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Nezināma sertifikāta kļūda."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-ms/strings.xml b/packages/CaptivePortalLogin/res/values-ms/strings.xml
index 933721a..aaa51c8 100644
--- a/packages/CaptivePortalLogin/res/values-ms/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-ms/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"Rangkaian yang anda cuba sertai mempunyai isu keselamatan."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Contohnya, halaman log masuk mungkin bukan milik organisasi yang ditunjukkan."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Teruskan juga melalui penyemak imbas"</string>
+    <string name="ok" msgid="1509280796718850364">"OK"</string>
+    <string name="page_info" msgid="4048529256302257195">"Maklumat halaman"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Alamat:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Amaran keselamatan"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Lihat sijil"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Sijil ini bukan daripada pihak berkuasa yang dipercayai."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Nama tapak tidak sepadan dengan nama pada sijil."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Sijil ini telah tamat tempoh."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Sijil ini belum lagi sah."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Sijil ini mempunyai tarikh yang tidak sah."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Sijil ini tidak sah."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Ralat sijil tidak diketahui."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-nb/strings.xml b/packages/CaptivePortalLogin/res/values-nb/strings.xml
index 0dd5b6c..29c23ed 100644
--- a/packages/CaptivePortalLogin/res/values-nb/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-nb/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"Nettverket du prøver å logge på, har sikkerhetsproblemer."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Det er for eksempel mulig at påloggingssiden kanskje ikke tilhører organisasjonen som vises."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Fortsett likevel via nettleseren"</string>
+    <string name="ok" msgid="1509280796718850364">"OK"</string>
+    <string name="page_info" msgid="4048529256302257195">"Sideinfo"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Adresse:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Sikkerhetsadvarsel"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Vis sertifikat"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Sertifikatet er ikke fra en pålitelig myndighet."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Navnet på nettstedet samsvarer ikke med navnet på sertifikatet."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Sertifikatet er utløpt."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Sertifikatet er ikke gyldig ennå."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Dette sertifikatet har en ugyldig dato."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Dette sertifikatet er ugyldig."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Ukjent sertifikatfeil."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-nl/strings.xml b/packages/CaptivePortalLogin/res/values-nl/strings.xml
index 1c59601..2cbca06 100644
--- a/packages/CaptivePortalLogin/res/values-nl/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-nl/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"Het netwerk waarmee u verbinding probeert te maken, heeft beveiligingsproblemen."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Zo hoort de weergegeven inlogpagina misschien niet bij de weergegeven organisatie."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Toch doorgaan via browser"</string>
+    <string name="ok" msgid="1509280796718850364">"OK"</string>
+    <string name="page_info" msgid="4048529256302257195">"Pagina-informatie"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Adres:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Beveiligingsmelding"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Certificaat weergeven"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Dit is geen certificaat van een vertrouwde autoriteit."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"De naam van deze site komt niet overeen met de naam op het certificaat."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Dit certificaat is verlopen."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Dit certificaat is nog niet geldig."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Dit certificaat heeft een ongeldige datum."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Dit certificaat is ongeldig."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Onbekende certificaatfout."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-pl/strings.xml b/packages/CaptivePortalLogin/res/values-pl/strings.xml
index 17f20df..9ba066e 100644
--- a/packages/CaptivePortalLogin/res/values-pl/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-pl/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"W sieci, z którą próbujesz się połączyć, występują problemy z zabezpieczeniami."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Na przykład strona logowania może nie należeć do wyświetlanej organizacji."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Kontynuuj mimo to w przeglądarce"</string>
+    <string name="ok" msgid="1509280796718850364">"OK"</string>
+    <string name="page_info" msgid="4048529256302257195">"Informacje o stronie"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Adres:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Ostrzeżenie zabezpieczeń"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Wyświetl certyfikat"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Certyfikat nie pochodzi od zaufanego urzędu."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Nazwa witryny nie pasuje do nazwy na certyfikacie."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Ten certyfikat wygasł."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Certyfikat nie jest jeszcze ważny."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Certyfikat ma nieprawidłową datę."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Certyfikat jest nieprawidłowy."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Nieznany błąd certyfikatu"</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-pt-rPT/strings.xml b/packages/CaptivePortalLogin/res/values-pt-rPT/strings.xml
index 94b9d60..5bef235 100644
--- a/packages/CaptivePortalLogin/res/values-pt-rPT/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-pt-rPT/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"A rede à qual está a tentar aceder tem problemas de segurança."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Por exemplo, a página de início de sessão pode não pertencer à entidade apresentada."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Continuar mesmo assim através do navegador"</string>
+    <string name="ok" msgid="1509280796718850364">"OK"</string>
+    <string name="page_info" msgid="4048529256302257195">"Informações da página"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Endereço:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Aviso de segurança"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Ver certificado"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Este certificado não pertence a uma autoridade fidedigna."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"O nome do Web site não corresponde ao nome constante no certificado."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Este certificado expirou."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Este certificado ainda não é válido."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Este certificado tem uma data inválida."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Este certificado é inválido."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Erro: certificado desconhecido."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-pt/strings.xml b/packages/CaptivePortalLogin/res/values-pt/strings.xml
index 3d1064c..ebe4148 100644
--- a/packages/CaptivePortalLogin/res/values-pt/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-pt/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"A rede à qual você está tentando se conectar tem problemas de segurança."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Por exemplo, a página de login pode não pertencer à organização mostrada."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Continuar mesmo assim pelo navegador"</string>
+    <string name="ok" msgid="1509280796718850364">"OK"</string>
+    <string name="page_info" msgid="4048529256302257195">"Informações da página"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Endereço:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Aviso de segurança"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Visualizar certificado"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Este certificado não é de uma autoridade confiável."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"O nome do site não corresponde ao nome no certificado."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Este certificado expirou."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Este certificado ainda não é válido."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Este certificado tem uma data inválida."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Este certificado é inválido."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Erro de certificado desconhecido."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-ro/strings.xml b/packages/CaptivePortalLogin/res/values-ro/strings.xml
index cf1b6b5..e2e4eac 100644
--- a/packages/CaptivePortalLogin/res/values-ro/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-ro/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"Rețeaua la care încercați să vă conectați are probleme de securitate."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"De exemplu, este posibil ca pagina de conectare să nu aparțină organizației afișate."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Continuați oricum prin browser"</string>
+    <string name="ok" msgid="1509280796718850364">"OK"</string>
+    <string name="page_info" msgid="4048529256302257195">"Informaţii pagină"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Adresă:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Avertisment de securitate"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Vizualizaţi certificatul"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Acest certificat nu provine de la o autoritate de încredere."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Numele acestui site nu se potriveşte cu numele de pe certificat."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Acest certificat a expirat."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Acest certificat nu este încă valid."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Acest certificat are o dată nevalidă."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Acest certificat este nevalid."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Eroare de certificat necunoscută."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-ru/strings.xml b/packages/CaptivePortalLogin/res/values-ru/strings.xml
index 6966bcd..c0153e6 100644
--- a/packages/CaptivePortalLogin/res/values-ru/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-ru/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"Сеть, к которой вы хотите подключиться, небезопасна."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Например, страница входа в аккаунт может быть фиктивной."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Игнорировать и открыть браузер"</string>
+    <string name="ok" msgid="1509280796718850364">"ОК"</string>
+    <string name="page_info" msgid="4048529256302257195">"Информация о странице"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Адрес:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Угроза безопасности"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Просмотреть сертификат"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Этот сертификат получен из ненадежных источников."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Название сайта не соответствует названию в сертификате."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Срок действия сертификата истек."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Сертификат еще не действителен."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Дата этого сертификата недействительна."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Этот сертификат недействителен."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Неизвестная ошибка сертификата."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-sk/strings.xml b/packages/CaptivePortalLogin/res/values-sk/strings.xml
index 54763be..8ba24b1 100644
--- a/packages/CaptivePortalLogin/res/values-sk/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-sk/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"Sieť, ku ktorej sa pokúšate pripojiť, má problémy so zabezpečením"</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Napríklad prihlasovacia stránka nemusí patriť uvedenej organizácii."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Pokračovať pomocou prehliadača"</string>
+    <string name="ok" msgid="1509280796718850364">"OK"</string>
+    <string name="page_info" msgid="4048529256302257195">"Informácie o stránke"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Adresa:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Upozornenie zabezpečenia"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Zobraziť certifikát"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Tento certifikát nepochádza od dôveryhodnej autority."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Názov stránky sa nezhoduje s názvom uvedeným v certifikáte."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Platnosť certifikátu skončila."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Tento certifikát zatiaľ nie je platný."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Tento certifikát má neplatný dátum."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Tento certifikát je neplatný."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Neznáma chyba certifikátu."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-sl/strings.xml b/packages/CaptivePortalLogin/res/values-sl/strings.xml
index 7dd0b37..b7d9a8a 100644
--- a/packages/CaptivePortalLogin/res/values-sl/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-sl/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"Omrežje, ki se mu poskušate pridružiti, ima varnostne težave."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Stran za prijavo na primer morda ne pripada prikazani organizaciji."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Vseeno nadaljuj v brskalniku"</string>
+    <string name="ok" msgid="1509280796718850364">"V redu"</string>
+    <string name="page_info" msgid="4048529256302257195">"Podatki o strani"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Naslov:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Varnostno opozorilo"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Prikaži potrdilo"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Potrdila ni izdal zaupanja vreden overitelj."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Ime spletnega mesta se ne ujema z imenom na potrdilu."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Potrdilo je poteklo."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"To potrdilo še ni veljavno."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Potrdilo ima neveljaven datum."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"To potrdilo ni veljavno."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Neznana napaka potrdila."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-sr/strings.xml b/packages/CaptivePortalLogin/res/values-sr/strings.xml
index f604289..967c8ba 100644
--- a/packages/CaptivePortalLogin/res/values-sr/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-sr/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"Мрежа којој покушавате да се придружите има безбедносних проблема."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"На пример, страница за пријављивање можда не припада приказаној организацији."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Ипак настави преко прегледача"</string>
+    <string name="ok" msgid="1509280796718850364">"Потврди"</string>
+    <string name="page_info" msgid="4048529256302257195">"Информације о страници"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Адреса:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Безбедносно упозорење"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Прикажи сертификат"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Овај сертификат не потиче од поузданог ауторитета."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Назив сајта се не подудара са називом на сертификату."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Овај сертификат је истекао."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Овај сертификат још увек није важећи."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Датум овог сертификата је неважећи."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Овај сертификат је неважећи."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Непозната грешка сертификата."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-sv/strings.xml b/packages/CaptivePortalLogin/res/values-sv/strings.xml
index 8cf7041..75356f0 100644
--- a/packages/CaptivePortalLogin/res/values-sv/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-sv/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"Nätverket du försöker ansluta till har säkerhetsproblem."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Det kan t.ex. hända att inloggningssidan inte tillhör den organisation som visas."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Fortsätt ändå via webbläsaren"</string>
+    <string name="ok" msgid="1509280796718850364">"OK"</string>
+    <string name="page_info" msgid="4048529256302257195">"Sidinformation"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Adress:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Säkerhetsvarning"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Visa certifikat"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Certifikatet kommer inte från en betrodd utfärdare."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Webbplatsens namn stämmer inte med namnet på certifikatet."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Certifikatet har upphört att gälla."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Certifikatet är inte giltigt än."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Det här certifikatet har ett ogiltigt datum."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Certifikatet är ogiltigt."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Okänt certifikatfel."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-sw/strings.xml b/packages/CaptivePortalLogin/res/values-sw/strings.xml
index 1c8b6e1..feb2dde 100644
--- a/packages/CaptivePortalLogin/res/values-sw/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-sw/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"Mtandao unaojaribu kujiunga nao una matatizo ya usalama."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Kwa mfano, ukurasa wa kuingia katika akaunti unaweza usiwe unamilikiwa na shirika lililoonyeshwa."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Endelea hata hivyo kupitia kivinjari"</string>
+    <string name="ok" msgid="1509280796718850364">"Sawa"</string>
+    <string name="page_info" msgid="4048529256302257195">"Maelezo ya ukurasa"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Anwani:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Ilani ya usalama"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Tazama cheti"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Cheti hiki hakijatoka kwa mamlaka inayoaminika."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Jina la tovuti halilingani na jina lililo katika cheti."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Cheti hiki kimepitwa na muda"</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Cheti bado si halali."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Cheti hiki kina tarehe batili."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Hati hii ni batili."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Hitilafu isiyojulikana ya cheti."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-th/strings.xml b/packages/CaptivePortalLogin/res/values-th/strings.xml
index 9a3a626..11a2131 100644
--- a/packages/CaptivePortalLogin/res/values-th/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-th/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"เครือข่ายที่คุณพยายามเข้าร่วมมีปัญหาด้านความปลอดภัย"</string>
     <string name="ssl_error_example" msgid="647898534624078900">"ตัวอย่างเช่น หน้าเข้าสู่ระบบอาจไม่ใช่ขององค์กรที่แสดงไว้"</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"ดำเนินการต่อผ่านเบราว์เซอร์"</string>
+    <string name="ok" msgid="1509280796718850364">"ตกลง"</string>
+    <string name="page_info" msgid="4048529256302257195">"ข้อมูลหน้าเว็บ"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"ที่อยู่:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"คำเตือนเกี่ยวกับความปลอดภัย"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"ดูใบรับรอง"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"ใบรับรองนี้ไม่ได้มาจากผู้ออกที่เชื่อถือได้"</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"ชื่อไซต์ไม่ตรงกับในใบรับรอง"</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"ใบรับรองนี้หมดอายุแล้ว"</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"ใบรับรองนี้ยังใช้งานไม่ได้"</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"ใบรับรองนี้มีวันที่ไม่ถูกต้อง"</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"ใบรับรองนี้ไม่ถูกต้อง"</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"ข้อผิดพลาดใบรับรองที่ไม่รู้จัก"</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-tl/strings.xml b/packages/CaptivePortalLogin/res/values-tl/strings.xml
index 565ef8f..07a2479 100644
--- a/packages/CaptivePortalLogin/res/values-tl/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-tl/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"May mga isyu sa seguridad ang network kung saan mo sinusubukang sumali."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Halimbawa, maaaring hindi sa organisasyong ipinapakita ang page sa pag-log in."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Magpatuloy pa rin sa pamamagitan ng browser"</string>
+    <string name="ok" msgid="1509280796718850364">"OK"</string>
+    <string name="page_info" msgid="4048529256302257195">"Impormasyon ng pahina"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Address:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Babala sa seguridad"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Tingnan ang certificate"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Ang certificate ay hindi mula sa isang pinagkakatiwalaang kinauukulan."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Ang pangalan ng site ay hindi tumutugma sa pangalan sa certificate."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Nag-expire na ang certificate na ito."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Wala pang bisa ang certificate na ito."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Ang certificate ay mayroong di-wastong petsa."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Di-wasto ang certificate na ito."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Hindi kilalang error ng certificate."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-tr/strings.xml b/packages/CaptivePortalLogin/res/values-tr/strings.xml
index 73d2455..cdedd33 100644
--- a/packages/CaptivePortalLogin/res/values-tr/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-tr/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"Katılmaya çalıştığınız ağda güvenlik sorunları var."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Örneğin, giriş sayfası, gösterilen kuruluşa ait olmayabilir."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Yine de tarayıcıyla devam et"</string>
+    <string name="ok" msgid="1509280796718850364">"Tamam"</string>
+    <string name="page_info" msgid="4048529256302257195">"Sayfa bilgileri"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Adres:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Güvenlik uyarısı"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Sertifikayı görüntüle"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Bu sertifika güvenilir bir yetkiliden değil."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Sitenin adı sertifika üzerindeki adla eşleşmiyor."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Bu sertifikanın süresi dolmuş."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Bu sertifika henüz geçerli değil."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Bu sertifikanın tarihi geçersiz."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Bu sertifika geçersiz."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Bilinmeyen sertifika hatası."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-uk/strings.xml b/packages/CaptivePortalLogin/res/values-uk/strings.xml
index 0e818d3..0f4cd16 100644
--- a/packages/CaptivePortalLogin/res/values-uk/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-uk/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"У мережі, до якої ви намагаєтеся під’єднатись, є проблеми з безпекою."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Наприклад, сторінка входу може не належати вказаній організації."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Усе одно продовжити у веб-переглядачі"</string>
+    <string name="ok" msgid="1509280796718850364">"OK"</string>
+    <string name="page_info" msgid="4048529256302257195">"Інфо про стор."</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Адреса:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Застереж. про небезп."</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Переглянути сертиф."</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Сертифікат видано ненадійним центром сертифікації."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Назва сайту не збігається з назвою в сертифікаті."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Термін дії сертиф. завершився."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Цей сертифікат ще не дійсний."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Цей сертифікат має недійсну дату."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Цей сертифікат недійсний."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Помилка невідомого сертифіката."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-vi/strings.xml b/packages/CaptivePortalLogin/res/values-vi/strings.xml
index e51d2aa..9c702b9 100644
--- a/packages/CaptivePortalLogin/res/values-vi/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-vi/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"Mạng mà bạn đang cố gắng tham gia có vấn đề về bảo mật."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Ví dụ, trang đăng nhập có thể không thuộc về tổ chức được hiển thị."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Vẫn tiếp tục qua trình duyệt"</string>
+    <string name="ok" msgid="1509280796718850364">"OK"</string>
+    <string name="page_info" msgid="4048529256302257195">"Thông tin trang"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Địa chỉ:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Cảnh báo bảo mật"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Xem chứng chỉ"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Chứng chỉ này không xuất phát từ tổ chức phát hành đáng tin cậy."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Tên của trang web không khớp với tên trên chứng chỉ."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Chứng chỉ này đã hết hạn."</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Chứng chỉ này chưa hợp lệ."</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Chứng chỉ này có ngày không hợp lệ."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Chứng chỉ này không hợp lệ."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Lỗi chứng chỉ không xác định."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-zh-rCN/strings.xml b/packages/CaptivePortalLogin/res/values-zh-rCN/strings.xml
index ce822e7..70c2a08 100644
--- a/packages/CaptivePortalLogin/res/values-zh-rCN/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-zh-rCN/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"您尝试加入的网络存在安全问题。"</string>
     <string name="ssl_error_example" msgid="647898534624078900">"例如,登录页面可能并不属于页面上显示的单位。"</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"仍然通过浏览器继续操作"</string>
+    <string name="ok" msgid="1509280796718850364">"确定"</string>
+    <string name="page_info" msgid="4048529256302257195">"网页信息"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"网址:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"安全警告"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"查看证书"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"该证书并非来自可信的授权中心。"</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"网站的名称与证书上的名称不一致。"</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"该证书已过期。"</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"该证书尚未生效。"</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"该证书的日期无效。"</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"该证书无效。"</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"未知证书错误。"</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-zh-rHK/strings.xml b/packages/CaptivePortalLogin/res/values-zh-rHK/strings.xml
index 9010e1e..df1c700 100644
--- a/packages/CaptivePortalLogin/res/values-zh-rHK/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-zh-rHK/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"您正在嘗試加入的網絡有安全性問題。"</string>
     <string name="ssl_error_example" msgid="647898534624078900">"例如,登入頁面並不屬於所顯示的機構。"</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"透過瀏覽器繼續"</string>
+    <string name="ok" msgid="1509280796718850364">"確定"</string>
+    <string name="page_info" msgid="4048529256302257195">"網頁資訊"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"地址:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"安全性警告"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"查看憑證"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"這個憑證並非由受信任的權威機構發出。"</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"網站名稱與憑證上的名稱不相符。"</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"這個憑證已過期。"</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"這個憑證尚未生效。"</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"此憑證的日期無效。"</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"此憑證是無效的。"</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"不明的憑證錯誤。"</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-zh-rTW/strings.xml b/packages/CaptivePortalLogin/res/values-zh-rTW/strings.xml
index 5b535e2..2a2e397 100644
--- a/packages/CaptivePortalLogin/res/values-zh-rTW/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-zh-rTW/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"你嘗試加入的網路有安全問題。"</string>
     <string name="ssl_error_example" msgid="647898534624078900">"例如,登入網頁中顯示的機構可能並非該網頁實際隸屬的機構。"</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"透過瀏覽器繼續"</string>
+    <string name="ok" msgid="1509280796718850364">"確定"</string>
+    <string name="page_info" msgid="4048529256302257195">"頁面資訊"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"位址:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"安全性警告"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"檢視憑證"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"這個憑證並非來自信任的授權單位。"</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"網站名稱與憑證上的名稱不相符。"</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"此憑證已過期"</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"這個憑證尚未生效。"</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"這個憑證的日期無效。"</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"這個憑證無效。"</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"不明的憑證錯誤。"</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values-zu/strings.xml b/packages/CaptivePortalLogin/res/values-zu/strings.xml
index 866ba18..7943645 100644
--- a/packages/CaptivePortalLogin/res/values-zu/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-zu/strings.xml
@@ -9,4 +9,16 @@
     <string name="ssl_error_warning" msgid="6653188881418638872">"Inethiwekhi ozama ukuyijoyina inezinkinga zokuvikela."</string>
     <string name="ssl_error_example" msgid="647898534624078900">"Isibonelo, ikhasi lokungena ngemvume kungenzeka lingelenhlangano ebonisiwe."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Qhubeka noma kunjalo ngesiphequluli"</string>
+    <string name="ok" msgid="1509280796718850364">"KULUNGILE"</string>
+    <string name="page_info" msgid="4048529256302257195">"Ulwazi lekhasi"</string>
+    <string name="page_info_address" msgid="2222306609532903254">"Ikheli:"</string>
+    <string name="ssl_security_warning_title" msgid="6607795404322797541">"Isexwayiso sokuvikeleka"</string>
+    <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Buka isitifiketi"</string>
+    <string name="ssl_error_untrusted" msgid="7754507359360636447">"Lesi sitifiketi asiphumi embusweni othembekile."</string>
+    <string name="ssl_error_mismatch" msgid="3809794439740523641">"Igama lale ngosi alifani negama elikusitifiketi."</string>
+    <string name="ssl_error_expired" msgid="5739349389499575559">"Lesi sitifiketi siphelelwe yisikhathi"</string>
+    <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Lesi sitifiketi asilungile okwamanje"</string>
+    <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Lesi sitifiketi sinosuku olungalungile."</string>
+    <string name="ssl_error_invalid" msgid="9041704741505449967">"Lesi sitifiketi asilungile."</string>
+    <string name="ssl_error_unknown" msgid="5679243486524754571">"Iphutha lesitifiketi elingaziwa."</string>
 </resources>
diff --git a/packages/CaptivePortalLogin/res/values/strings.xml b/packages/CaptivePortalLogin/res/values/strings.xml
index f486fe4..e9698db 100644
--- a/packages/CaptivePortalLogin/res/values/strings.xml
+++ b/packages/CaptivePortalLogin/res/values/strings.xml
@@ -9,5 +9,17 @@
     <string name="ssl_error_warning">The network you&#8217;re trying to join has security issues.</string>
     <string name="ssl_error_example">For example, the login page may not belong to the organization shown.</string>
     <string name="ssl_error_continue">Continue anyway via browser</string>
+    <string name="ssl_error_untrusted">This certificate isn\'t from a trusted authority.</string>
+    <string name="ssl_error_mismatch">The name of the site doesn\'t match the name on the certificate.</string>
+    <string name="ssl_error_expired">This certificate has expired.</string>
+    <string name="ssl_error_not_yet_valid">This certificate isn\'t valid yet.</string>
+    <string name="ssl_error_date_invalid">This certificate has an invalid date.</string>
+    <string name="ssl_error_invalid">This certificate is invalid.</string>
+    <string name="ssl_error_unknown">Unknown certificate error.</string>
+    <string name="ssl_security_warning_title">Security warning</string>
+    <string name="ssl_error_view_certificate">View certificate</string>
+    <string name="ok">OK</string>
+    <string name="page_info_address">Address:</string>
+    <string name="page_info">Page info</string>
 
 </resources>
diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
index 0ba37ae..83084c5 100644
--- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
+++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
@@ -20,8 +20,10 @@
 import static android.net.captiveportal.CaptivePortalProbeSpec.HTTP_LOCATION_HEADER_NAME;
 
 import android.app.Activity;
+import android.app.AlertDialog;
 import android.app.LoadedApk;
 import android.content.Context;
+import android.content.DialogInterface;
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.net.CaptivePortal;
@@ -33,6 +35,7 @@
 import android.net.Proxy;
 import android.net.Uri;
 import android.net.captiveportal.CaptivePortalProbeSpec;
+import android.net.http.SslCertificate;
 import android.net.http.SslError;
 import android.net.wifi.WifiInfo;
 import android.os.Build;
@@ -42,8 +45,9 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
-import android.util.TypedValue;
 import android.util.SparseArray;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
@@ -52,8 +56,8 @@
 import android.webkit.WebChromeClient;
 import android.webkit.WebSettings;
 import android.webkit.WebView;
-import android.webkit.WebView;
 import android.webkit.WebViewClient;
+import android.widget.LinearLayout;
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
@@ -276,6 +280,13 @@
     @Override
     public void onDestroy() {
         super.onDestroy();
+        final WebView webview = (WebView) findViewById(R.id.webview);
+        if (webview != null) {
+            webview.stopLoading();
+            webview.setWebViewClient(null);
+            webview.setWebChromeClient(null);
+            webview.destroy();
+        }
         if (mNetworkCallback != null) {
             // mNetworkCallback is not null if mUrl is not null.
             mCm.unregisterNetworkCallback(mNetworkCallback);
@@ -382,6 +393,7 @@
         private static final String INTERNAL_ASSETS = "file:///android_asset/";
 
         private final String mBrowserBailOutToken = Long.toString(new Random().nextLong());
+        private final String mCertificateOutToken = Long.toString(new Random().nextLong());
         // How many Android device-independent-pixels per scaled-pixel
         // dp/sp = (px/sp) / (px/dp) = (1/sp) / (1/dp)
         private final float mDpPerSp = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 1,
@@ -397,6 +409,10 @@
             return mPagesLoaded > 1;
         }
 
+        private String mSslErrorTitle = null;
+        private SslErrorHandler mSslErrorHandler = null;
+        private SslError mSslError = null;
+
         @Override
         public void onPageStarted(WebView view, String urlString, Bitmap favicon) {
             if (urlString.contains(mBrowserBailOutToken)) {
@@ -473,12 +489,16 @@
             logMetricsEvent(MetricsEvent.CAPTIVE_PORTAL_LOGIN_ACTIVITY_SSL_ERROR);
             final String sslErrorPage = makeSslErrorPage();
             view.loadDataWithBaseURL(INTERNAL_ASSETS, sslErrorPage, "text/HTML", "UTF-8", null);
+            mSslErrorTitle = view.getTitle() == null ? "" : view.getTitle();
+            mSslErrorHandler = handler;
+            mSslError = error;
         }
 
         private String makeSslErrorPage() {
             final String warningMsg = getString(R.string.ssl_error_warning);
             final String exampleMsg = getString(R.string.ssl_error_example);
             final String continueMsg = getString(R.string.ssl_error_continue);
+            final String certificateMsg = getString(R.string.ssl_error_view_certificate);
             return String.join("\n",
                     "<html>",
                     "<head>",
@@ -516,13 +536,18 @@
                     "      text-decoration:none;",
                     "      text-transform:uppercase;",
                     "    }",
+                    "    a.certificate {",
+                    "      margin-top:0px;",
+                    "    }",
                     "  </style>",
                     "</head>",
                     "<body>",
                     "  <p><img src=quantum_ic_warning_amber_96.png><br>",
                     "  <div class=warn>" + warningMsg + "</div>",
                     "  <div class=example>" + exampleMsg + "</div>",
-                    "  <a href=" + mBrowserBailOutToken + ">" + continueMsg + "</a>",
+                    "  <a href=" + mBrowserBailOutToken + ">" + continueMsg + "</a><br>",
+                    "  <a class=certificate href=" + mCertificateOutToken + ">" + certificateMsg +
+                            "</a>",
                     "</body>",
                     "</html>");
         }
@@ -533,8 +558,50 @@
                 startActivity(new Intent(Intent.ACTION_DIAL, Uri.parse(url)));
                 return true;
             }
+            if (url.contains(mCertificateOutToken) && mSslError != null) {
+                showSslAlertDialog(mSslErrorHandler, mSslError, mSslErrorTitle);
+                return true;
+            }
             return false;
         }
+        private void showSslAlertDialog(SslErrorHandler handler, SslError error, String title) {
+            final LayoutInflater factory = LayoutInflater.from(CaptivePortalLoginActivity.this);
+            final View sslWarningView = factory.inflate(R.layout.ssl_warning, null);
+
+            // Set Security certificate
+            setViewSecurityCertificate(sslWarningView.findViewById(R.id.certificate_layout), error);
+            ((TextView) sslWarningView.findViewById(R.id.ssl_error_type))
+                    .setText(sslErrorName(error));
+            ((TextView) sslWarningView.findViewById(R.id.title)).setText(mSslErrorTitle);
+            ((TextView) sslWarningView.findViewById(R.id.address)).setText(error.getUrl());
+
+            AlertDialog sslAlertDialog = new AlertDialog.Builder(CaptivePortalLoginActivity.this)
+                    .setTitle(R.string.ssl_security_warning_title)
+                    .setView(sslWarningView)
+                    .setPositiveButton(R.string.ok, (DialogInterface dialog, int whichButton) -> {
+                        // handler.cancel is called via OnCancelListener.
+                        dialog.cancel();
+                    })
+                    .setOnCancelListener((DialogInterface dialogInterface) -> handler.cancel())
+                    .create();
+            sslAlertDialog.show();
+        }
+
+        private void setViewSecurityCertificate(LinearLayout certificateLayout, SslError error) {
+            SslCertificate cert = error.getCertificate();
+
+            View certificateView = cert.inflateCertificateView(CaptivePortalLoginActivity.this);
+            final LinearLayout placeholder = (LinearLayout) certificateView
+                    .findViewById(com.android.internal.R.id.placeholder);
+            LayoutInflater factory = LayoutInflater.from(CaptivePortalLoginActivity.this);
+
+            TextView textView = (TextView) factory.inflate(
+                    R.layout.ssl_error_msg, placeholder, false);
+            textView.setText(sslErrorMessage(error));
+            placeholder.addView(textView);
+
+            certificateLayout.addView(certificateView);
+        }
     }
 
     private class MyWebChromeClient extends WebChromeClient {
@@ -587,4 +654,18 @@
     private static String sslErrorName(SslError error) {
         return SSL_ERRORS.get(error.getPrimaryError(), "UNKNOWN");
     }
+
+    private static final SparseArray<Integer> SSL_ERROR_MSGS = new SparseArray<>();
+    static {
+        SSL_ERROR_MSGS.put(SslError.SSL_NOTYETVALID,  R.string.ssl_error_not_yet_valid);
+        SSL_ERROR_MSGS.put(SslError.SSL_EXPIRED,      R.string.ssl_error_expired);
+        SSL_ERROR_MSGS.put(SslError.SSL_IDMISMATCH,   R.string.ssl_error_mismatch);
+        SSL_ERROR_MSGS.put(SslError.SSL_UNTRUSTED,    R.string.ssl_error_untrusted);
+        SSL_ERROR_MSGS.put(SslError.SSL_DATE_INVALID, R.string.ssl_error_date_invalid);
+        SSL_ERROR_MSGS.put(SslError.SSL_INVALID,      R.string.ssl_error_invalid);
+    }
+
+    private static Integer sslErrorMessage(SslError error) {
+        return SSL_ERROR_MSGS.get(error.getPrimaryError(), R.string.ssl_error_unknown);
+    }
 }
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 4abcf73..c9ee5c8 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -541,14 +541,14 @@
     }
 
     @Override
-    public Cursor querySearchDocuments(String rootId, String query, String[] projection)
+    public Cursor querySearchDocuments(String rootId, String[] projection, Bundle queryArgs)
             throws FileNotFoundException {
         final File parent;
         synchronized (mRootsLock) {
             parent = mRoots.get(rootId).path;
         }
 
-        return querySearchDocuments(parent, query, projection, Collections.emptySet());
+        return querySearchDocuments(parent, projection, Collections.emptySet(), queryArgs);
     }
 
     @Override
diff --git a/packages/SettingsLib/SettingsSpinner/res/values/styles.xml b/packages/SettingsLib/SettingsSpinner/res/values/styles.xml
index 8447b08..8af20e2 100644
--- a/packages/SettingsLib/SettingsSpinner/res/values/styles.xml
+++ b/packages/SettingsLib/SettingsSpinner/res/values/styles.xml
@@ -17,7 +17,7 @@
 
 <resources>
     <style name="SettingsSpinnerTitleBar">
-        <item name="android:textAppearance">?android:attr/textAppearance</item>
+        <item name="android:textAppearance">?android:attr/textAppearanceButton</item>
         <item name="android:paddingStart">16dp</item>
         <item name="android:paddingEnd">36dp</item>
         <item name="android:paddingTop">8dp</item>
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/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 0dbc037..2f082b9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -152,7 +152,11 @@
     private boolean mNetworkScoringUiEnabled;
     private long mMaxSpeedLabelScoreCacheAge;
 
-
+    private static final String WIFI_SECURITY_PSK = "PSK";
+    private static final String WIFI_SECURITY_EAP = "EAP";
+    private static final String WIFI_SECURITY_SAE = "SAE";
+    private static final String WIFI_SECURITY_OWE = "OWE";
+    private static final String WIFI_SECURITY_SUITE_B_192 = "SUITE_B_192";
 
     @VisibleForTesting
     Scanner mScanner;
@@ -505,13 +509,18 @@
      * {@link #updateAccessPoints(List, List)}.
      */
     private void fetchScansAndConfigsAndUpdateAccessPoints() {
-        final List<ScanResult> newScanResults = mWifiManager.getScanResults();
+        List<ScanResult> newScanResults = mWifiManager.getScanResults();
+
+        // Filter all unsupported networks from the scan result list
+        final List<ScanResult> filteredScanResults =
+                filterScanResultsByCapabilities(newScanResults);
+
         if (isVerboseLoggingEnabled()) {
-            Log.i(TAG, "Fetched scan results: " + newScanResults);
+            Log.i(TAG, "Fetched scan results: " + filteredScanResults);
         }
 
         List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
-        updateAccessPoints(newScanResults, configs);
+        updateAccessPoints(filteredScanResults, configs);
     }
 
     /** Update the internal list of access points. */
@@ -937,4 +946,49 @@
 
         mListener.onAccessPointsChanged();
     }
+
+    /**
+     * Filters unsupported networks from scan results. New WPA3 networks and OWE networks
+     * may not be compatible with the device HW/SW.
+     * @param scanResults List of scan results
+     * @return List of filtered scan results based on local device capabilities
+     */
+    private List<ScanResult> filterScanResultsByCapabilities(List<ScanResult> scanResults) {
+        if (scanResults == null) {
+            return null;
+        }
+
+        // Get and cache advanced capabilities
+        final boolean isOweSupported = mWifiManager.isOweSupported();
+        final boolean isSaeSupported = mWifiManager.isWpa3SaeSupported();
+        final boolean isSuiteBSupported = mWifiManager.isWpa3SuiteBSupported();
+
+        List<ScanResult> filteredScanResultList = new ArrayList<>();
+
+        // Iterate through the list of scan results and filter out APs which are not
+        // compatible with our device.
+        for (ScanResult scanResult : scanResults) {
+            if (scanResult.capabilities.contains(WIFI_SECURITY_PSK)) {
+                // All devices (today) support RSN-PSK or WPA-PSK
+                // Add this here because some APs may support both PSK and SAE and the check
+                // below will filter it out.
+                filteredScanResultList.add(scanResult);
+                continue;
+            }
+
+            if ((scanResult.capabilities.contains(WIFI_SECURITY_SUITE_B_192) && !isSuiteBSupported)
+                    || (scanResult.capabilities.contains(WIFI_SECURITY_SAE) && !isSaeSupported)
+                    || (scanResult.capabilities.contains(WIFI_SECURITY_OWE) && !isOweSupported)) {
+                if (isVerboseLoggingEnabled()) {
+                    Log.v(TAG, "filterScanResultsByCapabilities: Filtering SSID "
+                            + scanResult.SSID + " with capabilities: " + scanResult.capabilities);
+                }
+            } else {
+                // Safe to add
+                filteredScanResultList.add(scanResult);
+            }
+        }
+
+        return filteredScanResultList;
+    }
 }
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
index b51ad1c..51f6a4b 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
@@ -20,8 +20,8 @@
 <!-- This is a view that shows general status information in Keyguard. -->
 <com.android.keyguard.KeyguardSliceView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_marginStart="16dp"
-    android:layout_marginEnd="16dp"
+    android:paddingStart="16dp"
+    android:paddingEnd="16dp"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:layout_gravity="center_horizontal"
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/drawable/stat_sys_camera.xml b/packages/SystemUI/res/drawable/stat_sys_camera.xml
new file mode 100644
index 0000000..eb3e963
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_camera.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2018, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+    android:insetLeft="3dp"
+    android:insetRight="3dp">
+    <vector
+            android:width="17dp"
+            android:height="17dp"
+            android:viewportWidth="24.0"
+            android:viewportHeight="24.0">
+        <path
+            android:fillColor="#FFF"
+            android:pathData="M20,5h-3.17L15,3H9L7.17,5H4C2.9,5 2,5.9 2,7v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V7C22,5.9 21.1,5 20,5zM20,19H4V7h16V19zM12,9c-2.21,0 -4,1.79 -4,4c0,2.21 1.79,4 4,4s4,-1.79 4,-4C16,10.79 14.21,9 12,9z"/>
+    </vector>
+</inset>
diff --git a/packages/SystemUI/res/drawable/stat_sys_mic_none.xml b/packages/SystemUI/res/drawable/stat_sys_mic_none.xml
new file mode 100644
index 0000000..d6bdf9f
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_mic_none.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2018, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="18dp"
+    android:height="18dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FFF"
+        android:pathData="M12,14c1.66,0 3,-1.34 3,-3V5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v6C9,12.66 10.34,14 12,14zM11,5c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v6c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1V5z"/>
+    <path
+        android:fillColor="#FFF"
+        android:pathData="M17,11c0,2.76 -2.24,5 -5,5s-5,-2.24 -5,-5H5c0,3.53 2.61,6.43 6,6.92V21h2v-3.08c3.39,-0.49 6,-3.39 6,-6.92H17z"/>
+</vector>
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/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index f554150..890bf5d 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -62,7 +62,7 @@
                 android:layout_weight="1"
                 android:layout_marginEnd="32dp"
                 android:ellipsize="marquee"
-                android:textAppearance="@style/TextAppearance.QS.TileLabel"
+                android:textAppearance="@style/TextAppearance.QS.CarrierInfo"
                 android:textColor="?android:attr/textColorPrimary"
                 android:textDirection="locale"
                 android:singleLine="true" />
diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml
index 980442c..f34161e 100644
--- a/packages/SystemUI/res/layout/qs_tile_label.xml
+++ b/packages/SystemUI/res/layout/qs_tile_label.xml
@@ -79,7 +79,7 @@
         android:padding="0dp"
         android:visibility="gone"
         android:gravity="center"
-        android:textAppearance="@style/TextAppearance.QS.TileLabel"
+        android:textAppearance="@style/TextAppearance.QS.TileLabel.Secondary"
         android:textColor="?android:attr/textColorSecondary"/>
 
     <View
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 0e41a7f..97f5f86 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -369,6 +369,7 @@
     <dimen name="qs_page_indicator_height">8dp</dimen>
     <dimen name="qs_tile_icon_size">24dp</dimen>
     <dimen name="qs_tile_text_size">12sp</dimen>
+    <dimen name="qs_carrier_info_text_size">14sp</dimen>
     <dimen name="qs_tile_divider_height">1dp</dimen>
     <dimen name="qs_panel_padding">16dp</dimen>
     <dimen name="qs_dual_tile_height">112dp</dimen>
@@ -939,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 6244e1c..e9aa1b6 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -125,7 +125,7 @@
 
     <style name="TextAppearance.StatusBar.Clock" parent="@*android:style/TextAppearance.StatusBar.Icon">
         <item name="android:textSize">@dimen/status_bar_clock_size</item>
-        <item name="android:fontFamily">sans-serif-medium</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
         <item name="android:textColor">@color/status_bar_clock_color</item>
     </style>
 
@@ -135,7 +135,7 @@
 
     <style name="TextAppearance.StatusBar.Expanded.Clock">
         <item name="android:textSize">@dimen/qs_time_expanded_size</item>
-        <item name="android:fontFamily">sans-serif-medium</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
         <item name="android:textColor">?android:attr/textColorPrimary</item>
         <item name="android:textStyle">normal</item>
     </style>
@@ -240,9 +240,31 @@
 
     <style name="TextAppearance.QS.TileLabel">
         <item name="android:textSize">@dimen/qs_tile_text_size</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+    </style>
+
+    <style name="TextAppearance.QS.TileLabel.Secondary">
+        <item name="android:textSize">@dimen/qs_tile_text_size</item>
         <item name="android:fontFamily">sans-serif</item>
     </style>
 
+    <style name="TextAppearance.QS.CarrierInfo">
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+        <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/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index 79966f7..c41ef0e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -243,46 +243,6 @@
         }
     }
 
-    /**
-     * Breaks a string in 2 lines where both have similar character count
-     * but first line is always longer.
-     *
-     * @param charSequence Original text.
-     * @return Optimal string.
-     */
-    private static CharSequence findBestLineBreak(CharSequence charSequence) {
-        if (TextUtils.isEmpty(charSequence)) {
-            return charSequence;
-        }
-
-        String source = charSequence.toString();
-        // Ignore if there is only 1 word,
-        // or if line breaks were manually set.
-        if (source.contains("\n") || !source.contains(" ")) {
-            return source;
-        }
-
-        final String[] words = source.split(" ");
-        final StringBuilder optimalString = new StringBuilder(source.length());
-        int current = 0;
-        while (optimalString.length() < source.length() - optimalString.length()) {
-            optimalString.append(words[current]);
-            if (current < words.length - 1) {
-                optimalString.append(" ");
-            }
-            current++;
-        }
-        optimalString.append("\n");
-        for (int i = current; i < words.length; i++) {
-            optimalString.append(words[i]);
-            if (current < words.length - 1) {
-                optimalString.append(" ");
-            }
-        }
-
-        return optimalString.toString();
-    }
-
     public void setDarkAmount(float darkAmount) {
         mDarkAmount = darkAmount;
         mRow.setDarkAmount(darkAmount);
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/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index ce84b84..01a2345 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -33,8 +33,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dependency;
 
-import java.util.concurrent.atomic.AtomicBoolean;
-
 /**
  * Controls the screen brightness when dozing.
  */
@@ -66,7 +64,6 @@
      * --ei brightness_bucket 1}
      */
     private int mDebugBrightnessBucket = -1;
-    private AtomicBoolean mIsDestroyed = new AtomicBoolean();
 
     @VisibleForTesting
     public DozeScreenBrightness(Context context, DozeMachine.Service service,
@@ -89,9 +86,7 @@
             Dependency.get(Dependency.BG_HANDLER).post(()-> {
                 IntentFilter filter = new IntentFilter();
                 filter.addAction(ACTION_AOD_BRIGHTNESS);
-                if (!mIsDestroyed.get()) {
-                    mContext.registerReceiverAsUser(this, UserHandle.ALL, filter, null, handler);
-                }
+                mContext.registerReceiverAsUser(this, UserHandle.ALL, filter, null, handler);
             });
         }
     }
@@ -129,10 +124,11 @@
     }
 
     private void onDestroy() {
-        mIsDestroyed.set(true);
         setLightSensorEnabled(false);
         if (mDebuggable) {
-            mContext.unregisterReceiver(this);
+            Dependency.get(Dependency.BG_HANDLER).post(()-> {
+                mContext.unregisterReceiver(this);
+            });
         }
     }
 
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 f409902..85e99f0 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
@@ -23,22 +23,27 @@
 typealias Privacy = PrivacyType
 
 enum class PrivacyType(val nameId: Int, val iconId: Int) {
-    TYPE_CAMERA(R.string.privacy_type_camera, com.android.internal.R.drawable.ic_camera),
+    TYPE_CAMERA(R.string.privacy_type_camera, R.drawable.stat_sys_camera),
     TYPE_LOCATION(R.string.privacy_type_location, R.drawable.stat_sys_location),
-    TYPE_MICROPHONE(R.string.privacy_type_microphone, R.drawable.ic_mic_26dp);
+    TYPE_MICROPHONE(R.string.privacy_type_microphone, R.drawable.stat_sys_mic_none);
 
     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/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index 938dc0b..90890c0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.Intent;
+import android.hardware.display.ColorDisplayManager;
 import android.metrics.LogMaker;
 import android.provider.Settings;
 import android.service.quicksettings.Tile;
@@ -54,7 +55,6 @@
     private static final String PATTERN_HOUR_MINUTE = "h:mm a";
     private static final String PATTERN_HOUR_NINUTE_24 = "HH:mm";
 
-
     private ColorDisplayController mController;
     private boolean mIsListening;
 
@@ -65,7 +65,7 @@
 
     @Override
     public boolean isAvailable() {
-        return ColorDisplayController.isAvailable(mContext);
+        return ColorDisplayManager.isNightDisplayAvailable(mContext);
     }
 
     @Override
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/NotificationUiAdjustment.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
index f42c6ef..f23ae3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
@@ -53,7 +53,8 @@
 
     public static NotificationUiAdjustment extractFromNotificationEntry(
             NotificationData.Entry entry) {
-        return new NotificationUiAdjustment(entry.key, entry.smartActions, entry.smartReplies);
+        return new NotificationUiAdjustment(
+                entry.key, entry.systemGeneratedSmartActions, entry.smartReplies);
     }
 
     public static boolean needReinflate(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java
index 4e712a5..da6d977 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java
@@ -114,8 +114,9 @@
         public CharSequence remoteInputText;
         public List<SnoozeCriterion> snoozeCriteria;
         public int userSentiment = Ranking.USER_SENTIMENT_NEUTRAL;
+        /** Smart Actions provided by the NotificationAssistantService. */
         @NonNull
-        public List<Notification.Action> smartActions = Collections.emptyList();
+        public List<Notification.Action> systemGeneratedSmartActions = Collections.emptyList();
         public CharSequence[] smartReplies = new CharSequence[0];
 
         private int mCachedContrastColor = COLOR_INVALID;
@@ -171,7 +172,7 @@
             importance = ranking.getImportance();
             snoozeCriteria = ranking.getSnoozeCriteria();
             userSentiment = ranking.getUserSentiment();
-            smartActions = ranking.getSmartActions() == null
+            systemGeneratedSmartActions = ranking.getSmartActions() == null
                     ? Collections.emptyList() : ranking.getSmartActions();
             smartReplies = ranking.getSmartReplies() == null
                     ? new CharSequence[0]
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 3bea7db..274d4b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -702,7 +702,6 @@
                 && !mPresenter.isPresenterFullyCollapsed();
         row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
         row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
-        row.setSmartActions(entry.smartActions);
         row.setEntry(entry);
 
         row.updateInflationFlag(FLAG_CONTENT_VIEW_HEADS_UP, shouldHeadsUp(entry));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 5166e06..c7876cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -39,7 +39,6 @@
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.Notification;
 import android.app.NotificationChannel;
 import android.content.Context;
 import android.content.pm.PackageInfo;
@@ -1567,10 +1566,6 @@
         mNotificationInflater.setUsesIncreasedHeight(use);
     }
 
-    public void setSmartActions(List<Notification.Action> smartActions) {
-        mNotificationInflater.setSmartActions(smartActions);
-    }
-
     public void setUseIncreasedHeadsUpHeight(boolean use) {
         mUseIncreasedHeadsUpHeight = use;
         mNotificationInflater.setUsesIncreasedHeadsUpHeight(use);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index b838c9b..37bf06e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -21,6 +21,8 @@
 import static android.service.notification.NotificationListenerService.Ranking
         .USER_SENTIMENT_NEGATIVE;
 
+import static com.android.systemui.statusbar.notification.row.NotificationInfo.ACTION_NONE;
+
 import android.app.INotificationManager;
 import android.app.NotificationChannel;
 import android.content.Context;
@@ -189,7 +191,13 @@
             } else if (gutsView instanceof AppOpsInfo) {
                 initializeAppOpsInfo(row, (AppOpsInfo) gutsView);
             } else if (gutsView instanceof NotificationInfo) {
-                initializeNotificationInfo(row, (NotificationInfo) gutsView);
+                int action;
+                if (item instanceof NotificationMenuRow.NotificationInfoMenuItem) {
+                    action = ((NotificationMenuRow.NotificationInfoMenuItem) item).mAction;
+                } else {
+                    action = ACTION_NONE;
+                }
+                initializeNotificationInfo(row, (NotificationInfo) gutsView, action);
             }
             return true;
         } catch (Exception e) {
@@ -246,14 +254,15 @@
 
     /**
      * Sets up the {@link NotificationInfo} inside the notification row's guts.
-     *
      * @param row view to set up the guts for
      * @param notificationInfoView view to set up/bind within {@code row}
+     * @param action The action to take immediately upon binding, if any.
      */
     @VisibleForTesting
     void initializeNotificationInfo(
             final ExpandableNotificationRow row,
-            NotificationInfo notificationInfoView) throws Exception {
+            NotificationInfo notificationInfoView,
+            @NotificationInfo.NotificationInfoAction int action) throws Exception {
         NotificationGuts guts = row.getGuts();
         StatusBarNotification sbn = row.getStatusBarNotification();
         String packageName = sbn.getPackageName();
@@ -297,7 +306,8 @@
                 isForBlockingHelper,
                 row.getEntry().userSentiment == USER_SENTIMENT_NEGATIVE,
                 row.getEntry().noisy,
-                row.getEntry().importance);
+                row.getEntry().importance,
+                action);
 
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java
index 38d6b35..e1c2f73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java
@@ -43,10 +43,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
-import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadFactory;
@@ -131,7 +128,6 @@
     private boolean mIsChildInGroup;
     private InflationCallback mCallback;
     private boolean mRedactAmbient;
-    private List<Notification.Action> mSmartActions;
     private final ArrayMap<Integer, RemoteViews> mCachedContentViews = new ArrayMap<>();
 
     public NotificationInflater(ExpandableNotificationRow row) {
@@ -161,10 +157,6 @@
         mUsesIncreasedHeight = usesIncreasedHeight;
     }
 
-    public void setSmartActions(List<Notification.Action> smartActions) {
-        mSmartActions = smartActions;
-    }
-
     public void setUsesIncreasedHeadsUpHeight(boolean usesIncreasedHeight) {
         mUsesIncreasedHeadsUpHeight = usesIncreasedHeight;
     }
@@ -258,8 +250,7 @@
         StatusBarNotification sbn = mRow.getEntry().notification;
         AsyncInflationTask task = new AsyncInflationTask(sbn, reInflateFlags, mCachedContentViews,
                 mRow, mIsLowPriority, mIsChildInGroup, mUsesIncreasedHeight,
-                mUsesIncreasedHeadsUpHeight, mRedactAmbient, mCallback, mRemoteViewClickHandler,
-                mSmartActions);
+                mUsesIncreasedHeadsUpHeight, mRedactAmbient, mCallback, mRemoteViewClickHandler);
         if (mCallback != null && mCallback.doInflateSynchronous()) {
             task.onPostExecute(task.doInBackground());
         } else {
@@ -765,15 +756,13 @@
         private Exception mError;
         private RemoteViews.OnClickHandler mRemoteViewClickHandler;
         private CancellationSignal mCancellationSignal;
-        private List<Notification.Action> mSmartActions;
 
         private AsyncInflationTask(StatusBarNotification notification,
                 @InflationFlag int reInflateFlags,
                 ArrayMap<Integer, RemoteViews> cachedContentViews, ExpandableNotificationRow row,
                 boolean isLowPriority, boolean isChildInGroup, boolean usesIncreasedHeight,
                 boolean usesIncreasedHeadsUpHeight, boolean redactAmbient,
-                InflationCallback callback, RemoteViews.OnClickHandler remoteViewClickHandler,
-                List<Notification.Action> smartActions) {
+                InflationCallback callback, RemoteViews.OnClickHandler remoteViewClickHandler) {
             mRow = row;
             mSbn = notification;
             mReInflateFlags = reInflateFlags;
@@ -786,9 +775,6 @@
             mRedactAmbient = redactAmbient;
             mRemoteViewClickHandler = remoteViewClickHandler;
             mCallback = callback;
-            mSmartActions = smartActions == null
-                    ? Collections.emptyList()
-                    : new ArrayList<>(smartActions);
             NotificationData.Entry entry = row.getEntry();
             entry.setInflationTask(this);
         }
@@ -806,8 +792,6 @@
                         = Notification.Builder.recoverBuilder(mContext,
                         mSbn.getNotification());
 
-                applyChanges(recoveredBuilder);
-
                 Context packageContext = mSbn.getPackageContext(mContext);
                 Notification notification = mSbn.getNotification();
                 if (notification.isMediaNotification()) {
@@ -834,18 +818,6 @@
             }
         }
 
-        /**
-         * Apply changes to the given notification builder, like adding smart actions suggested by
-         * a {@link android.service.notification.NotificationAssistantService}.
-         */
-        private void applyChanges(Notification.Builder builder) {
-            if (mSmartActions != null) {
-                for (Notification.Action smartAction : mSmartActions) {
-                    builder.addAction(smartAction);
-                }
-            }
-        }
-
         private void handleError(Exception e) {
             mRow.getEntry().onInflationTaskFinished();
             StatusBarNotification sbn = mRow.getStatusBarNotification();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index 522da4d..3a7091b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -71,16 +71,19 @@
 public class NotificationInfo extends LinearLayout implements NotificationGuts.GutsContent {
     private static final String TAG = "InfoGuts";
 
-    @IntDef(prefix = { "SWAP_CONTENT_" }, value = {
-            SWAP_CONTENT_UNDO,
-            SWAP_CONTENT_TOGGLE_SILENT,
-            SWAP_CONTENT_BLOCK,
+    @IntDef(prefix = { "ACTION_" }, value = {
+            ACTION_NONE,
+            ACTION_UNDO,
+            ACTION_TOGGLE_SILENT,
+            ACTION_BLOCK,
     })
-    @interface SwapContentAction {}
+    public @interface NotificationInfoAction {
+    }
 
-    private static final int SWAP_CONTENT_UNDO = 0;
-    private static final int SWAP_CONTENT_TOGGLE_SILENT = 1;
-    private static final int SWAP_CONTENT_BLOCK = 2;
+    public static final int ACTION_NONE = 0;
+    public static final int ACTION_UNDO = 1;
+    public static final int ACTION_TOGGLE_SILENT = 2;
+    public static final int ACTION_BLOCK = 3;
 
     private INotificationManager mINotificationManager;
     private PackageManager mPm;
@@ -123,8 +126,7 @@
 
     private OnClickListener mOnToggleSilent = v -> {
         Runnable saveImportance = () -> {
-            mExitReason = NotificationCounters.BLOCKING_HELPER_TOGGLE_SILENT;
-            swapContent(SWAP_CONTENT_TOGGLE_SILENT);
+            swapContent(ACTION_TOGGLE_SILENT, true /* animate */);
         };
         if (mCheckSaveListener != null) {
             mCheckSaveListener.checkSave(saveImportance, mSbn);
@@ -135,8 +137,7 @@
 
     private OnClickListener mOnStopOrMinimizeNotifications = v -> {
         Runnable saveImportance = () -> {
-            mExitReason = NotificationCounters.BLOCKING_HELPER_STOP_NOTIFICATIONS;
-            swapContent(SWAP_CONTENT_BLOCK);
+            swapContent(ACTION_BLOCK, true /* animate */);
         };
         if (mCheckSaveListener != null) {
             mCheckSaveListener.checkSave(saveImportance, mSbn);
@@ -149,7 +150,7 @@
         // Reset exit counter that we'll log and record an undo event separately (not an exit event)
         mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED;
         logBlockingHelperCounter(NotificationCounters.BLOCKING_HELPER_UNDO);
-        swapContent(SWAP_CONTENT_UNDO);
+        swapContent(ACTION_UNDO, true /* animate */);
     };
 
     public NotificationInfo(Context context, AttributeSet attrs) {
@@ -185,13 +186,14 @@
             boolean isDeviceProvisioned,
             boolean isNonblockable,
             boolean isNoisy,
-            int importance)
+            int importance,
+            @NotificationInfoAction int action)
             throws RemoteException {
         bindNotification(pm, iNotificationManager, pkg, notificationChannel,
                 numUniqueChannelsInRow, sbn, checkSaveListener, onSettingsClick,
                 onAppSettingsClick, isDeviceProvisioned, isNonblockable,
                 false /* isBlockingHelper */, false /* isUserSentimentNegative */, isNoisy,
-                importance);
+                importance, action);
     }
 
     public void bindNotification(
@@ -209,7 +211,8 @@
             boolean isForBlockingHelper,
             boolean isUserSentimentNegative,
             boolean isNoisy,
-            int importance)
+            int importance,
+            @NotificationInfoAction int action)
             throws RemoteException {
         mINotificationManager = iNotificationManager;
         mMetricsLogger = Dependency.get(MetricsLogger.class);
@@ -250,6 +253,10 @@
         bindHeader();
         bindPrompt();
         bindButtons();
+
+        if (action != ACTION_NONE) {
+            swapContent(action, false /* don't animate */);
+        }
     }
 
     private void bindHeader() throws RemoteException {
@@ -351,7 +358,8 @@
     }
 
     private void saveImportance() {
-        if (!mIsNonblockable) {
+        if (!mIsNonblockable
+                || mExitReason != NotificationCounters.BLOCKING_HELPER_STOP_NOTIFICATIONS) {
             updateImportance();
         }
     }
@@ -421,7 +429,7 @@
         }
     }
 
-    private void swapContent(@SwapContentAction int action) {
+    private void swapContent(@NotificationInfoAction int action, boolean animate) {
         if (mExpandAnimation != null) {
             mExpandAnimation.cancel();
         }
@@ -432,10 +440,11 @@
         View header = findViewById(R.id.header);
 
         switch (action) {
-            case SWAP_CONTENT_UNDO:
+            case ACTION_UNDO:
                 mChosenImportance = mStartingChannelImportance;
                 break;
-            case SWAP_CONTENT_TOGGLE_SILENT:
+            case ACTION_TOGGLE_SILENT:
+                mExitReason = NotificationCounters.BLOCKING_HELPER_TOGGLE_SILENT;
                 if (mStartingChannelOrNotificationImportance >= IMPORTANCE_DEFAULT) {
                     mChosenImportance = IMPORTANCE_LOW;
                     confirmationText.setText(R.string.notification_channel_silenced);
@@ -444,7 +453,8 @@
                     confirmationText.setText(R.string.notification_channel_unsilenced);
                 }
                 break;
-            case SWAP_CONTENT_BLOCK:
+            case ACTION_BLOCK:
+                mExitReason = NotificationCounters.BLOCKING_HELPER_STOP_NOTIFICATIONS;
                 if (mIsForeground) {
                     mChosenImportance = IMPORTANCE_MIN;
                     confirmationText.setText(R.string.notification_channel_minimized);
@@ -457,38 +467,41 @@
                 throw new IllegalArgumentException();
         }
 
-        boolean isUndo = action == SWAP_CONTENT_UNDO;
-        ObjectAnimator promptAnim = ObjectAnimator.ofFloat(prompt, View.ALPHA,
-                prompt.getAlpha(), isUndo ? 1f : 0f);
-        promptAnim.setInterpolator(isUndo ? Interpolators.ALPHA_IN : Interpolators.ALPHA_OUT);
-        ObjectAnimator confirmAnim = ObjectAnimator.ofFloat(confirmation, View.ALPHA,
-                confirmation.getAlpha(), isUndo ? 0f : 1f);
-        confirmAnim.setInterpolator(isUndo ? Interpolators.ALPHA_OUT : Interpolators.ALPHA_IN);
+        boolean isUndo = action == ACTION_UNDO;
 
         prompt.setVisibility(isUndo ? VISIBLE : GONE);
         confirmation.setVisibility(isUndo ? GONE : VISIBLE);
         header.setVisibility(isUndo ? VISIBLE : GONE);
 
-        mExpandAnimation = new AnimatorSet();
-        mExpandAnimation.playTogether(promptAnim, confirmAnim);
-        mExpandAnimation.setDuration(150);
-        mExpandAnimation.addListener(new AnimatorListenerAdapter() {
-            boolean cancelled = false;
+        if (animate) {
+            ObjectAnimator promptAnim = ObjectAnimator.ofFloat(prompt, View.ALPHA,
+                    prompt.getAlpha(), isUndo ? 1f : 0f);
+            promptAnim.setInterpolator(isUndo ? Interpolators.ALPHA_IN : Interpolators.ALPHA_OUT);
+            ObjectAnimator confirmAnim = ObjectAnimator.ofFloat(confirmation, View.ALPHA,
+                    confirmation.getAlpha(), isUndo ? 0f : 1f);
+            confirmAnim.setInterpolator(isUndo ? Interpolators.ALPHA_OUT : Interpolators.ALPHA_IN);
 
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                cancelled = true;
-            }
+            mExpandAnimation = new AnimatorSet();
+            mExpandAnimation.playTogether(promptAnim, confirmAnim);
+            mExpandAnimation.setDuration(150);
+            mExpandAnimation.addListener(new AnimatorListenerAdapter() {
+                boolean mCancelled = false;
 
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (!cancelled) {
-                    prompt.setVisibility(isUndo ? VISIBLE : GONE);
-                    confirmation.setVisibility(isUndo ? GONE : VISIBLE);
+                @Override
+                public void onAnimationCancel(Animator animation) {
+                    mCancelled = true;
                 }
-            }
-        });
-        mExpandAnimation.start();
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    if (!mCancelled) {
+                        prompt.setVisibility(isUndo ? VISIBLE : GONE);
+                        confirmation.setVisibility(isUndo ? GONE : VISIBLE);
+                    }
+                }
+            });
+            mExpandAnimation.start();
+        }
 
         // Since we're swapping/update the content, reset the timeout so the UI can't close
         // immediately after the update.
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 674c8ee..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,12 +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;
@@ -40,7 +44,9 @@
 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;
 
 import java.util.ArrayList;
@@ -67,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;
@@ -170,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
@@ -214,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
@@ -229,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) {
@@ -595,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);
@@ -605,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 NotificationMenuItem(context, infoDescription, infoContent,
-                R.drawable.ic_settings);
-        return info;
+        return new NotificationInfoMenuItem(context, infoDescription, infoContent,
+                R.drawable.ic_settings, ACTION_NONE);
     }
 
-    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,
@@ -623,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) {
@@ -704,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);
@@ -717,7 +766,7 @@
                 iv.setAlpha(1f);
                 mMenuView = iv;
             }
-            mContentDescription = s;
+            mContentDescription = contentDescription;
             mGutsContent = content;
         }
 
@@ -737,4 +786,23 @@
             return mContentDescription;
         }
     }
+
+    /** A {@link NotificationMenuItem} with an associated {@link NotificationInfoAction}. */
+    public static class NotificationInfoMenuItem extends NotificationMenuItem {
+
+        @NotificationInfoAction
+        int mAction;
+
+        public NotificationInfoMenuItem(Context context, String contentDescription,
+                NotificationInfo content, int iconResId,
+                @NotificationInfoAction int action) {
+            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/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index d5056b7..1d7e899 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -15,6 +15,7 @@
 package com.android.systemui.statusbar.phone;
 
 import android.content.Context;
+import android.hardware.display.ColorDisplayManager;
 import android.os.Handler;
 import android.provider.Settings.Secure;
 
@@ -81,7 +82,7 @@
             Dependency.get(ManagedProfileController.class).addCallback(mProfileCallback);
         }
         if (!mAutoTracker.isAdded(NIGHT)
-            && ColorDisplayController.isAvailable(mContext)) {
+                && ColorDisplayManager.isNightDisplayAvailable(mContext)) {
             Dependency.get(ColorDisplayController.class).setListener(mColorDisplayCallback);
         }
     }
@@ -94,7 +95,9 @@
         Dependency.get(HotspotController.class).removeCallback(mHotspotCallback);
         Dependency.get(DataSaverController.class).removeCallback(mDataSaverListener);
         Dependency.get(ManagedProfileController.class).removeCallback(mProfileCallback);
-        Dependency.get(ColorDisplayController.class).setListener(null);
+        if (ColorDisplayManager.isNightDisplayAvailable(mContext)) {
+            Dependency.get(ColorDisplayController.class).setListener(null);
+        }
     }
 
     public void unmarkTileAsAutoAdded(String tabSpec) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 5439497..21c506b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -54,6 +54,7 @@
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.MathUtils;
 import android.util.TypedValue;
 import android.view.View;
 import android.view.ViewGroup;
@@ -568,6 +569,7 @@
         mDarkAmount = darkAmount;
         mIndicationController.setDarkAmount(darkAmount);
         mLockIcon.setDarkAmount(darkAmount);
+        dozeTimeTick();
     }
 
     private static boolean isSuccessfulLaunch(int result) {
@@ -840,12 +842,10 @@
     }
 
     public void dozeTimeTick() {
-        if (mDarkAmount == 1) {
-            // Move views every minute to avoid burn-in
-            int burnInYOffset = getBurnInOffset(mBurnInYOffset * 2, false /* xAxis */)
-                    - mBurnInYOffset;
-            mLockIcon.setTranslationY(burnInYOffset);
-        }
+        // Move views every minute to avoid burn-in
+        int burnInYOffset = -getBurnInOffset(mBurnInYOffset, false /* xAxis */);
+        burnInYOffset = (int) MathUtils.lerp(0, burnInYOffset, mDarkAmount);
+        mLockIcon.setTranslationY(burnInYOffset);
     }
 
     public void setBurnInXOffset(int burnInXOffset) {
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/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 0e6efc8..c84f3db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -66,6 +66,8 @@
 import com.android.systemui.R;
 import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.UiOffloadThread;
+import com.android.systemui.privacy.PrivacyItem;
+import com.android.systemui.privacy.PrivacyItemController;
 import com.android.systemui.qs.tiles.DndTile;
 import com.android.systemui.qs.tiles.RotationLockTile;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -101,7 +103,8 @@
  */
 public class PhoneStatusBarPolicy implements Callback, Callbacks,
         RotationLockControllerCallback, Listener, LocationChangeCallback,
-        ZenModeController.Callback, DeviceProvisionedListener, KeyguardMonitor.Callback {
+        ZenModeController.Callback, DeviceProvisionedListener, KeyguardMonitor.Callback,
+        PrivacyItemController.Callback {
     private static final String TAG = "PhoneStatusBarPolicy";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
@@ -120,6 +123,8 @@
     private final String mSlotHeadset;
     private final String mSlotDataSaver;
     private final String mSlotLocation;
+    private final String mSlotMicrophone;
+    private final String mSlotCamera;
 
     private final Context mContext;
     private final Handler mHandler = new Handler();
@@ -136,6 +141,7 @@
     private final DeviceProvisionedController mProvisionedController;
     private final KeyguardMonitor mKeyguardMonitor;
     private final LocationController mLocationController;
+    private final PrivacyItemController mPrivacyItemController;
     private final ArraySet<Pair<String, Integer>> mCurrentNotifs = new ArraySet<>();
     private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
 
@@ -169,6 +175,7 @@
         mProvisionedController = Dependency.get(DeviceProvisionedController.class);
         mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
         mLocationController = Dependency.get(LocationController.class);
+        mPrivacyItemController = new PrivacyItemController(mContext, this);
 
         mSlotCast = context.getString(com.android.internal.R.string.status_bar_cast);
         mSlotHotspot = context.getString(com.android.internal.R.string.status_bar_hotspot);
@@ -183,6 +190,8 @@
         mSlotHeadset = context.getString(com.android.internal.R.string.status_bar_headset);
         mSlotDataSaver = context.getString(com.android.internal.R.string.status_bar_data_saver);
         mSlotLocation = context.getString(com.android.internal.R.string.status_bar_location);
+        mSlotMicrophone = context.getString(com.android.internal.R.string.status_bar_microphone);
+        mSlotCamera = context.getString(com.android.internal.R.string.status_bar_camera);
 
         // listen for broadcasts
         IntentFilter filter = new IntentFilter();
@@ -241,6 +250,12 @@
                 context.getString(R.string.accessibility_data_saver_on));
         mIconController.setIconVisibility(mSlotDataSaver, false);
 
+        // privacy items
+        mIconController.setIcon(mSlotMicrophone, R.drawable.stat_sys_mic_none, null);
+        mIconController.setIconVisibility(mSlotMicrophone, false);
+        mIconController.setIcon(mSlotCamera, R.drawable.stat_sys_camera, null);
+        mIconController.setIconVisibility(mSlotCamera, false);
+
         mRotationLockController.addCallback(this);
         mBluetooth.addCallback(this);
         mProvisionedController.addCallback(this);
@@ -251,6 +266,7 @@
         mDataSaver.addCallback(this);
         mKeyguardMonitor.addCallback(this);
         mLocationController.addCallback(this);
+        mPrivacyItemController.setListening(true);
 
         SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallbacks(this);
         ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskListener);
@@ -279,6 +295,7 @@
         mDataSaver.removeCallback(this);
         mKeyguardMonitor.removeCallback(this);
         mLocationController.removeCallback(this);
+        mPrivacyItemController.setListening(false);
         SysUiServiceProvider.getComponent(mContext, CommandQueue.class).removeCallbacks(this);
         mContext.unregisterReceiver(mIntentReceiver);
 
@@ -798,6 +815,34 @@
         mIconController.setIconVisibility(mSlotDataSaver, isDataSaving);
     }
 
+    @Override  // PrivacyItemController.Callback
+    public void privacyChanged(List<PrivacyItem> privacyItems) {
+        updatePrivacyItems(privacyItems);
+    }
+
+    private void updatePrivacyItems(List<PrivacyItem> items) {
+        boolean showCamera = false;
+        boolean showMicrophone = false;
+        boolean showLocation = false;
+        for (PrivacyItem item : items) {
+            switch (item.getPrivacyType()) {
+                case TYPE_CAMERA:
+                    showCamera = true;
+                    break;
+                case TYPE_LOCATION:
+                    showLocation = true;
+                    break;
+                case TYPE_MICROPHONE:
+                    showMicrophone = true;
+                    break;
+            }
+        }
+
+        mIconController.setIconVisibility(mSlotCamera, showCamera);
+        mIconController.setIconVisibility(mSlotMicrophone, showMicrophone);
+        mIconController.setIconVisibility(mSlotLocation, showLocation);
+    }
+
     private final TaskStackChangeListener mTaskListener = new TaskStackChangeListener() {
         @Override
         public void onTaskStackChanged() {
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/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java
index d85e18c..67da8a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java
@@ -14,14 +14,11 @@
 
 package com.android.systemui.statusbar.policy;
 
-import android.accessibilityservice.AccessibilityServiceInfo;
 import android.content.Context;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
 
-import java.util.List;
-
 /**
  * For mocking because AccessibilityManager is final for some reason...
  */
@@ -62,8 +59,8 @@
         mAccessibilityManager.sendAccessibilityEvent(event);
     }
 
-    public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
-            int feedbackTypeFlags) {
-        return mAccessibilityManager.getEnabledAccessibilityServiceList(feedbackTypeFlags);
+    /** Returns a recommended ui timeout value in milliseconds. */
+    public int getRecommendedTimeoutMillis(int originalTimeout, int uiContentFlags) {
+        return mAccessibilityManager.getRecommendedTimeoutMillis(originalTimeout, uiContentFlags);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 798f8bc..b588305 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.volume;
 
-import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
-import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_GENERIC;
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
 import static android.media.AudioManager.RINGER_MODE_NORMAL;
 import static android.media.AudioManager.RINGER_MODE_SILENT;
@@ -32,7 +30,6 @@
 
 import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED;
 
-import android.accessibilityservice.AccessibilityServiceInfo;
 import android.animation.ObjectAnimator;
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
@@ -68,13 +65,12 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.AccessibilityDelegate;
-import android.view.View.OnAttachStateChangeListener;
 import android.view.ViewGroup;
 import android.view.ViewPropertyAnimator;
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
+import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.DecelerateInterpolator;
 import android.widget.FrameLayout;
@@ -113,6 +109,10 @@
     private static final long USER_ATTEMPT_GRACE_PERIOD = 1000;
     private static final int UPDATE_ANIMATION_DURATION = 80;
 
+    static final int DIALOG_TIMEOUT_MILLIS = 3000;
+    static final int DIALOG_SAFETYWARNING_TIMEOUT_MILLIS = 5000;
+    static final int DIALOG_HOVERING_TIMEOUT_MILLIS = 16000;
+
     private final Context mContext;
     private final H mHandler = new H();
     private final VolumeDialogController mController;
@@ -170,7 +170,6 @@
 
     @Override
     public void destroy() {
-        mAccessibility.destroy();
         mController.removeCallback(mControllerCallbackH);
         mHandler.removeCallbacksAndMessages(null);
     }
@@ -356,8 +355,6 @@
         writer.print("  mDynamic: "); writer.println(mDynamic);
         writer.print("  mAutomute: "); writer.println(mAutomute);
         writer.print("  mSilentMode: "); writer.println(mSilentMode);
-        writer.print("  mAccessibility.mFeedbackEnabled: ");
-        writer.println(mAccessibility.mFeedbackEnabled);
     }
 
     private static int getImpliedLevel(SeekBar seekBar, int progress) {
@@ -571,10 +568,18 @@
     }
 
     private int computeTimeoutH() {
-        if (mAccessibility.mFeedbackEnabled) return 20000;
-        if (mHovering) return 16000;
-        if (mSafetyWarning != null) return 5000;
-        return 3000;
+        if (mHovering) {
+            return mAccessibilityMgr.getRecommendedTimeoutMillis(DIALOG_HOVERING_TIMEOUT_MILLIS,
+                    AccessibilityManager.FLAG_CONTENT_CONTROLS);
+        }
+        if (mSafetyWarning != null) {
+            return mAccessibilityMgr.getRecommendedTimeoutMillis(
+                    DIALOG_SAFETYWARNING_TIMEOUT_MILLIS,
+                    AccessibilityManager.FLAG_CONTENT_TEXT
+                            | AccessibilityManager.FLAG_CONTENT_CONTROLS);
+        }
+        return mAccessibilityMgr.getRecommendedTimeoutMillis(DIALOG_TIMEOUT_MILLIS,
+                AccessibilityManager.FLAG_CONTENT_CONTROLS);
     }
 
     protected void dismissH(int reason) {
@@ -1261,28 +1266,8 @@
     }
 
     private final class Accessibility extends AccessibilityDelegate {
-        private boolean mFeedbackEnabled;
-
         public void init() {
-            mDialogView.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
-                @Override
-                public void onViewDetachedFromWindow(View v) {
-                    if (D.BUG) Log.d(TAG, "onViewDetachedFromWindow");
-                }
-
-                @Override
-                public void onViewAttachedToWindow(View v) {
-                    if (D.BUG) Log.d(TAG, "onViewAttachedToWindow");
-                    updateFeedbackEnabled();
-                }
-            });
             mDialogView.setAccessibilityDelegate(this);
-            mAccessibilityMgr.addCallback(mListener);
-            updateFeedbackEnabled();
-        }
-
-        public void destroy() {
-            mAccessibilityMgr.removeCallback(mListener);
         }
 
         @Override
@@ -1298,25 +1283,6 @@
             rescheduleTimeoutH();
             return super.onRequestSendAccessibilityEvent(host, child, event);
         }
-
-        private void updateFeedbackEnabled() {
-            mFeedbackEnabled = computeFeedbackEnabled();
-        }
-
-        private boolean computeFeedbackEnabled() {
-            // are there any enabled non-generic a11y services?
-            final List<AccessibilityServiceInfo> services =
-                    mAccessibilityMgr.getEnabledAccessibilityServiceList(FEEDBACK_ALL_MASK);
-            for (AccessibilityServiceInfo asi : services) {
-                if (asi.feedbackType != 0 && asi.feedbackType != FEEDBACK_GENERIC) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        private final AccessibilityServicesStateChangeListener mListener =
-                enabled -> updateFeedbackEnabled();
     }
 
     private static class VolumeRow {
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/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java
index 8e6bfe3..f59bfae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java
@@ -72,6 +72,8 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -390,10 +392,16 @@
     @Test
     public void testCreateNotificationDataEntry_RankingUpdate() {
         Ranking ranking = mock(Ranking.class);
+        initStatusBarNotification(false);
 
-        ArrayList<Notification.Action> smartActions = new ArrayList<>();
-        smartActions.add(createAction());
-        when(ranking.getSmartActions()).thenReturn(smartActions);
+        List<Notification.Action> appGeneratedSmartActions =
+                Collections.singletonList(createContextualAction("appGeneratedAction"));
+        mMockStatusBarNotification.getNotification().actions =
+                appGeneratedSmartActions.toArray(new Notification.Action[0]);
+
+        List<Notification.Action> systemGeneratedSmartActions =
+                Collections.singletonList(createAction("systemGeneratedAction"));
+        when(ranking.getSmartActions()).thenReturn(systemGeneratedSmartActions);
 
         when(ranking.getChannel()).thenReturn(NOTIFICATION_CHANNEL);
 
@@ -407,7 +415,7 @@
         NotificationData.Entry entry =
                 new NotificationData.Entry(mMockStatusBarNotification, ranking);
 
-        assertEquals(smartActions, entry.smartActions);
+        assertEquals(systemGeneratedSmartActions, entry.systemGeneratedSmartActions);
         assertEquals(NOTIFICATION_CHANNEL, entry.channel);
         assertEquals(Ranking.USER_SENTIMENT_NEGATIVE, entry.userSentiment);
         assertEquals(snoozeCriterions, entry.snoozeCriteria);
@@ -459,10 +467,20 @@
         }
     }
 
-    private Notification.Action createAction() {
+    private Notification.Action createContextualAction(String title) {
         return new Notification.Action.Builder(
                 Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
-                "action",
+                title,
+                PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"), 0))
+                        .setSemanticAction(
+                                Notification.Action.SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION)
+                        .build();
+    }
+
+    private Notification.Action createAction(String title) {
+        return new Notification.Action.Builder(
+                Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
+                title,
                 PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"), 0)).build();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index 9f8a5cc..d1fe5af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -438,8 +438,8 @@
 
         mEntryManager.updateNotificationRanking(mRankingMap);
         verify(mRow).setEntry(eq(mEntry));
-        assertEquals(1, mEntry.smartActions.size());
-        assertEquals("action", mEntry.smartActions.get(0).title);
+        assertEquals(1, mEntry.systemGeneratedSmartActions.size());
+        assertEquals("action", mEntry.systemGeneratedSmartActions.get(0).title);
     }
 
     @Test
@@ -453,7 +453,7 @@
 
         mEntryManager.updateNotificationRanking(mRankingMap);
         verify(mRow, never()).setEntry(eq(mEntry));
-        assertEquals(0, mEntry.smartActions.size());
+        assertEquals(0, mEntry.systemGeneratedSmartActions.size());
     }
 
     @Test
@@ -467,8 +467,8 @@
 
         mEntryManager.updateNotificationRanking(mRankingMap);
         verify(mRow, never()).setEntry(eq(mEntry));
-        assertEquals(1, mEntry.smartActions.size());
-        assertEquals("action", mEntry.smartActions.get(0).title);
+        assertEquals(1, mEntry.systemGeneratedSmartActions.size());
+        assertEquals("action", mEntry.systemGeneratedSmartActions.get(0).title);
     }
 
     @Test
@@ -482,8 +482,8 @@
 
         mEntryManager.updateNotificationRanking(mRankingMap);
         verify(mRow, never()).setEntry(eq(mEntry));
-        assertEquals(1, mEntry.smartActions.size());
-        assertEquals("action", mEntry.smartActions.get(0).title);
+        assertEquals(1, mEntry.systemGeneratedSmartActions.size());
+        assertEquals("action", mEntry.systemGeneratedSmartActions.get(0).title);
     }
 
     private Notification.Action createAction() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 626726d..3d2ea70 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -284,7 +284,8 @@
         when(row.getIsNonblockable()).thenReturn(false);
         StatusBarNotification statusBarNotification = row.getStatusBarNotification();
 
-        mGutsManager.initializeNotificationInfo(row, notificationInfoView);
+        mGutsManager.initializeNotificationInfo(row, notificationInfoView,
+                NotificationInfo.ACTION_NONE);
 
         verify(notificationInfoView).bindNotification(
                 any(PackageManager.class),
@@ -301,7 +302,8 @@
                 eq(true) /* isForBlockingHelper */,
                 eq(true) /* isUserSentimentNegative */,
                 eq(false) /*isNoisy */,
-                eq(0));
+                eq(0),
+                eq(NotificationInfo.ACTION_NONE));
     }
 
     @Test
@@ -313,7 +315,8 @@
         when(row.getIsNonblockable()).thenReturn(false);
         StatusBarNotification statusBarNotification = row.getStatusBarNotification();
 
-        mGutsManager.initializeNotificationInfo(row, notificationInfoView);
+        mGutsManager.initializeNotificationInfo(row, notificationInfoView,
+                NotificationInfo.ACTION_NONE);
 
         verify(notificationInfoView).bindNotification(
                 any(PackageManager.class),
@@ -330,7 +333,8 @@
                 eq(false) /* isForBlockingHelper */,
                 eq(true) /* isUserSentimentNegative */,
                 eq(false) /*isNoisy */,
-                eq(0));
+                eq(0),
+                eq(NotificationInfo.ACTION_NONE));
     }
 
     @Test
@@ -343,7 +347,8 @@
         when(row.getIsNonblockable()).thenReturn(false);
         StatusBarNotification statusBarNotification = row.getStatusBarNotification();
 
-        mGutsManager.initializeNotificationInfo(row, notificationInfoView);
+        mGutsManager.initializeNotificationInfo(row, notificationInfoView,
+                NotificationInfo.ACTION_NONE);
 
         verify(notificationInfoView).bindNotification(
                 any(PackageManager.class),
@@ -360,7 +365,8 @@
                 eq(true) /* isForBlockingHelper */,
                 eq(true) /* isUserSentimentNegative */,
                 eq(true) /*isNoisy */,
-                eq(0));
+                eq(0),
+                eq(NotificationInfo.ACTION_NONE));
     }
 
     @Test
@@ -373,7 +379,8 @@
         when(row.getIsNonblockable()).thenReturn(false);
         StatusBarNotification statusBarNotification = row.getStatusBarNotification();
 
-        mGutsManager.initializeNotificationInfo(row, notificationInfoView);
+        mGutsManager.initializeNotificationInfo(row, notificationInfoView,
+                NotificationInfo.ACTION_NONE);
 
         verify(notificationInfoView).bindNotification(
                 any(PackageManager.class),
@@ -390,7 +397,8 @@
                 eq(true) /* isForBlockingHelper */,
                 eq(true) /* isUserSentimentNegative */,
                 eq(false) /*isNoisy */,
-                eq(IMPORTANCE_DEFAULT));
+                eq(IMPORTANCE_DEFAULT),
+                eq(NotificationInfo.ACTION_NONE));
     }
 
     @Test
@@ -403,7 +411,8 @@
         StatusBarNotification statusBarNotification = row.getStatusBarNotification();
         when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
 
-        mGutsManager.initializeNotificationInfo(row, notificationInfoView);
+        mGutsManager.initializeNotificationInfo(row, notificationInfoView,
+                NotificationInfo.ACTION_NONE);
 
         verify(notificationInfoView).bindNotification(
                 any(PackageManager.class),
@@ -420,7 +429,39 @@
                 eq(false) /* isForBlockingHelper */,
                 eq(true) /* isUserSentimentNegative */,
                 eq(false) /*isNoisy */,
-                eq(0));
+                eq(0),
+                eq(NotificationInfo.ACTION_NONE));
+    }
+
+    @Test
+    public void testInitializeNotificationInfoView_withInitialAction() throws Exception {
+        NotificationInfo notificationInfoView = mock(NotificationInfo.class);
+        ExpandableNotificationRow row = spy(mHelper.createRow());
+        row.setBlockingHelperShowing(true);
+        row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
+        when(row.getIsNonblockable()).thenReturn(false);
+        StatusBarNotification statusBarNotification = row.getStatusBarNotification();
+
+        mGutsManager.initializeNotificationInfo(row, notificationInfoView,
+                NotificationInfo.ACTION_BLOCK);
+
+        verify(notificationInfoView).bindNotification(
+                any(PackageManager.class),
+                any(INotificationManager.class),
+                eq(statusBarNotification.getPackageName()),
+                any(NotificationChannel.class),
+                anyInt(),
+                eq(statusBarNotification),
+                any(NotificationInfo.CheckSaveListener.class),
+                any(NotificationInfo.OnSettingsClickListener.class),
+                any(NotificationInfo.OnAppSettingsClickListener.class),
+                eq(false),
+                eq(false),
+                eq(true) /* isForBlockingHelper */,
+                eq(true) /* isUserSentimentNegative */,
+                eq(false) /*isNoisy */,
+                eq(0),
+                eq(NotificationInfo.ACTION_BLOCK));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index 3744196..1cc1c63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -187,7 +187,7 @@
         when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         final TextView textView = mNotificationInfo.findViewById(R.id.pkgname);
         assertTrue(textView.getText().toString().contains("App Name"));
         assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
@@ -200,7 +200,7 @@
                 .thenReturn(iconDrawable);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         final ImageView iconView = mNotificationInfo.findViewById(R.id.pkgicon);
         assertEquals(iconDrawable, iconView.getDrawable());
     }
@@ -209,7 +209,7 @@
     public void testBindNotification_GroupNameHiddenIfNoGroup() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name);
         assertEquals(GONE, groupNameView.getVisibility());
         final TextView groupDividerView = mNotificationInfo.findViewById(R.id.pkg_group_divider);
@@ -226,7 +226,7 @@
                 .thenReturn(notificationChannelGroup);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name);
         assertEquals(View.VISIBLE, groupNameView.getVisibility());
         assertEquals("Test Group Name", groupNameView.getText());
@@ -238,7 +238,7 @@
     public void testBindNotification_SetsTextChannelName() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
         assertEquals(TEST_CHANNEL_NAME, textView.getText());
     }
@@ -247,7 +247,7 @@
     public void testBindNotification_DefaultChannelDoesNotUseChannelName() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mDefaultNotificationChannel, 1, mSbn, null, null, null, true,
-                false, false, IMPORTANCE_DEFAULT);
+                false, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
         assertEquals(GONE, textView.getVisibility());
     }
@@ -260,7 +260,7 @@
                 eq(TEST_PACKAGE_NAME), eq(TEST_UID), anyBoolean())).thenReturn(10);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mDefaultNotificationChannel, 1, mSbn, null, null, null, true,
-                false, false, IMPORTANCE_DEFAULT);
+                false, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
         assertEquals(VISIBLE, textView.getVisibility());
     }
@@ -269,7 +269,7 @@
     public void testBindNotification_UnblockablePackageUsesChannelName() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
         assertEquals(VISIBLE, textView.getVisibility());
     }
@@ -278,7 +278,7 @@
     public void testBindNotification_BlockButton() throws Exception {
        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-               false, IMPORTANCE_DEFAULT);
+               false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         final View block = mNotificationInfo.findViewById(R.id.block);
         final View toggleSilent = mNotificationInfo.findViewById(R.id.toggle_silent);
         final View minimize = mNotificationInfo.findViewById(R.id.minimize);
@@ -292,7 +292,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                true, IMPORTANCE_DEFAULT);
+                true, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         final TextView toggleSilent = mNotificationInfo.findViewById(R.id.toggle_silent);
         assertEquals(VISIBLE, toggleSilent.getVisibility());
         assertEquals(
@@ -304,7 +304,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                true, IMPORTANCE_LOW);
+                true, IMPORTANCE_LOW, NotificationInfo.ACTION_NONE);
         final TextView toggleSilent = mNotificationInfo.findViewById(R.id.toggle_silent);
         assertEquals(VISIBLE, toggleSilent.getVisibility());
         assertEquals(
@@ -316,7 +316,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                true, IMPORTANCE_DEFAULT);
+                true, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         final TextView toggleSilent = mNotificationInfo.findViewById(R.id.toggle_silent);
         assertEquals(VISIBLE, toggleSilent.getVisibility());
         assertEquals(
@@ -329,7 +329,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                true, IMPORTANCE_LOW);
+                true, IMPORTANCE_LOW, NotificationInfo.ACTION_NONE);
         final TextView toggleSilent = mNotificationInfo.findViewById(R.id.toggle_silent);
         assertEquals(VISIBLE, toggleSilent.getVisibility());
         assertEquals(
@@ -341,7 +341,7 @@
         mSbn.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         final View block = mNotificationInfo.findViewById(R.id.block);
         final View minimize = mNotificationInfo.findViewById(R.id.minimize);
         assertEquals(GONE, block.getVisibility());
@@ -356,7 +356,7 @@
                 (View v, NotificationChannel c, int appUid) -> {
                     assertEquals(mNotificationChannel, c);
                     latch.countDown();
-                }, null, true, false, false, IMPORTANCE_DEFAULT);
+                }, null, true, false, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
 
         final View settingsButton = mNotificationInfo.findViewById(R.id.info);
         settingsButton.performClick();
@@ -368,7 +368,7 @@
     public void testBindNotification_SettingsButtonInvisibleWhenNoClickListener() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         final View settingsButton = mNotificationInfo.findViewById(R.id.info);
         assertTrue(settingsButton.getVisibility() != View.VISIBLE);
     }
@@ -380,7 +380,7 @@
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null,
                 (View v, NotificationChannel c, int appUid) -> {
                     assertEquals(mNotificationChannel, c);
-                }, null, false, false, false, IMPORTANCE_DEFAULT);
+                }, null, false, false, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         final View settingsButton = mNotificationInfo.findViewById(R.id.info);
         assertTrue(settingsButton.getVisibility() != View.VISIBLE);
     }
@@ -389,11 +389,11 @@
     public void testBindNotification_SettingsButtonReappearsAfterSecondBind() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null,
                 (View v, NotificationChannel c, int appUid) -> {
-                }, null, true, false, false, IMPORTANCE_DEFAULT);
+                }, null, true, false, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         final View settingsButton = mNotificationInfo.findViewById(R.id.info);
         assertEquals(View.VISIBLE, settingsButton.getVisibility());
     }
@@ -402,7 +402,7 @@
     public void testLogBlockingHelperCounter_doesntLogForNormalGutsView() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         mNotificationInfo.logBlockingHelperCounter("HowCanNotifsBeRealIfAppsArent");
         verify(mMetricsLogger, times(0)).count(anyString(), anyInt());
     }
@@ -411,7 +411,7 @@
     public void testLogBlockingHelperCounter_logsForBlockingHelper() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false, true,
-                true, true, false, IMPORTANCE_DEFAULT);
+                true, true, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         mNotificationInfo.logBlockingHelperCounter("HowCanNotifsBeRealIfAppsArent");
         verify(mMetricsLogger, times(1)).count(anyString(), anyInt());
     }
@@ -424,7 +424,7 @@
                 (View v, NotificationChannel c, int appUid) -> {
                     assertEquals(null, c);
                     latch.countDown();
-                }, null, true, true, false, IMPORTANCE_DEFAULT);
+                }, null, true, true, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.findViewById(R.id.info).performClick();
         // Verify that listener was triggered.
@@ -437,7 +437,7 @@
             throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, MULTIPLE_CHANNEL_COUNT, mSbn, null, null,
-                null, true, true, false, IMPORTANCE_DEFAULT);
+                null, true, true, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         final TextView channelNameView =
                 mNotificationInfo.findViewById(R.id.channel_name);
         assertEquals(GONE, channelNameView.getVisibility());
@@ -448,7 +448,7 @@
     public void testStopInvisibleIfBundleFromDifferentChannels() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, MULTIPLE_CHANNEL_COUNT, mSbn, null, null,
-                null, true, true, false, IMPORTANCE_DEFAULT);
+                null, true, true, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         final TextView blockView = mNotificationInfo.findViewById(R.id.block);
         assertEquals(GONE, blockView.getVisibility());
     }
@@ -457,7 +457,7 @@
     public void testbindNotification_BlockingHelper() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false, false,
-                true, true, false, IMPORTANCE_DEFAULT);
+                true, true, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         final TextView view = mNotificationInfo.findViewById(R.id.block_prompt);
         assertEquals(View.VISIBLE, view.getVisibility());
         assertEquals(mContext.getString(R.string.inline_blocking_helper), view.getText());
@@ -467,7 +467,7 @@
     public void testbindNotification_UnblockableTextVisibleWhenAppUnblockable() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         final TextView view = mNotificationInfo.findViewById(R.id.block_prompt);
         assertEquals(View.VISIBLE, view.getVisibility());
         assertEquals(mContext.getString(R.string.notification_unblockable_desc),
@@ -478,7 +478,7 @@
     public void testBindNotification_DoesNotUpdateNotificationChannel() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         mTestableLooper.processAllMessages();
         verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
                 anyString(), eq(TEST_UID), any());
@@ -489,7 +489,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.findViewById(R.id.block).performClick();
         mTestableLooper.processAllMessages();
@@ -503,7 +503,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.findViewById(R.id.minimize).performClick();
         mTestableLooper.processAllMessages();
@@ -517,7 +517,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                true, IMPORTANCE_DEFAULT);
+                true, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.findViewById(R.id.toggle_silent).performClick();
         mTestableLooper.processAllMessages();
@@ -531,7 +531,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                true, IMPORTANCE_DEFAULT);
+                true, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.findViewById(R.id.toggle_silent).performClick();
         mTestableLooper.processAllMessages();
@@ -545,7 +545,7 @@
         int originalImportance = mNotificationChannel.getImportance();
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.handleCloseControls(true, false);
         mTestableLooper.processAllMessages();
@@ -560,7 +560,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.handleCloseControls(true, false);
 
@@ -578,7 +578,8 @@
                 TEST_PACKAGE_NAME, mNotificationChannel /* notificationChannel */,
                 10 /* numUniqueChannelsInRow */, mSbn, null /* checkSaveListener */,
                 null /* onSettingsClick */, null /* onAppSettingsClick */ ,
-                true, false /* isNonblockable */, false, /* isNoisy */IMPORTANCE_DEFAULT);
+                true, false /* isNonblockable */, false /* isNoisy */, IMPORTANCE_DEFAULT,
+                NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.findViewById(R.id.block).performClick();
         waitForUndoButton();
@@ -598,8 +599,9 @@
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel /* notificationChannel */,
                 10 /* numUniqueChannelsInRow */, mSbn, null /* checkSaveListener */,
-                null /* onSettingsClick */, null /* onAppSettingsClick */ ,
-                true, false /* isNonblockable */, false, /* isNoisy */IMPORTANCE_DEFAULT);
+                null /* onSettingsClick */, null /* onAppSettingsClick */,
+                true, false /* isNonblockable */, false /* isNoisy */, IMPORTANCE_DEFAULT,
+                NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.findViewById(R.id.block).performClick();
         waitForUndoButton();
@@ -620,7 +622,8 @@
                 null /* onSettingsClick */, null /* onAppSettingsClick */ ,
                 true /* provisioned */,
                 false /* isNonblockable */, true /* isForBlockingHelper */,
-                true /* isUserSentimentNegative */, false, /* isNoisy */IMPORTANCE_DEFAULT);
+                true /* isUserSentimentNegative */, false /* isNoisy */, IMPORTANCE_DEFAULT,
+                NotificationInfo.ACTION_NONE);
 
         NotificationGuts guts = spy(new NotificationGuts(mContext, null));
         when(guts.getWindowToken()).thenReturn(mock(IBinder.class));
@@ -648,7 +651,8 @@
                 10 /* numUniqueChannelsInRow */, mSbn, listener /* checkSaveListener */,
                 null /* onSettingsClick */, null /* onAppSettingsClick */ , true /* provisioned */,
                 false /* isNonblockable */, true /* isForBlockingHelper */,
-                true /* isUserSentimentNegative */, false, /* isNoisy */IMPORTANCE_DEFAULT);
+                true /* isUserSentimentNegative */, false /* isNoisy */, IMPORTANCE_DEFAULT,
+                NotificationInfo.ACTION_NONE);
 
         NotificationGuts guts = spy(new NotificationGuts(mContext, null));
         when(guts.getWindowToken()).thenReturn(mock(IBinder.class));
@@ -676,8 +680,8 @@
                 10 /* numUniqueChannelsInRow */, mSbn, listener /* checkSaveListener */,
                 null /* onSettingsClick */, null /* onAppSettingsClick */ ,
                 false /* isNonblockable */, true /* isForBlockingHelper */,
-                true, true /* isUserSentimentNegative */, false, /* isNoisy */
-                IMPORTANCE_DEFAULT);
+                true, true /* isUserSentimentNegative */, false /* isNoisy */,
+                IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.handleCloseControls(true /* save */, false /* force */);
 
@@ -696,7 +700,8 @@
                 null /* onSettingsClick */, null /* onAppSettingsClick */,
                 true /* provisioned */,
                 false /* isNonblockable */, true /* isForBlockingHelper */,
-                true /* isUserSentimentNegative */, false, /* isNoisy */IMPORTANCE_DEFAULT);
+                true /* isUserSentimentNegative */, false /* isNoisy */, IMPORTANCE_DEFAULT,
+                NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.findViewById(R.id.block).performClick();
         mTestableLooper.processAllMessages();
@@ -719,7 +724,7 @@
                 true /* isForBlockingHelper */,
                 true,
                 false /* isUserSentimentNegative */,
-                false, /* isNoisy */IMPORTANCE_DEFAULT);
+                false /* isNoisy */, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         NotificationGuts guts = mock(NotificationGuts.class);
         doCallRealMethod().when(guts).closeControls(anyInt(), anyInt(), anyBoolean(), anyBoolean());
         mNotificationInfo.setGutsParent(guts);
@@ -734,7 +739,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         mNotificationInfo.findViewById(R.id.block).performClick();
         waitForUndoButton();
 
@@ -748,7 +753,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.findViewById(R.id.block).performClick();
         waitForUndoButton();
@@ -781,7 +786,7 @@
                 false /* isNonblockable */,
                 true /* isForBlockingHelper */,
                 true /* isUserSentimentNegative */,
-                false, /* isNoisy */IMPORTANCE_DEFAULT);
+                false/* isNoisy */, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.findViewById(R.id.block).performClick();
         waitForUndoButton();
@@ -803,7 +808,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         mNotificationInfo.findViewById(R.id.minimize).performClick();
         waitForUndoButton();
 
@@ -818,7 +823,7 @@
         mSbn.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.findViewById(R.id.minimize).performClick();
         waitForUndoButton();
@@ -839,7 +844,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.handleCloseControls(true, false);
 
@@ -857,7 +862,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.findViewById(R.id.block).performClick();
         waitForUndoButton();
@@ -879,7 +884,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.findViewById(R.id.minimize).performClick();
         waitForUndoButton();
@@ -901,7 +906,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                true, IMPORTANCE_DEFAULT);
+                true, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.findViewById(R.id.toggle_silent).performClick();
         waitForUndoButton();
@@ -922,7 +927,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.findViewById(R.id.toggle_silent).performClick();
         waitForUndoButton();
@@ -944,7 +949,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                true, IMPORTANCE_DEFAULT);
+                true, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.findViewById(R.id.toggle_silent).performClick();
         waitForUndoButton();
@@ -966,7 +971,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                false, IMPORTANCE_LOW);
+                false, IMPORTANCE_LOW, NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.findViewById(R.id.toggle_silent).performClick();
         waitForUndoButton();
@@ -987,7 +992,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.findViewById(R.id.minimize).performClick();
         waitForUndoButton();
@@ -1003,7 +1008,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.findViewById(R.id.block).performClick();
         waitForUndoButton();
@@ -1020,7 +1025,7 @@
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn,
                 (Runnable saveImportance, StatusBarNotification sbn) -> {
-                }, null, null, true, true, false, IMPORTANCE_DEFAULT);
+                }, null, null, true, true, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.findViewById(R.id.block).performClick();
         mTestableLooper.processAllMessages();
@@ -1038,7 +1043,8 @@
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn,
                 (Runnable saveImportance, StatusBarNotification sbn) -> {
                     saveImportance.run();
-                }, null, null, true, false, false, IMPORTANCE_DEFAULT);
+                }, null, null, true, false, false, IMPORTANCE_DEFAULT,
+                NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.findViewById(R.id.block).performClick();
         mTestableLooper.processAllMessages();
@@ -1074,7 +1080,7 @@
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, sbn, null, null,
                 (View v, Intent intent) -> {
                     latch.countDown();
-                }, true, false, false, IMPORTANCE_DEFAULT);
+                }, true, false, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         final TextView settingsLink = mNotificationInfo.findViewById(R.id.app_settings);
         assertEquals(View.VISIBLE, settingsLink.getVisibility());
         settingsLink.performClick();
@@ -1102,7 +1108,7 @@
                 TEST_PACKAGE_NAME, mNotificationChannel, MULTIPLE_CHANNEL_COUNT, sbn, null, null,
                 (View v, Intent intent) -> {
                     latch.countDown();
-                }, true, false, false, IMPORTANCE_DEFAULT);
+                }, true, false, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         final TextView settingsLink = mNotificationInfo.findViewById(R.id.app_settings);
         assertEquals(View.VISIBLE, settingsLink.getVisibility());
         settingsLink.performClick();
@@ -1121,7 +1127,7 @@
 
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, MULTIPLE_CHANNEL_COUNT, sbn, null, null,
-                null, true, false, false, IMPORTANCE_DEFAULT);
+                null, true, false, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         final TextView settingsLink = mNotificationInfo.findViewById(R.id.app_settings);
         assertEquals(GONE, settingsLink.getVisibility());
     }
@@ -1142,7 +1148,7 @@
 
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, sbn, null, null, null, true, false,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         final TextView settingsLink = mNotificationInfo.findViewById(R.id.app_settings);
         assertEquals(GONE, settingsLink.getVisibility());
     }
@@ -1165,7 +1171,7 @@
 
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, sbn, null, null, null, false, true,
-                true, true, false, IMPORTANCE_DEFAULT);
+                true, true, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
         final TextView settingsLink = mNotificationInfo.findViewById(R.id.app_settings);
         assertEquals(GONE, settingsLink.getVisibility());
     }
@@ -1182,7 +1188,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.findViewById(R.id.minimize).performClick();
         waitForUndoButton();
@@ -1195,7 +1201,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.findViewById(R.id.block).performClick();
         waitForUndoButton();
@@ -1208,7 +1214,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
-                true, IMPORTANCE_DEFAULT);
+                true, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.findViewById(R.id.toggle_silent).performClick();
         waitForUndoButton();
@@ -1222,7 +1228,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
-                true, IMPORTANCE_DEFAULT);
+                true, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.findViewById(R.id.toggle_silent).performClick();
         waitForUndoButton();
@@ -1236,7 +1242,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.findViewById(R.id.block).performClick();
         waitForUndoButton();
@@ -1248,7 +1254,7 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
-                false, IMPORTANCE_DEFAULT);
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
 
         mNotificationInfo.findViewById(R.id.block).performClick();
         waitForUndoButton();
@@ -1256,4 +1262,60 @@
         waitForStopButton();
         assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
     }
+
+    @Test
+    public void testBindNotificationWithInitialBlockAction() throws Exception {
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
+                false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_BLOCK);
+        waitForUndoButton();
+        mNotificationInfo.handleCloseControls(true, false);
+
+        mTestableLooper.processAllMessages();
+        ArgumentCaptor<NotificationChannel> updated =
+                ArgumentCaptor.forClass(NotificationChannel.class);
+        verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+                anyString(), eq(TEST_UID), updated.capture());
+        assertTrue((updated.getValue().getUserLockedFields()
+                & USER_LOCKED_IMPORTANCE) != 0);
+        assertEquals(IMPORTANCE_NONE, updated.getValue().getImportance());
+    }
+
+    @Test
+    public void testBindNotificationWithInitialSilenceAction() throws Exception {
+        mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
+                true, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_TOGGLE_SILENT);
+        waitForUndoButton();
+        mNotificationInfo.handleCloseControls(true, false);
+
+        mTestableLooper.processAllMessages();
+        ArgumentCaptor<NotificationChannel> updated =
+                ArgumentCaptor.forClass(NotificationChannel.class);
+        verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+                anyString(), eq(TEST_UID), updated.capture());
+        assertTrue((updated.getValue().getUserLockedFields()
+                & USER_LOCKED_IMPORTANCE) != 0);
+        assertEquals(IMPORTANCE_LOW, updated.getValue().getImportance());
+    }
+
+    @Test
+    public void testBindNotificationWithInitialUnSilenceAction() throws Exception {
+        mNotificationChannel.setImportance(IMPORTANCE_LOW);
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
+                true, IMPORTANCE_LOW, NotificationInfo.ACTION_TOGGLE_SILENT);
+        waitForUndoButton();
+        mNotificationInfo.handleCloseControls(true, false);
+
+        mTestableLooper.processAllMessages();
+        ArgumentCaptor<NotificationChannel> updated =
+                ArgumentCaptor.forClass(NotificationChannel.class);
+        verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+                anyString(), eq(TEST_UID), updated.capture());
+        assertTrue((updated.getValue().getUserLockedFields()
+                & USER_LOCKED_IMPORTANCE) != 0);
+        assertEquals(IMPORTANCE_HIGH, updated.getValue().getImportance());
+    }
 }
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/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index 23365a4..1481aef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -19,22 +19,22 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
+import android.hardware.display.ColorDisplayManager;
 import android.os.Handler;
 import android.support.test.filters.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
+
 import com.android.internal.app.ColorDisplayController;
-import com.android.systemui.Dependency;
-import com.android.systemui.Prefs;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.qs.AutoAddTracker;
 import com.android.systemui.qs.QSTileHost;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 @RunWith(AndroidTestingRunner.class)
@@ -56,7 +56,7 @@
 
     @Test
     public void nightTileAdded_whenActivated() {
-        if (!ColorDisplayController.isAvailable(mContext)) {
+        if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
             return;
         }
         mAutoTileManager.mColorDisplayCallback.onActivated(true);
@@ -65,7 +65,7 @@
 
     @Test
     public void nightTileNotAdded_whenDeactivated() {
-        if (!ColorDisplayController.isAvailable(mContext)) {
+        if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
             return;
         }
         mAutoTileManager.mColorDisplayCallback.onActivated(false);
@@ -74,7 +74,7 @@
 
     @Test
     public void nightTileAdded_whenNightModeTwilight() {
-        if (!ColorDisplayController.isAvailable(mContext)) {
+        if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
             return;
         }
         mAutoTileManager.mColorDisplayCallback.onAutoModeChanged(
@@ -84,7 +84,7 @@
 
     @Test
     public void nightTileAdded_whenNightModeCustom() {
-        if (!ColorDisplayController.isAvailable(mContext)) {
+        if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
             return;
         }
         mAutoTileManager.mColorDisplayCallback.onAutoModeChanged(
@@ -94,7 +94,7 @@
 
     @Test
     public void nightTileNotAdded_whenNightModeDisabled() {
-        if (!ColorDisplayController.isAvailable(mContext)) {
+        if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
             return;
         }
         mAutoTileManager.mColorDisplayCallback.onAutoModeChanged(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index c536dca..5f02fad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -27,18 +27,23 @@
 
 import static junit.framework.Assert.assertTrue;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.KeyguardManager;
 import android.media.AudioManager;
+import android.os.SystemClock;
 import android.support.test.filters.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.text.TextUtils;
+import android.view.InputDevice;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityManager;
 import android.widget.ImageView;
 
 import com.android.systemui.R;
@@ -48,10 +53,11 @@
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import java.util.function.Predicate;
@@ -59,7 +65,6 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-@Ignore
 public class VolumeDialogImplTest extends SysuiTestCase {
 
     VolumeDialogImpl mDialog;
@@ -113,6 +118,45 @@
                     + " failed test", condition.test(view));
         }
     }
+
+    @Test
+    public void testComputeTimeout() {
+        Mockito.reset(mAccessibilityMgr);
+        mDialog.rescheduleTimeoutH();
+        verify(mAccessibilityMgr).getRecommendedTimeoutMillis(
+                VolumeDialogImpl.DIALOG_TIMEOUT_MILLIS,
+                AccessibilityManager.FLAG_CONTENT_CONTROLS);
+    }
+
+    @Test
+    public void testComputeTimeout_withHovering() {
+        Mockito.reset(mAccessibilityMgr);
+        View dialog = mDialog.getDialogView();
+        long uptimeMillis = SystemClock.uptimeMillis();
+        MotionEvent event = MotionEvent.obtain(uptimeMillis, uptimeMillis,
+                MotionEvent.ACTION_HOVER_ENTER, 0, 0, 0);
+        event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+        dialog.dispatchGenericMotionEvent(event);
+        event.recycle();
+        verify(mAccessibilityMgr).getRecommendedTimeoutMillis(
+                VolumeDialogImpl.DIALOG_HOVERING_TIMEOUT_MILLIS,
+                AccessibilityManager.FLAG_CONTENT_CONTROLS);
+    }
+
+    @Test
+    public void testComputeTimeout_withSafetyWarningOn() {
+        Mockito.reset(mAccessibilityMgr);
+        ArgumentCaptor<VolumeDialogController.Callbacks> controllerCallbackCapture =
+                ArgumentCaptor.forClass(VolumeDialogController.Callbacks.class);
+        verify(mController).addCallback(controllerCallbackCapture.capture(), any());
+        VolumeDialogController.Callbacks callbacks = controllerCallbackCapture.getValue();
+        callbacks.onShowSafetyWarning(AudioManager.FLAG_SHOW_UI);
+        verify(mAccessibilityMgr).getRecommendedTimeoutMillis(
+                VolumeDialogImpl.DIALOG_SAFETYWARNING_TIMEOUT_MILLIS,
+                AccessibilityManager.FLAG_CONTENT_TEXT
+                        | AccessibilityManager.FLAG_CONTENT_CONTROLS);
+    }
+
 /*
     @Test
     public void testContentDescriptions() {
diff --git a/packages/services/PacProcessor/Android.mk b/packages/services/PacProcessor/Android.mk
index 75d0363..295b3d8b 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/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index 8ae5872..3e07d12 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -844,6 +844,8 @@
     //   PACKAGE: App that posted the notification
     // DETAIL: Notification is expanded by user.
     //   PACKAGE: App that posted the notification
+    // COLLAPSE: Notification is collapsed by user.
+    //   PACKAGE: App that posted the notification
     // DISMISS: Notification is dismissed.
     //   PACKAGE: App that posted the notification
     //   SUBTYPE: Dismiss reason from NotificationManagerService.java
@@ -6596,6 +6598,12 @@
     // OS: Q
     NOTIFICATION_ZEN_MODE_OVERRIDING_APP = 1589;
 
+    // ACTION: User sent a direct reply
+    //    PACKAGE: App that posted the notification
+    // CATEGORY: NOTIFICATION
+    // OS: Q
+    NOTIFICATION_DIRECT_REPLY_ACTION = 1590;
+
     // ---- End Q Constants, all Q constants go above this line ----
 
     // Add new aosp constants above this line.
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 4205ac7..31238df 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -166,12 +166,12 @@
         context.registerReceiver(mBroadcastReceiver, filter, null, FgThread.getHandler());
     }
 
-    @Override // from MasterSystemService
+    @Override // from AbstractMasterSystemService
     protected String getServiceSettingsProperty() {
         return Settings.Secure.AUTOFILL_SERVICE;
     }
 
-    @Override // from MasterSystemService
+    @Override // from AbstractMasterSystemService
     protected void registerForExtraSettingsChanges(@NonNull ContentResolver resolver,
             @NonNull ContentObserver observer) {
         resolver.registerContentObserver(Settings.Global.getUriFor(
@@ -188,7 +188,7 @@
                 UserHandle.USER_ALL);
     }
 
-    @Override // from MasterSystemService
+    @Override // from AbstractMasterSystemService
     protected void onSettingsChanged(int userId, @NonNull String property) {
         switch (property) {
             case Settings.Global.AUTOFILL_LOGGING_LEVEL:
@@ -210,25 +210,24 @@
         }
     }
 
-    @Override // from MasterSystemService
-    protected AutofillManagerServiceImpl newServiceLocked(int resolvedUserId, boolean disabled) {
+    @Override // from AbstractMasterSystemService
+    protected AutofillManagerServiceImpl newServiceLocked(@UserIdInt int resolvedUserId,
+            boolean disabled) {
         return new AutofillManagerServiceImpl(this, mLock, mRequestsHistory,
                 mUiLatencyHistory, mWtfHistory, resolvedUserId, mUi, mAutofillCompatState,
                 disabled);
     }
 
-    @Override // MasterSystemService
-    protected AutofillManagerServiceImpl removeCachedServiceLocked(int userId) {
-        final AutofillManagerServiceImpl service = super.removeCachedServiceLocked(userId);
-        if (service != null) {
-            service.destroyLocked();
-            mAutofillCompatState.removeCompatibilityModeRequests(userId);
-        }
-        return service;
+    @Override // AbstractMasterSystemService
+    protected void onServiceRemoved(@NonNull AutofillManagerServiceImpl service,
+            @UserIdInt int userId) {
+        service.destroyLocked();
+        mAutofillCompatState.removeCompatibilityModeRequests(userId);
     }
 
-    @Override // from MasterSystemService
-    protected void onServiceEnabledLocked(@NonNull AutofillManagerServiceImpl service, int userId) {
+    @Override // from AbstractMasterSystemService
+    protected void onServiceEnabledLocked(@NonNull AutofillManagerServiceImpl service,
+            @UserIdInt int userId) {
         addCompatibilityModeRequestsLocked(service, userId);
     }
 
@@ -245,7 +244,7 @@
     }
 
     // Called by Shell command.
-    void destroySessions(int userId, IResultReceiver receiver) {
+    void destroySessions(@UserIdInt int userId, IResultReceiver receiver) {
         Slog.i(TAG, "destroySessions() for userId " + userId);
         getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
 
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/Android.bp b/services/core/Android.bp
index 888ad1d..6174300 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -3,6 +3,7 @@
 
     aidl: {
         include_dirs: [
+            "frameworks/base/cmds/idmap2/idmap2d/aidl",
             "frameworks/native/aidl/binder",
             "frameworks/native/cmds/dumpstate/binder",
             "system/core/storaged/binder",
@@ -13,6 +14,7 @@
     srcs: [
         "java/**/*.java",
         ":dumpstate_aidl",
+        ":idmap2_aidl",
         ":netd_aidl",
         ":netd_metrics_aidl",
         ":installd_aidl",
diff --git a/services/core/java/com/android/server/AbstractMasterSystemService.java b/services/core/java/com/android/server/AbstractMasterSystemService.java
index c955daf..6cae887 100644
--- a/services/core/java/com/android/server/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/AbstractMasterSystemService.java
@@ -244,7 +244,7 @@
      */
     @GuardedBy("mLock")
     @Nullable
-    protected S peekServiceForUserLocked(int userId) {
+    protected S peekServiceForUserLocked(@UserIdInt int userId) {
         final int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
                 Binder.getCallingUid(), userId, false, false, null, null);
         return mServicesCache.get(resolvedUserId);
@@ -254,7 +254,7 @@
      * Updates a cached service for a given user.
      */
     @GuardedBy("mLock")
-    protected void updateCachedServiceLocked(int userId) {
+    protected void updateCachedServiceLocked(@UserIdInt int userId) {
         updateCachedServiceLocked(userId, isDisabledLocked(userId));
     }
 
@@ -262,7 +262,7 @@
      * Checks whether the service is disabled (through {@link UserManager} restrictions) for the
      * given user.
      */
-    protected boolean isDisabledLocked(int userId) {
+    protected boolean isDisabledLocked(@UserIdInt int userId) {
         return mDisabledUsers == null ? false : mDisabledUsers.get(userId);
     }
 
@@ -274,7 +274,7 @@
      * @return service for the user.
      */
     @GuardedBy("mLock")
-    protected S updateCachedServiceLocked(int userId, boolean disabled) {
+    protected S updateCachedServiceLocked(@UserIdInt int userId, boolean disabled) {
         final S service = getServiceForUserLocked(userId);
         if (service != null) {
             service.updateLocked(disabled);
@@ -304,7 +304,7 @@
      * <p>By default doesn't do anything, but can be overridden by subclasses.
      */
     @SuppressWarnings("unused")
-    protected void onServiceEnabledLocked(S service, @UserIdInt int userId) {
+    protected void onServiceEnabledLocked(@NonNull S service, @UserIdInt int userId) {
     }
 
     /**
@@ -314,15 +314,23 @@
      */
     @GuardedBy("mLock")
     @NonNull
-    protected S removeCachedServiceLocked(@UserIdInt int userId) {
+    private S removeCachedServiceLocked(@UserIdInt int userId) {
         final S service = peekServiceForUserLocked(userId);
         if (service != null) {
             mServicesCache.delete(userId);
+            onServiceRemoved(service, userId);
         }
         return service;
     }
 
     /**
+     * Called after the service is removed from the cache.
+     */
+    @SuppressWarnings("unused")
+    protected void onServiceRemoved(@NonNull S service, @UserIdInt int userId) {
+    }
+
+    /**
      * Visits all services in the cache.
      */
     @GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/AbstractPerUserSystemService.java b/services/core/java/com/android/server/AbstractPerUserSystemService.java
index 201abe6..97977df 100644
--- a/services/core/java/com/android/server/AbstractPerUserSystemService.java
+++ b/services/core/java/com/android/server/AbstractPerUserSystemService.java
@@ -167,7 +167,7 @@
     @GuardedBy("mLock")
     protected final int getServiceUidLocked() {
         if (mServiceInfo == null) {
-            Slog.w(mTag, "getServiceUidLocked(): no mServiceInfo");
+            if (mMaster.verbose) Slog.v(mTag, "getServiceUidLocked(): no mServiceInfo");
             return Process.INVALID_UID;
         }
         return mServiceInfo.applicationInfo.uid;
@@ -267,8 +267,18 @@
     @GuardedBy("mLock")
     protected void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) {
         pw.print(prefix); pw.print("User: "); pw.println(mUserId);
-        pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled);
+        pw.print(prefix); pw.print("Disabled by UserManager: "); pw.println(mDisabled);
         pw.print(prefix); pw.print("Setup complete: "); pw.println(mSetupComplete);
-        pw.print(prefix); pw.print("Service name: "); pw.println(getComponentNameFromSettings());
+        if (mServiceInfo != null) {
+            pw.print(prefix); pw.print("Service UID: ");
+            pw.println(mServiceInfo.applicationInfo.uid);
+        }
+        final String componentName = getComponentNameFromSettings();
+        if (componentName != null) {
+            pw.print(prefix); pw.print("Service name: ");
+            pw.println(componentName);
+        } else {
+            pw.println("No service package set");
+        }
     }
 }
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index 5814064..fa98da5 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -80,6 +80,7 @@
 import android.util.TimeUtils;
 import android.util.Xml;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IAppOpsActiveCallback;
 import com.android.internal.app.IAppOpsCallback;
@@ -219,6 +220,7 @@
 
     SparseIntArray mProfileOwners;
 
+    @GuardedBy("this")
     private CheckOpsDelegate mCheckOpsDelegate;
 
     /**
@@ -1589,24 +1591,28 @@
     public int checkOperation(int code, int uid, String packageName) {
         final CheckOpsDelegate delegate;
         synchronized (this) {
-            if (mCheckOpsDelegate == null) {
-                return checkOperationImpl(code, uid, packageName);
-            }
             delegate = mCheckOpsDelegate;
         }
+        if (delegate == null) {
+            return checkOperationImpl(code, uid, packageName);
+        }
         return delegate.checkOperation(code, uid, packageName,
                     AppOpsService.this::checkOperationImpl);
     }
 
     private int checkOperationImpl(int code, int uid, String packageName) {
+        verifyIncomingUid(uid);
+        verifyIncomingOp(code);
+        String resolvedPackageName = resolvePackageName(uid, packageName);
+        if (resolvedPackageName == null) {
+            return AppOpsManager.MODE_IGNORED;
+        }
+        return checkOperationUnchecked(code, uid, resolvedPackageName);
+    }
+
+    private int checkOperationUnchecked(int code, int uid, String packageName) {
         synchronized (this) {
-            verifyIncomingUid(uid);
-            verifyIncomingOp(code);
-            String resolvedPackageName = resolvePackageName(uid, packageName);
-            if (resolvedPackageName == null) {
-                return AppOpsManager.MODE_IGNORED;
-            }
-            if (isOpRestrictedLocked(uid, code, resolvedPackageName)) {
+            if (isOpRestrictedLocked(uid, code, packageName)) {
                 return AppOpsManager.MODE_IGNORED;
             }
             code = AppOpsManager.opToSwitch(code);
@@ -1615,7 +1621,7 @@
                     && uidState.opModes.indexOfKey(code) >= 0) {
                 return uidState.opModes.get(code);
             }
-            Op op = getOpLocked(code, uid, resolvedPackageName, false, true, false);
+            Op op = getOpLocked(code, uid, packageName, false, true, false);
             if (op == null) {
                 return AppOpsManager.opToDefaultMode(code);
             }
@@ -1627,31 +1633,31 @@
     public int checkAudioOperation(int code, int usage, int uid, String packageName) {
         final CheckOpsDelegate delegate;
         synchronized (this) {
-            if (mCheckOpsDelegate == null) {
-                return checkAudioOperationImpl(code, usage, uid, packageName);
-            }
             delegate = mCheckOpsDelegate;
         }
+        if (delegate == null) {
+            return checkAudioOperationImpl(code, usage, uid, packageName);
+        }
         return delegate.checkAudioOperation(code, usage, uid, packageName,
                 AppOpsService.this::checkAudioOperationImpl);
     }
 
     private int checkAudioOperationImpl(int code, int usage, int uid, String packageName) {
+        boolean suspended;
+        try {
+            suspended = isPackageSuspendedForUser(packageName, uid);
+        } catch (IllegalArgumentException ex) {
+            // Package not found.
+            suspended = false;
+        }
+
+        if (suspended) {
+            Slog.i(TAG, "Audio disabled for suspended package=" + packageName
+                    + " for uid=" + uid);
+            return AppOpsManager.MODE_IGNORED;
+        }
+
         synchronized (this) {
-            boolean suspended;
-            try {
-                suspended = isPackageSuspendedForUser(packageName, uid);
-            } catch (IllegalArgumentException ex) {
-                // Package not found.
-                suspended = false;
-            }
-
-            if (suspended) {
-                Slog.i(TAG, "Audio disabled for suspended package=" + packageName
-                        + " for uid=" + uid);
-                return AppOpsManager.MODE_IGNORED;
-            }
-
             final int mode = checkRestrictionLocked(code, usage, uid, packageName);
             if (mode != AppOpsManager.MODE_ALLOWED) {
                 return mode;
@@ -1754,11 +1760,11 @@
     public int noteOperation(int code, int uid, String packageName) {
         final CheckOpsDelegate delegate;
         synchronized (this) {
-            if (mCheckOpsDelegate == null) {
-                return noteOperationImpl(code, uid, packageName);
-            }
             delegate = mCheckOpsDelegate;
         }
+        if (delegate == null) {
+            return noteOperationImpl(code, uid, packageName);
+        }
         return delegate.noteOperation(code, uid, packageName,
                 AppOpsService.this::noteOperationImpl);
     }
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index b5217ad..c660cc6 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -677,6 +677,8 @@
             stracker.setStarted(true, mAm.mProcessStats.getMemFactorLocked(), r.lastActivity);
         }
         r.callStart = false;
+        StatsLog.write(StatsLog.SERVICE_STATE_CHANGED, r.appInfo.uid, r.name.getPackageName(),
+                r.name.getClassName(), StatsLog.SERVICE_STATE_CHANGED__STATE__START);
         synchronized (r.stats.getBatteryStats()) {
             r.stats.startRunningLocked();
         }
@@ -715,6 +717,9 @@
             service.delayedStop = true;
             return;
         }
+        StatsLog.write(StatsLog.SERVICE_STATE_CHANGED, service.appInfo.uid,
+                service.name.getPackageName(), service.name.getClassName(),
+                StatsLog.SERVICE_STATE_CHANGED__STATE__STOP);
         synchronized (service.stats.getBatteryStats()) {
             service.stats.stopRunningLocked();
         }
@@ -856,6 +861,8 @@
                 }
             }
 
+            StatsLog.write(StatsLog.SERVICE_STATE_CHANGED, r.appInfo.uid, r.name.getPackageName(),
+                    r.name.getClassName(), StatsLog.SERVICE_STATE_CHANGED__STATE__STOP);
             synchronized (r.stats.getBatteryStats()) {
                 r.stats.stopRunningLocked();
             }
@@ -2517,6 +2524,8 @@
                 EventLogTags.writeAmCreateService(
                         r.userId, System.identityHashCode(r), nameTerm, r.app.uid, r.app.pid);
             }
+            StatsLog.write(StatsLog.SERVICE_LAUNCH_REPORTED, r.appInfo.uid, r.name.getPackageName(),
+                    r.name.getClassName());
             synchronized (r.stats.getBatteryStats()) {
                 r.stats.startLaunchedLocked();
             }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 771d376..aa96082 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -4820,6 +4820,10 @@
             String packageName, IBinder token, String resultWho,
             int requestCode, Intent[] intents, String[] resolvedTypes,
             int flags, Bundle bOptions, int userId) {
+
+        // NOTE: The service lock isn't held in this method because nothing in the method requires
+        // the service lock to be held.
+
         enforceNotIsolatedCaller("getIntentSender");
         // Refuse possible leaked file descriptors
         if (intents != null) {
@@ -4851,43 +4855,41 @@
             }
         }
 
-        synchronized(this) {
-            int callingUid = Binder.getCallingUid();
-            int origUserId = userId;
-            userId = mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
-                    type == ActivityManager.INTENT_SENDER_BROADCAST,
-                    ALLOW_NON_FULL, "getIntentSender", null);
-            if (origUserId == UserHandle.USER_CURRENT) {
-                // We don't want to evaluate this until the pending intent is
-                // actually executed.  However, we do want to always do the
-                // security checking for it above.
-                userId = UserHandle.USER_CURRENT;
-            }
-            try {
-                if (callingUid != 0 && callingUid != SYSTEM_UID) {
-                    final int uid = AppGlobals.getPackageManager().getPackageUid(packageName,
-                            MATCH_DEBUG_TRIAGED_MISSING, UserHandle.getUserId(callingUid));
-                    if (!UserHandle.isSameApp(callingUid, uid)) {
-                        String msg = "Permission Denial: getIntentSender() from pid="
-                            + Binder.getCallingPid()
-                            + ", uid=" + Binder.getCallingUid()
-                            + ", (need uid=" + uid + ")"
-                            + " is not allowed to send as package " + packageName;
-                        Slog.w(TAG, msg);
-                        throw new SecurityException(msg);
-                    }
+        int callingUid = Binder.getCallingUid();
+        int origUserId = userId;
+        userId = mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
+                type == ActivityManager.INTENT_SENDER_BROADCAST,
+                ALLOW_NON_FULL, "getIntentSender", null);
+        if (origUserId == UserHandle.USER_CURRENT) {
+            // We don't want to evaluate this until the pending intent is
+            // actually executed.  However, we do want to always do the
+            // security checking for it above.
+            userId = UserHandle.USER_CURRENT;
+        }
+        try {
+            if (callingUid != 0 && callingUid != SYSTEM_UID) {
+                final int uid = AppGlobals.getPackageManager().getPackageUid(packageName,
+                        MATCH_DEBUG_TRIAGED_MISSING, UserHandle.getUserId(callingUid));
+                if (!UserHandle.isSameApp(callingUid, uid)) {
+                    String msg = "Permission Denial: getIntentSender() from pid="
+                        + Binder.getCallingPid()
+                        + ", uid=" + Binder.getCallingUid()
+                        + ", (need uid=" + uid + ")"
+                        + " is not allowed to send as package " + packageName;
+                    Slog.w(TAG, msg);
+                    throw new SecurityException(msg);
                 }
+            }
 
-                if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) {
-                    return mAtmInternal.getIntentSender(type, packageName, callingUid, userId,
-                            token, resultWho, requestCode, intents, resolvedTypes, flags, bOptions);
-                }
-                return mPendingIntentController.getIntentSender(type, packageName, callingUid,
-                        userId, token, resultWho, requestCode, intents, resolvedTypes, flags,
-                        bOptions);
-            } catch (RemoteException e) {
-                throw new SecurityException(e);
+            if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) {
+                return mAtmInternal.getIntentSender(type, packageName, callingUid, userId,
+                        token, resultWho, requestCode, intents, resolvedTypes, flags, bOptions);
             }
+            return mPendingIntentController.getIntentSender(type, packageName, callingUid,
+                    userId, token, resultWho, requestCode, intents, resolvedTypes, flags,
+                    bOptions);
+        } catch (RemoteException e) {
+            throw new SecurityException(e);
         }
     }
 
@@ -7002,7 +7004,7 @@
         mCoreSettingsObserver = new CoreSettingsObserver(this);
         mActivityTaskManager.installSystemProviders();
         mDevelopmentSettingsObserver = new DevelopmentSettingsObserver();
-        GlobalSettingsToPropertiesMapper.start(mContext.getContentResolver());
+        SettingsToPropertiesMapper.start(mContext.getContentResolver());
 
         // Now that the settings provider is published we can consider sending
         // in a rescue party.
diff --git a/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java
deleted file mode 100644
index 1366c21..0000000
--- a/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.am;
-
-import android.content.ContentResolver;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Build;
-import android.os.SystemProperties;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.Slog;
-import android.view.ThreadedRenderer;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
-
-/**
- * Maps global system settings to system properties.
- * <p>The properties are dynamically updated when settings change.
- */
-class GlobalSettingsToPropertiesMapper {
-
-    private static final String TAG = "GlobalSettingsToPropertiesMapper";
-
-    // List mapping entries in the following format:
-    // {Settings.Global.SETTING_NAME, "system_property_name"}
-    // Important: Property being added should be whitelisted by SELinux policy or have one of the
-    // already whitelisted prefixes in system_server.te, e.g. sys.
-    private static final String[][] sGlobalSettingsMapping = new String[][] {
-        {Settings.Global.SYS_VDSO, "sys.vdso"},
-        {Settings.Global.FPS_DEVISOR, ThreadedRenderer.DEBUG_FPS_DIVISOR},
-        {Settings.Global.DISPLAY_PANEL_LPM, "sys.display_panel_lpm"},
-        {Settings.Global.SYS_UIDCPUPOWER, "sys.uidcpupower"},
-        {Settings.Global.SYS_TRACED, "sys.traced.enable_override"},
-    };
-
-
-    private final ContentResolver mContentResolver;
-    private final String[][] mGlobalSettingsMapping;
-
-    @VisibleForTesting
-    GlobalSettingsToPropertiesMapper(ContentResolver contentResolver,
-            String[][] globalSettingsMapping) {
-        mContentResolver = contentResolver;
-        mGlobalSettingsMapping = globalSettingsMapping;
-    }
-
-    void updatePropertiesFromGlobalSettings() {
-        for (String[] entry : mGlobalSettingsMapping) {
-            final String settingName = entry[0];
-            final String propName = entry[1];
-            Uri settingUri = Settings.Global.getUriFor(settingName);
-            Preconditions.checkNotNull(settingUri, "Setting " + settingName + " not found");
-            ContentObserver co = new ContentObserver(null) {
-                @Override
-                public void onChange(boolean selfChange) {
-                    updatePropertyFromSetting(settingName, propName);
-                }
-            };
-            updatePropertyFromSetting(settingName, propName);
-            mContentResolver.registerContentObserver(settingUri, false, co);
-        }
-    }
-
-    public static void start(ContentResolver contentResolver) {
-        new GlobalSettingsToPropertiesMapper(contentResolver, sGlobalSettingsMapping)
-                .updatePropertiesFromGlobalSettings();
-    }
-
-    private String getGlobalSetting(String name) {
-        return Settings.Global.getString(mContentResolver, name);
-    }
-
-    private void setProperty(String key, String value) {
-        // Check if need to clear the property
-        if (value == null) {
-            // It's impossible to remove system property, therefore we check previous value to
-            // avoid setting an empty string if the property wasn't set.
-            if (TextUtils.isEmpty(systemPropertiesGet(key))) {
-                return;
-            }
-            value = "";
-        }
-        try {
-            systemPropertiesSet(key, value);
-        } catch (Exception e) {
-            // Failure to set a property can be caused by SELinux denial. This usually indicates
-            // that the property wasn't whitelisted in sepolicy.
-            // No need to report it on all user devices, only on debug builds.
-            if (Build.IS_DEBUGGABLE) {
-                Slog.wtf(TAG, "Unable to set property " + key + " value '" + value + "'", e);
-            } else {
-                Slog.e(TAG, "Unable to set property " + key + " value '" + value + "'", e);
-            }
-        }
-    }
-
-    @VisibleForTesting
-    protected String systemPropertiesGet(String key) {
-        return SystemProperties.get(key);
-    }
-
-    @VisibleForTesting
-    protected void systemPropertiesSet(String key, String value) {
-        SystemProperties.set(key, value);
-    }
-
-    @VisibleForTesting
-    void updatePropertyFromSetting(String settingName, String propName) {
-        String settingValue = getGlobalSetting(settingName);
-        setProperty(propName, settingValue);
-    }
-}
diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS
index 79c98e5..5208ca5 100644
--- a/services/core/java/com/android/server/am/OWNERS
+++ b/services/core/java/com/android/server/am/OWNERS
@@ -27,4 +27,4 @@
 michaelwr@google.com
 narayan@google.com
 
-per-file GlobalSettingsToPropertiesMapper.java = omakoto@google.com, svetoslavganov@google.com, yamasani@google.com
+per-file SettingsToPropertiesMapper.java = omakoto@google.com, svetoslavganov@google.com, yamasani@google.com
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
new file mode 100644
index 0000000..a5848ca
--- /dev/null
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Build;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.HashSet;
+
+/**
+ * Maps system settings to system properties.
+ * <p>The properties are dynamically updated when settings change.
+ */
+class SettingsToPropertiesMapper {
+
+    private static final String TAG = "SettingsToPropertiesMapper";
+
+    private static final String SYSTEM_PROPERTY_PREFIX = "persist.device_config.";
+
+    private static final String RESET_PERFORMED_PROPERTY = "device_config.reset_performed";
+
+    private static final String RESET_RECORD_FILE_PATH =
+            "/data/server_configurable_flags/reset_flags";
+
+    private static final String SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX = "^[\\w\\.\\-@:]*$";
+
+    private static final String SYSTEM_PROPERTY_INVALID_SUBSTRING = "..";
+
+    private static final int SYSTEM_PROPERTY_MAX_LENGTH = 92;
+
+    // experiment flags added to Global.Settings(before new "Config" provider table is available)
+    // will be added under this category.
+    private static final String GLOBAL_SETTINGS_CATEGORY = "global_settings";
+
+    // Add the global setting you want to push to native level as experiment flag into this list.
+    //
+    // NOTE: please grant write permission system property prefix
+    // with format persist.experiment.[experiment_category_name]. in system_server.te and grant read
+    // permission in the corresponding .te file your feature belongs to.
+    @VisibleForTesting
+    static final String[] sGlobalSettings = new String[] {
+    };
+
+    @VisibleForTesting
+    static final String[] sDeviceConfigScopes = new String[] {
+    };
+
+    private final String[] mGlobalSettings;
+
+    private final String[] mDeviceConfigScopes;
+
+    private final ContentResolver mContentResolver;
+
+    @VisibleForTesting
+    protected SettingsToPropertiesMapper(ContentResolver contentResolver,
+            String[] globalSettings,
+            String[] deviceConfigScopes) {
+        mContentResolver = contentResolver;
+        mGlobalSettings = globalSettings;
+        mDeviceConfigScopes = deviceConfigScopes;
+    }
+
+    @VisibleForTesting
+    void updatePropertiesFromSettings() {
+        for (String globalSetting : mGlobalSettings) {
+            Uri settingUri = Settings.Global.getUriFor(globalSetting);
+            String propName = makePropertyName(GLOBAL_SETTINGS_CATEGORY, globalSetting);
+            if (settingUri == null) {
+                log("setting uri is null for globalSetting " + globalSetting);
+                continue;
+            }
+            if (propName == null) {
+                log("invalid prop name for globalSetting " + globalSetting);
+                continue;
+            }
+
+            ContentObserver co = new ContentObserver(null) {
+                @Override
+                public void onChange(boolean selfChange) {
+                    updatePropertyFromSetting(globalSetting, propName, true);
+                }
+            };
+
+            // only updating on starting up when no native flags reset is performed during current
+            // booting.
+            if (!isNativeFlagsResetPerformed()) {
+                updatePropertyFromSetting(globalSetting, propName, true);
+            }
+            mContentResolver.registerContentObserver(settingUri, false, co);
+        }
+
+        // TODO: address sDeviceConfigScopes after DeviceConfig APIs are available.
+    }
+
+    public static SettingsToPropertiesMapper start(ContentResolver contentResolver) {
+        SettingsToPropertiesMapper mapper =  new SettingsToPropertiesMapper(
+                contentResolver, sGlobalSettings, sDeviceConfigScopes);
+        mapper.updatePropertiesFromSettings();
+        return mapper;
+    }
+
+    /**
+     * If native level flags reset has been performed as an attempt to recover from a crash loop
+     * during current device booting.
+     * @return
+     */
+    public boolean isNativeFlagsResetPerformed() {
+        String value = systemPropertiesGet(RESET_PERFORMED_PROPERTY);
+        return "true".equals(value);
+    }
+
+    /**
+     * return an array of native flag categories under which flags got reset during current device
+     * booting.
+     * @return
+     */
+    public String[] getResetNativeCategories() {
+        if (!isNativeFlagsResetPerformed()) {
+            return new String[0];
+        }
+
+        String content = getResetFlagsFileContent();
+        if (TextUtils.isEmpty(content)) {
+            return new String[0];
+        }
+
+        String[] property_names = content.split(";");
+        HashSet<String> categories = new HashSet<>();
+        for (String property_name : property_names) {
+            String[] segments = property_name.split("\\.");
+            if (segments.length < 3) {
+                log("failed to extract category name from property " + property_name);
+                continue;
+            }
+            categories.add(segments[2]);
+        }
+        return categories.toArray(new String[0]);
+    }
+
+    /**
+     * system property name constructing rule: "persist.device_config.[category_name].[flag_name]".
+     * If the name contains invalid characters or substrings for system property name,
+     * will return null.
+     * @param categoryName
+     * @param flagName
+     * @return
+     */
+    @VisibleForTesting
+    static String makePropertyName(String categoryName, String flagName) {
+        String propertyName = SYSTEM_PROPERTY_PREFIX + categoryName + "." + flagName;
+
+        if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX)
+                || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) {
+            return null;
+        }
+
+        return propertyName;
+    }
+
+    private String getSetting(String name, boolean isGlobalSetting) {
+        if (isGlobalSetting) {
+            return Settings.Global.getString(mContentResolver, name);
+        } else {
+            // TODO: complete the code after DeviceConfig APIs implemented.
+            return null;
+        }
+    }
+
+    private void setProperty(String key, String value) {
+        // Check if need to clear the property
+        if (value == null) {
+            // It's impossible to remove system property, therefore we check previous value to
+            // avoid setting an empty string if the property wasn't set.
+            if (TextUtils.isEmpty(systemPropertiesGet(key))) {
+                return;
+            }
+            value = "";
+        } else if (value.length() > SYSTEM_PROPERTY_MAX_LENGTH) {
+            log(value + " exceeds system property max length.");
+            return;
+        }
+
+        try {
+            systemPropertiesSet(key, value);
+        } catch (Exception e) {
+            // Failure to set a property can be caused by SELinux denial. This usually indicates
+            // that the property wasn't whitelisted in sepolicy.
+            // No need to report it on all user devices, only on debug builds.
+            log("Unable to set property " + key + " value '" + value + "'", e);
+        }
+    }
+
+    private static void log(String msg, Exception e) {
+        if (Build.IS_DEBUGGABLE) {
+            Slog.wtf(TAG, msg, e);
+        } else {
+            Slog.e(TAG, msg, e);
+        }
+    }
+
+    private static void log(String msg) {
+        if (Build.IS_DEBUGGABLE) {
+            Slog.wtf(TAG, msg);
+        } else {
+            Slog.e(TAG, msg);
+        }
+    }
+
+    @VisibleForTesting
+    protected String systemPropertiesGet(String key) {
+        return SystemProperties.get(key);
+    }
+
+    @VisibleForTesting
+    protected void systemPropertiesSet(String key, String value) {
+        SystemProperties.set(key, value);
+    }
+
+    @VisibleForTesting
+    protected String getResetFlagsFileContent() {
+        String content = null;
+        try {
+            File reset_flag_file = new File(RESET_RECORD_FILE_PATH);
+            BufferedReader br = new BufferedReader(new FileReader(reset_flag_file));
+            content = br.readLine();
+
+            br.close();
+        } catch (IOException ioe) {
+            log("failed to read file " + RESET_RECORD_FILE_PATH, ioe);
+        }
+        return content;
+    }
+
+    @VisibleForTesting
+    void updatePropertyFromSetting(String settingName, String propName, boolean isGlobalSetting) {
+        String settingValue = getSetting(settingName, isGlobalSetting);
+        setProperty(propName, settingValue);
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 278c55f..5f09189 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -246,9 +246,20 @@
         public void authenticate(IBinder token, long sessionId, int userId,
                 IBiometricServiceReceiver receiver, int flags, String opPackageName,
                 Bundle bundle, IBiometricPromptReceiver dialogReceiver) throws RemoteException {
-            // Check the USE_BIOMETRIC permission here. In the BiometricServiceBase, check do the
-            // AppOps and foreground check.
-            checkPermission();
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+            final int callingUserId = UserHandle.getCallingUserId();
+
+            // In the BiometricServiceBase, check do the AppOps and foreground check.
+            if (userId == callingUserId) {
+                // Check the USE_BIOMETRIC permission here.
+                checkPermission();
+            } else {
+                // Only allow internal clients to authenticate with a different userId
+                Slog.w(TAG, "User " + callingUserId + " is requesting authentication of userid: "
+                        + userId);
+                checkInternalPermission();
+            }
 
             if (token == null || receiver == null || opPackageName == null || bundle == null
                     || dialogReceiver == null) {
@@ -262,10 +273,6 @@
                 checkInternalPermission();
             }
 
-            final int callingUid = Binder.getCallingUid();
-            final int callingPid = Binder.getCallingPid();
-            final int callingUserId = UserHandle.getCallingUserId();
-
             mHandler.post(() -> {
                 final Pair<Integer, Integer> result = checkAndGetBiometricModality(callingUserId);
                 final int modality = result.first;
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index a769590..65537ad 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -55,6 +55,7 @@
 
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.intelligence.IntelligenceManagerInternal;
 import com.android.server.uri.UriGrantsManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
@@ -157,6 +158,7 @@
     private final IUserManager mUm;
     private final PackageManager mPm;
     private final AppOpsManager mAppOps;
+    private final IntelligenceManagerInternal mIm;
     private final IBinder mPermissionOwner;
     private HostClipboardMonitor mHostClipboardMonitor = null;
     private Thread mHostMonitorThread = null;
@@ -176,6 +178,7 @@
         mPm = getContext().getPackageManager();
         mUm = (IUserManager) ServiceManager.getService(Context.USER_SERVICE);
         mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
+        mIm = LocalServices.getService(IntelligenceManagerInternal.class);
         final IBinder permOwner = mUgmInternal.newUriPermissionOwner("clipboard");
         mPermissionOwner = permOwner;
         if (IS_EMULATOR) {
@@ -635,8 +638,9 @@
             return true;
         }
         // The default IME is always allowed to access the clipboard.
+        int userId = UserHandle.getUserId(callingUid);
         String defaultIme = Settings.Secure.getStringForUser(getContext().getContentResolver(),
-                Settings.Secure.DEFAULT_INPUT_METHOD, UserHandle.getUserId(callingUid));
+                Settings.Secure.DEFAULT_INPUT_METHOD, userId);
         if (!TextUtils.isEmpty(defaultIme)) {
             final String imePkg = ComponentName.unflattenFromString(defaultIme).getPackageName();
             if (imePkg.equals(callingPackage)) {
@@ -646,13 +650,18 @@
 
         switch (op) {
             case AppOpsManager.OP_READ_CLIPBOARD:
-                // Clipboard can only be read by applications with focus.
-                boolean uidFocused = mWm.isUidFocused(callingUid);
-                if (!uidFocused) {
-                    Slog.e(TAG, "Denying clipboard access to " + callingPackage
-                            + ", application is not in focus.");
+                // Clipboard can only be read by applications with focus..
+                boolean allowed = mWm.isUidFocused(callingUid);
+                if (!allowed && mIm != null) {
+                    // ...or the Intelligence Service
+                    allowed = mIm.isIntelligenceServiceForUser(callingUid, userId);
                 }
-                return uidFocused;
+                if (!allowed) {
+                    Slog.e(TAG, "Denying clipboard access to " + callingPackage
+                            + ", application is not in focus neither is the IntelligeService for "
+                            + "user " + userId);
+                }
+                return allowed;
             case AppOpsManager.OP_WRITE_CLIPBOARD:
                 // Writing is allowed without focus.
                 return true;
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/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index c20079e..3a31c9c 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -72,6 +72,8 @@
 import android.view.IInputFilterHost;
 import android.view.IWindow;
 import android.view.InputChannel;
+import android.view.InputApplicationHandle;
+import android.view.InputWindowHandle;
 import android.view.InputDevice;
 import android.view.InputEvent;
 import android.view.KeyEvent;
@@ -197,7 +199,7 @@
     private static native boolean nativeHasKeys(long ptr,
             int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists);
     private static native void nativeRegisterInputChannel(long ptr, InputChannel inputChannel,
-            InputWindowHandle inputWindowHandle, int displayId);
+            int displayId);
     private static native void nativeUnregisterInputChannel(long ptr, InputChannel inputChannel);
     private static native void nativeSetInputFilterEnabled(long ptr, boolean enable);
     private static native int nativeInjectInputEvent(long ptr, InputEvent event,
@@ -486,8 +488,7 @@
         }
 
         InputChannel[] inputChannels = InputChannel.openInputChannelPair(inputChannelName);
-        // Register channel for monitor.
-        nativeRegisterInputChannel(mPtr, inputChannels[0], null, displayId);
+        nativeRegisterInputChannel(mPtr, inputChannels[0], displayId);
         inputChannels[0].dispose(); // don't need to retain the Java object reference
         return inputChannels[1];
     }
@@ -498,14 +499,17 @@
      * @param inputWindowHandle The handle of the input window associated with the
      * input channel, or null if none.
      */
-    public void registerInputChannel(InputChannel inputChannel,
-            InputWindowHandle inputWindowHandle) {
+    public void registerInputChannel(InputChannel inputChannel, IBinder token) {
         if (inputChannel == null) {
             throw new IllegalArgumentException("inputChannel must not be null.");
         }
 
-        // Register channel for normal.
-        nativeRegisterInputChannel(mPtr, inputChannel, inputWindowHandle, Display.INVALID_DISPLAY);
+        if (token == null) {
+            token = new Binder();
+        }
+        inputChannel.setToken(token);
+
+        nativeRegisterInputChannel(mPtr, inputChannel, Display.INVALID_DISPLAY);
     }
 
     /**
@@ -1791,15 +1795,15 @@
     }
 
     // Native callback.
-    private void notifyInputChannelBroken(InputWindowHandle inputWindowHandle) {
-        mWindowManagerCallbacks.notifyInputChannelBroken(inputWindowHandle);
+    private void notifyInputChannelBroken(IBinder token) {
+        mWindowManagerCallbacks.notifyInputChannelBroken(token);
     }
 
     // Native callback.
     private long notifyANR(InputApplicationHandle inputApplicationHandle,
-            InputWindowHandle inputWindowHandle, String reason) {
+            IBinder token, String reason) {
         return mWindowManagerCallbacks.notifyANR(
-                inputApplicationHandle, inputWindowHandle, reason);
+                inputApplicationHandle, token, reason);
     }
 
     // Native callback.
@@ -1830,13 +1834,13 @@
     }
 
     // Native callback.
-    private long interceptKeyBeforeDispatching(InputWindowHandle focus,
+    private long interceptKeyBeforeDispatching(IBinder focus,
             KeyEvent event, int policyFlags) {
         return mWindowManagerCallbacks.interceptKeyBeforeDispatching(focus, event, policyFlags);
     }
 
     // Native callback.
-    private KeyEvent dispatchUnhandledKey(InputWindowHandle focus,
+    private KeyEvent dispatchUnhandledKey(IBinder focus,
             KeyEvent event, int policyFlags) {
         return mWindowManagerCallbacks.dispatchUnhandledKey(focus, event, policyFlags);
     }
@@ -1987,19 +1991,19 @@
 
         public void notifyCameraLensCoverSwitchChanged(long whenNanos, boolean lensCovered);
 
-        public void notifyInputChannelBroken(InputWindowHandle inputWindowHandle);
+        public void notifyInputChannelBroken(IBinder token);
 
         public long notifyANR(InputApplicationHandle inputApplicationHandle,
-                InputWindowHandle inputWindowHandle, String reason);
+                IBinder token, String reason);
 
         public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags);
 
         public int interceptMotionBeforeQueueingNonInteractive(long whenNanos, int policyFlags);
 
-        public long interceptKeyBeforeDispatching(InputWindowHandle focus,
+        public long interceptKeyBeforeDispatching(IBinder token,
                 KeyEvent event, int policyFlags);
 
-        public KeyEvent dispatchUnhandledKey(InputWindowHandle focus,
+        public KeyEvent dispatchUnhandledKey(IBinder token,
                 KeyEvent event, int policyFlags);
 
         public int getPointerLayer();
diff --git a/services/core/java/com/android/server/intelligence/IntelligenceManagerInternal.java b/services/core/java/com/android/server/intelligence/IntelligenceManagerInternal.java
new file mode 100644
index 0000000..0ed56ff
--- /dev/null
+++ b/services/core/java/com/android/server/intelligence/IntelligenceManagerInternal.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.intelligence;
+
+import android.annotation.UserIdInt;
+
+/**
+ * Intelligence Manager local system service interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class IntelligenceManagerInternal {
+
+    /**
+     * Checks whether the given {@code uid} owns the
+     * {@link android.service.intelligence.IntelligenceService} implementation associated with the
+     * given {@code userId}.
+     */
+    public abstract boolean isIntelligenceServiceForUser(int uid, @UserIdInt int userId);
+}
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/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 1c7572e..6195ed9e 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -870,6 +870,7 @@
                     }
                     if (expanded && userAction) {
                         r.recordExpanded();
+                        reportUserInteraction(r);
                     }
                     EventLogTags.writeNotificationExpansion(key,
                             userAction ? 1 : 0, expanded ? 1 : 0,
@@ -885,6 +886,9 @@
                 NotificationRecord r = mNotificationsByKey.get(key);
                 if (r != null) {
                     r.recordDirectReplied();
+                    mMetricsLogger.write(r.getLogMaker()
+                            .setCategory(MetricsEvent.NOTIFICATION_DIRECT_REPLY_ACTION)
+                            .setType(MetricsEvent.TYPE_ACTION));
                     reportUserInteraction(r);
                 }
             }
@@ -1160,6 +1164,7 @@
                     mConditionProviders.onUserSwitched(userId);
                     mListeners.onUserSwitched(userId);
                     mZenModeHelper.onUserSwitched(userId);
+                    mPreferencesHelper.onUserSwitched(userId);
                 }
                 // assistant is the only thing that cares about managed profiles specifically
                 mAssistants.onUserSwitched(userId);
@@ -1188,6 +1193,7 @@
                     mConditionProviders.onUserUnlocked(userId);
                     mListeners.onUserUnlocked(userId);
                     mZenModeHelper.onUserUnlocked(userId);
+                    mPreferencesHelper.onUserUnlocked(userId);
                 }
             }
         }
@@ -1982,7 +1988,7 @@
     }
 
     /**
-     * Report to usage stats that the notification was clicked.
+     * Report to usage stats that the user interacted with the notification.
      * @param r notification record
      */
     protected void reportUserInteraction(NotificationRecord r) {
@@ -2525,6 +2531,19 @@
         }
 
         @Override
+        public int getAppsBypassingDndCount(int userId) {
+            checkCallerIsSystem();
+            return mPreferencesHelper.getAppsBypassingDndCount(userId);
+        }
+
+        @Override
+        public ParceledListSlice<NotificationChannel> getNotificationChannelsBypassingDnd(
+                String pkg, int userId) {
+            checkCallerIsSystem();
+            return mPreferencesHelper.getNotificationChannelsBypassingDnd(pkg, userId);
+        }
+
+        @Override
         public boolean areChannelsBypassingDnd() {
             return mPreferencesHelper.areChannelsBypassingDnd();
         }
@@ -4521,6 +4540,7 @@
                             mDuration)
                     .addTaggedData(MetricsEvent.NOTIFICATION_SNOOZED_CRITERIA,
                             mSnoozeCriterionId == null ? 0 : 1));
+            reportUserInteraction(r);
             boolean wasPosted = removeFromNotificationListsLocked(r);
             cancelNotificationLocked(r, false, REASON_SNOOZED, wasPosted, null);
             updateLightsLocked();
@@ -5529,7 +5549,7 @@
             ArrayList<ArrayList<SnoozeCriterion>> snoozeCriteriaBefore = new ArrayList<>(N);
             ArrayList<Integer> userSentimentBefore = new ArrayList<>(N);
             ArrayList<Integer> suppressVisuallyBefore = new ArrayList<>(N);
-            ArrayList<ArrayList<Notification.Action>> smartActionsBefore = new ArrayList<>(N);
+            ArrayList<ArrayList<Notification.Action>> systemSmartActionsBefore = new ArrayList<>(N);
             ArrayList<ArrayList<CharSequence>> smartRepliesBefore = new ArrayList<>(N);
             for (int i = 0; i < N; i++) {
                 final NotificationRecord r = mNotificationList.get(i);
@@ -5542,7 +5562,7 @@
                 snoozeCriteriaBefore.add(r.getSnoozeCriteria());
                 userSentimentBefore.add(r.getUserSentiment());
                 suppressVisuallyBefore.add(r.getSuppressedVisualEffects());
-                smartActionsBefore.add(r.getSmartActions());
+                systemSmartActionsBefore.add(r.getSystemGeneratedSmartActions());
                 smartRepliesBefore.add(r.getSmartReplies());
                 mRankingHelper.extractSignals(r);
             }
@@ -5559,7 +5579,8 @@
                         || !Objects.equals(userSentimentBefore.get(i), r.getUserSentiment())
                         || !Objects.equals(suppressVisuallyBefore.get(i),
                         r.getSuppressedVisualEffects())
-                        || !Objects.equals(smartActionsBefore.get(i), r.getSmartActions())
+                        || !Objects.equals(systemSmartActionsBefore.get(i),
+                                r.getSystemGeneratedSmartActions())
                         || !Objects.equals(smartRepliesBefore.get(i), r.getSmartReplies())) {
                     mHandler.scheduleSendRankingUpdate();
                     return;
@@ -6561,7 +6582,7 @@
         Bundle showBadge = new Bundle();
         Bundle userSentiment = new Bundle();
         Bundle hidden = new Bundle();
-        Bundle smartActions = new Bundle();
+        Bundle systemGeneratedSmartActions = new Bundle();
         Bundle smartReplies = new Bundle();
         Bundle audiblyAlerted = new Bundle();
         Bundle noisy = new Bundle();
@@ -6592,7 +6613,8 @@
             showBadge.putBoolean(key, record.canShowBadge());
             userSentiment.putInt(key, record.getUserSentiment());
             hidden.putBoolean(key, record.isHidden());
-            smartActions.putParcelableArrayList(key, record.getSmartActions());
+            systemGeneratedSmartActions.putParcelableArrayList(key,
+                    record.getSystemGeneratedSmartActions());
             smartReplies.putCharSequenceArrayList(key, record.getSmartReplies());
             audiblyAlerted.putBoolean(key, record.getAudiblyAlerted());
             noisy.putBoolean(key, record.getSound() != null || record.getVibration() != null);
@@ -6607,7 +6629,7 @@
         return new NotificationRankingUpdate(keysAr, interceptedKeysAr, visibilityOverrides,
                 suppressedVisualEffects, importanceAr, explanation, overrideGroupKeys,
                 channels, overridePeople, snoozeCriteria, showBadge, userSentiment, hidden,
-                smartActions, smartReplies, audiblyAlerted, noisy);
+                systemGeneratedSmartActions, smartReplies, audiblyAlerted, noisy);
     }
 
     boolean hasCompanionDevice(ManagedServiceInfo info) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index a11b03f..1a9257c 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -163,7 +163,11 @@
     private Light mLight;
     private String mGroupLogTag;
     private String mChannelIdLogTag;
-    private ArrayList<Notification.Action> mSmartActions;
+    /**
+     * This list contains system generated smart actions from NAS, app-generated smart actions are
+     * stored in Notification.actions marked as SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION.
+     */
+    private ArrayList<Notification.Action> mSystemGeneratedSmartActions;
     private ArrayList<CharSequence> mSmartReplies;
 
     private final List<Adjustment> mAdjustments;
@@ -653,10 +657,11 @@
                     }
                 }
                 if (signals.containsKey(Adjustment.KEY_SMART_ACTIONS)) {
-                    setSmartActions(signals.getParcelableArrayList(Adjustment.KEY_SMART_ACTIONS));
+                    setSystemGeneratedSmartActions(
+                            signals.getParcelableArrayList(Adjustment.KEY_SMART_ACTIONS));
                     MetricsLogger.action(getAdjustmentLogMaker()
                             .addTaggedData(MetricsEvent.ADJUSTMENT_KEY_SMART_ACTIONS,
-                                    getSmartActions().size()));
+                                    getSystemGeneratedSmartActions().size()));
                 }
                 if (signals.containsKey(Adjustment.KEY_SMART_REPLIES)) {
                     setSmartReplies(signals.getCharSequenceArrayList(Adjustment.KEY_SMART_REPLIES));
@@ -1132,12 +1137,13 @@
         mHasSeenSmartReplies = hasSeenSmartReplies;
     }
 
-    public void setSmartActions(ArrayList<Notification.Action> smartActions) {
-        mSmartActions = smartActions;
+    public void setSystemGeneratedSmartActions(
+            ArrayList<Notification.Action> systemGeneratedSmartActions) {
+        mSystemGeneratedSmartActions = systemGeneratedSmartActions;
     }
 
-    public ArrayList<Notification.Action> getSmartActions() {
-        return mSmartActions;
+    public ArrayList<Notification.Action> getSystemGeneratedSmartActions() {
+        return mSystemGeneratedSmartActions;
     }
 
     public void setSmartReplies(ArrayList<CharSequence> smartReplies) {
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 8fce5e3..fd65ebe 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -111,7 +111,6 @@
     // pkg => PackagePreferences
     private final ArrayMap<String, PackagePreferences> mRestoredWithoutUids = new ArrayMap<>();
 
-
     private final Context mContext;
     private final PackageManager mPm;
     private final RankingHandler mRankingHandler;
@@ -120,7 +119,6 @@
     private SparseBooleanArray mBadgingEnabled;
     private boolean mAreChannelsBypassingDnd;
 
-
     public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
             ZenModeHelper zenHelper) {
         mContext = context;
@@ -129,11 +127,7 @@
         mPm = pm;
 
         updateBadgingEnabled();
-
-        mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state &
-                NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
-        updateChannelsBypassingDnd();
-
+        syncChannelsBypassingDnd(mContext.getUserId());
     }
 
     public void readXml(XmlPullParser parser, boolean forRestore)
@@ -525,6 +519,7 @@
                 // but the system can
                 if (group.isBlocked() != oldGroup.isBlocked()) {
                     group.lockFields(NotificationChannelGroup.USER_LOCKED_BLOCKED_STATE);
+                    updateChannelsBypassingDnd(mContext.getUserId());
                 }
                 if (group.canOverlayApps() != oldGroup.canOverlayApps()) {
                     group.lockFields(NotificationChannelGroup.USER_LOCKED_ALLOW_APP_OVERLAY);
@@ -571,6 +566,7 @@
 
             // Apps are allowed to downgrade channel importance if the user has not changed any
             // fields on this channel yet.
+            final int previousExistingImportance = existing.getImportance();
             if (existing.getUserLockedFields() == 0 &&
                     channel.getImportance() < existing.getImportance()) {
                 existing.setImportance(channel.getImportance());
@@ -582,8 +578,9 @@
                 boolean bypassDnd = channel.canBypassDnd();
                 existing.setBypassDnd(bypassDnd);
 
-                if (bypassDnd != mAreChannelsBypassingDnd) {
-                    updateChannelsBypassingDnd();
+                if (bypassDnd != mAreChannelsBypassingDnd
+                        || previousExistingImportance != existing.getImportance()) {
+                    updateChannelsBypassingDnd(mContext.getUserId());
                 }
             }
 
@@ -613,7 +610,7 @@
 
         r.channels.put(channel.getId(), channel);
         if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
-            updateChannelsBypassingDnd();
+            updateChannelsBypassingDnd(mContext.getUserId());
         }
         MetricsLogger.action(getChannelLog(channel, pkg).setType(
                 com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
@@ -663,8 +660,9 @@
             MetricsLogger.action(getChannelLog(updatedChannel, pkg));
         }
 
-        if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd) {
-            updateChannelsBypassingDnd();
+        if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd
+                || channel.getImportance() != updatedChannel.getImportance()) {
+            updateChannelsBypassingDnd(mContext.getUserId());
         }
         updateConfig();
     }
@@ -701,7 +699,7 @@
             MetricsLogger.action(lm);
 
             if (mAreChannelsBypassingDnd && channel.canBypassDnd()) {
-                updateChannelsBypassingDnd();
+                updateChannelsBypassingDnd(mContext.getUserId());
             }
         }
     }
@@ -859,6 +857,27 @@
     }
 
     /**
+     * Gets all notification channels associated with the given pkg and userId that can bypass dnd
+     */
+    public ParceledListSlice<NotificationChannel> getNotificationChannelsBypassingDnd(String pkg,
+            int userId) {
+        List<NotificationChannel> channels = new ArrayList<>();
+        synchronized (mPackagePreferences) {
+            final PackagePreferences r = mPackagePreferences.get(
+                    packagePreferencesKey(pkg, userId));
+            // notifications from this package aren't blocked
+            if (r != null && r.importance != IMPORTANCE_NONE) {
+                for (NotificationChannel channel : r.channels.values()) {
+                    if (channelIsLive(r, channel) && channel.canBypassDnd()) {
+                        channels.add(channel);
+                    }
+                }
+            }
+        }
+        return new ParceledListSlice<>(channels);
+    }
+
+    /**
      * True for pre-O apps that only have the default channel, or pre O apps that have no
      * channels yet. This method will create the default channel for pre-O apps that don't have it.
      * Should never be true for O+ targeting apps, but that's enforced on boot/when an app
@@ -922,18 +941,62 @@
         return count;
     }
 
-    public void updateChannelsBypassingDnd() {
+    /**
+     * Returns the number of apps that have at least one notification channel that can bypass DND
+     * for given particular user
+     */
+    public int getAppsBypassingDndCount(int userId) {
+        int count = 0;
         synchronized (mPackagePreferences) {
-            final int numPackagePreferencess = mPackagePreferences.size();
-            for (int PackagePreferencesIndex = 0; PackagePreferencesIndex < numPackagePreferencess;
-                    PackagePreferencesIndex++) {
-                final PackagePreferences r = mPackagePreferences.valueAt(PackagePreferencesIndex);
-                final int numChannels = r.channels.size();
+            final int numPackagePreferences = mPackagePreferences.size();
+            for (int i = 0; i < numPackagePreferences; i++) {
+                final PackagePreferences r = mPackagePreferences.valueAt(i);
+                // Package isn't associated with this userId or notifications from this package are
+                // blocked
+                if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) {
+                    continue;
+                }
 
-                for (int channelIndex = 0; channelIndex < numChannels; channelIndex++) {
-                    NotificationChannel channel = r.channels.valueAt(channelIndex);
-                    if (!channel.isDeleted() && channel.canBypassDnd()) {
-                        // If any channel bypasses DND, synchronize state and return early.
+                for (NotificationChannel channel : r.channels.values()) {
+                    if (channelIsLive(r, channel) && channel.canBypassDnd()) {
+                        count++;
+                        break;
+                    }
+                }
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Syncs {@link #mAreChannelsBypassingDnd} with the user's notification policy before
+     * updating
+     * @param userId
+     */
+    private void syncChannelsBypassingDnd(int userId) {
+        mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state
+                & NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
+        updateChannelsBypassingDnd(userId);
+    }
+
+    /**
+     * Updates the user's NotificationPolicy based on whether the given userId
+     * has channels bypassing DND
+     * @param userId
+     */
+    private void updateChannelsBypassingDnd(int userId) {
+        synchronized (mPackagePreferences) {
+            final int numPackagePreferences = mPackagePreferences.size();
+            for (int i = 0; i < numPackagePreferences; i++) {
+                final PackagePreferences r = mPackagePreferences.valueAt(i);
+                // Package isn't associated with this userId or notifications from this package are
+                // blocked
+                if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) {
+                    continue;
+                }
+
+                for (NotificationChannel channel : r.channels.values()) {
+                    if (channelIsLive(r, channel) && channel.canBypassDnd()) {
                         if (!mAreChannelsBypassingDnd) {
                             mAreChannelsBypassingDnd = true;
                             updateZenPolicy(true);
@@ -943,7 +1006,6 @@
                 }
             }
         }
-
         // If no channels bypass DND, update the zen policy once to disable DND bypass.
         if (mAreChannelsBypassingDnd) {
             mAreChannelsBypassingDnd = false;
@@ -951,6 +1013,22 @@
         }
     }
 
+    private boolean channelIsLive(PackagePreferences pkgPref, NotificationChannel channel) {
+        // Channel is in a group that's blocked
+        if (!TextUtils.isEmpty(channel.getGroup())) {
+            if (pkgPref.groups.get(channel.getGroup()).isBlocked()) {
+                return false;
+            }
+        }
+
+        // Channel is deleted or is blocked
+        if (channel.isDeleted() || channel.getImportance() == IMPORTANCE_NONE) {
+            return false;
+        }
+
+        return true;
+    }
+
     public void updateZenPolicy(boolean areChannelsBypassingDnd) {
         NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
         mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy(
@@ -1329,6 +1407,20 @@
         return packageChannels;
     }
 
+    /**
+     * Called when user switches
+     */
+    public void onUserSwitched(int userId) {
+        syncChannelsBypassingDnd(userId);
+    }
+
+    /**
+     * Called when user is unlocked
+     */
+    public void onUserUnlocked(int userId) {
+        syncChannelsBypassingDnd(userId);
+    }
+
     public void onUserRemoved(int userId) {
         synchronized (mPackagePreferences) {
             int N = mPackagePreferences.size();
diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java
index 807c343..731e6bc 100644
--- a/services/core/java/com/android/server/om/IdmapManager.java
+++ b/services/core/java/com/android/server/om/IdmapManager.java
@@ -16,36 +16,46 @@
 
 package com.android.server.om;
 
+import static android.content.Context.IDMAP_SERVICE;
+import static android.text.format.DateUtils.SECOND_IN_MILLIS;
+
 import static com.android.server.om.OverlayManagerService.DEBUG;
 import static com.android.server.om.OverlayManagerService.TAG;
 
 import android.annotation.NonNull;
 import android.content.om.OverlayInfo;
 import android.content.pm.PackageInfo;
+import android.os.IBinder;
+import android.os.IIdmap2;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.util.Slog;
 
-import com.android.server.pm.Installer.InstallerException;
+import com.android.internal.os.BackgroundThread;
 import com.android.server.pm.Installer;
 
-import java.io.DataInputStream;
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
 
 /**
  * Handle the creation and deletion of idmap files.
  *
  * The actual work is performed by the idmap binary, launched through Installer
- * and installd.
+ * and installd (or idmap2).
  *
  * Note: this class is subclassed in the OMS unit tests, and hence not marked as final.
  */
 class IdmapManager {
+    private static final boolean FEATURE_FLAG_IDMAP2 = false;
+
     private final Installer mInstaller;
+    private IIdmap2 mIdmap2Service;
 
     IdmapManager(final Installer installer) {
         mInstaller = installer;
+        if (FEATURE_FLAG_IDMAP2) {
+            connectToIdmap2d();
+        }
     }
 
     boolean createIdmap(@NonNull final PackageInfo targetPackage,
@@ -59,8 +69,12 @@
         final String targetPath = targetPackage.applicationInfo.getBaseCodePath();
         final String overlayPath = overlayPackage.applicationInfo.getBaseCodePath();
         try {
-            mInstaller.idmap(targetPath, overlayPath, sharedGid);
-        } catch (InstallerException e) {
+            if (FEATURE_FLAG_IDMAP2) {
+                mIdmap2Service.createIdmap(targetPath, overlayPath, userId);
+            } else {
+                mInstaller.idmap(targetPath, overlayPath, sharedGid);
+            }
+        } catch (Exception e) {
             Slog.w(TAG, "failed to generate idmap for " + targetPath + " and "
                     + overlayPath + ": " + e.getMessage());
             return false;
@@ -69,13 +83,16 @@
     }
 
     boolean removeIdmap(@NonNull final OverlayInfo oi, final int userId) {
-        // unused userId: see comment in OverlayManagerServiceImpl.removeIdmapIfPossible
         if (DEBUG) {
             Slog.d(TAG, "remove idmap for " + oi.baseCodePath);
         }
         try {
-            mInstaller.removeIdmap(oi.baseCodePath);
-        } catch (InstallerException e) {
+            if (FEATURE_FLAG_IDMAP2) {
+                mIdmap2Service.removeIdmap(oi.baseCodePath, userId);
+            } else {
+                mInstaller.removeIdmap(oi.baseCodePath);
+            }
+        } catch (Exception e) {
             Slog.w(TAG, "failed to remove idmap for " + oi.baseCodePath + ": " + e.getMessage());
             return false;
         }
@@ -83,19 +100,58 @@
     }
 
     boolean idmapExists(@NonNull final OverlayInfo oi) {
-        // unused OverlayInfo.userId: see comment in OverlayManagerServiceImpl.removeIdmapIfPossible
-        return new File(getIdmapPath(oi.baseCodePath)).isFile();
+        return new File(getIdmapPath(oi.baseCodePath, oi.userId)).isFile();
     }
 
     boolean idmapExists(@NonNull final PackageInfo overlayPackage, final int userId) {
-        // unused userId: see comment in OverlayManagerServiceImpl.removeIdmapIfPossible
-        return new File(getIdmapPath(overlayPackage.applicationInfo.getBaseCodePath())).isFile();
+        return new File(getIdmapPath(overlayPackage.applicationInfo.getBaseCodePath(), userId))
+            .isFile();
     }
 
-    private String getIdmapPath(@NonNull final String baseCodePath) {
-        final StringBuilder sb = new StringBuilder("/data/resource-cache/");
-        sb.append(baseCodePath.substring(1).replace('/', '@'));
-        sb.append("@idmap");
-        return sb.toString();
+    private @NonNull String getIdmapPath(@NonNull final String overlayPackagePath,
+            final int userId) {
+        if (FEATURE_FLAG_IDMAP2) {
+            try {
+                return mIdmap2Service.getIdmapPath(overlayPackagePath, userId);
+            } catch (Exception e) {
+                Slog.w(TAG, "failed to get idmap path for " + overlayPackagePath + ": "
+                        + e.getMessage());
+                return "";
+            }
+        } else {
+            final StringBuilder sb = new StringBuilder("/data/resource-cache/");
+            sb.append(overlayPackagePath.substring(1).replace('/', '@'));
+            sb.append("@idmap");
+            return sb.toString();
+        }
+    }
+
+    private void connectToIdmap2d() {
+        IBinder binder = ServiceManager.getService(IDMAP_SERVICE);
+        if (binder != null) {
+            try {
+                binder.linkToDeath(new IBinder.DeathRecipient() {
+                    @Override
+                    public void binderDied() {
+                        Slog.w(TAG, "service '" + IDMAP_SERVICE + "' died; reconnecting...");
+                        connectToIdmap2d();
+                    }
+
+                }, 0);
+            } catch (RemoteException e) {
+                binder = null;
+            }
+        }
+        if (binder != null) {
+            mIdmap2Service = IIdmap2.Stub.asInterface(binder);
+            if (DEBUG) {
+                Slog.d(TAG, "service '" + IDMAP_SERVICE + "' connected");
+            }
+        } else {
+            Slog.w(TAG, "service '" + IDMAP_SERVICE + "' not found; trying again...");
+            BackgroundThread.getHandler().postDelayed(() -> {
+                connectToIdmap2d();
+            }, SECOND_IN_MILLIS);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index 36bf83d..572d368 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -179,19 +179,13 @@
 
     List<OverlayInfo> getOverlaysForTarget(@NonNull final String targetPackageName,
             final int userId) {
-        // Static RROs targeting "android" are loaded from AssetManager, and so they should be
-        // ignored in OverlayManagerService.
         return selectWhereTarget(targetPackageName, userId)
-                .filter((i) -> !(i.isStatic() && "android".equals(i.getTargetPackageName())))
                 .map(SettingsItem::getOverlayInfo)
                 .collect(Collectors.toList());
     }
 
     ArrayMap<String, List<OverlayInfo>> getOverlaysForUser(final int userId) {
-        // Static RROs targeting "android" are loaded from AssetManager, and so they should be
-        // ignored in OverlayManagerService.
         return selectWhereUser(userId)
-                .filter((i) -> !(i.isStatic() && "android".equals(i.getTargetPackageName())))
                 .map(SettingsItem::getOverlayInfo)
                 .collect(Collectors.groupingBy(info -> info.targetPackageName, ArrayMap::new,
                         Collectors.toList()));
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 275f3dc..b490381 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -362,7 +362,7 @@
         }
 
         private static boolean shouldShowHiddenApp(ApplicationInfo appInfo) {
-            if (appInfo.isSystemApp() || appInfo.isUpdatedSystemApp()) {
+            if (appInfo == null || appInfo.isSystemApp() || appInfo.isUpdatedSystemApp()) {
                 return false;
             }
             return true;
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/policy/keyguard/KeyguardStateMonitor.java b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
index a55b49f..f78d263 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
@@ -20,7 +20,7 @@
 import android.content.Context;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.security.IKeystoreService;
+import android.security.keystore.IKeystoreService;
 import android.util.Slog;
 
 import com.android.internal.policy.IKeyguardService;
diff --git a/services/core/java/com/android/server/role/RemoteRoleControllerService.java b/services/core/java/com/android/server/role/RemoteRoleControllerService.java
index b670291..7d34270 100644
--- a/services/core/java/com/android/server/role/RemoteRoleControllerService.java
+++ b/services/core/java/com/android/server/role/RemoteRoleControllerService.java
@@ -16,10 +16,10 @@
 
 package com.android.server.role;
 
-import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
 import android.app.role.IRoleManagerCallback;
 import android.app.role.RoleManagerCallback;
 import android.content.ComponentName;
@@ -34,6 +34,7 @@
 import android.rolecontrollerservice.RoleControllerService;
 import android.util.Slog;
 
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.function.pooled.PooledLambda;
 
 import java.util.ArrayDeque;
@@ -44,9 +45,13 @@
  */
 public class RemoteRoleControllerService {
 
+    static final boolean DEBUG = false;
     private static final String LOG_TAG = RemoteRoleControllerService.class.getSimpleName();
 
     @NonNull
+    private static final Handler sCallbackHandler = BackgroundThread.getHandler();
+
+    @NonNull
     private final Connection mConnection;
 
     public RemoteRoleControllerService(@UserIdInt int userId, @NonNull Context context) {
@@ -87,6 +92,16 @@
                 service.onClearRoleHolders(roleName, callbackDelegate), callback));
     }
 
+    /**
+     * Performs granting of default roles and permissions and appops
+     *
+     * @see RoleControllerService#onGrantDefaultRoles(RoleManagerCallback)
+     */
+    public void onGrantDefaultRoles(@NonNull IRoleManagerCallback callback) {
+        mConnection.enqueueCall(
+                new Connection.Call(IRoleControllerService::onGrantDefaultRoles, callback));
+    }
+
     private static final class Connection implements ServiceConnection {
 
         private static final long UNBIND_DELAY_MILLIS = 15 * 1000;
@@ -106,9 +121,6 @@
         private final Queue<Call> mPendingCalls = new ArrayDeque<>();
 
         @NonNull
-        private final Handler mMainHandler = Handler.getMain();
-
-        @NonNull
         private final Runnable mUnbindRunnable = this::unbind;
 
         Connection(@UserIdInt int userId, @NonNull Context context) {
@@ -116,14 +128,14 @@
             mContext = context;
         }
 
-        @MainThread
         @Override
+        @WorkerThread
         public void onServiceConnected(@NonNull ComponentName name, @NonNull IBinder service) {
             mService = IRoleControllerService.Stub.asInterface(service);
             executePendingCalls();
         }
 
-        @MainThread
+        @WorkerThread
         private void executePendingCalls() {
             while (!mPendingCalls.isEmpty()) {
                 Call call = mPendingCalls.poll();
@@ -132,26 +144,33 @@
             scheduleUnbind();
         }
 
-        @MainThread
         @Override
+        @WorkerThread
         public void onServiceDisconnected(@NonNull ComponentName name) {
             mService = null;
         }
 
-        @MainThread
         @Override
+        @WorkerThread
         public void onBindingDied(@NonNull ComponentName name) {
             unbind();
         }
 
         public void enqueueCall(@NonNull Call call) {
-            mMainHandler.post(PooledLambda.obtainRunnable(this::executeCall, call));
+            if (DEBUG) {
+                Slog.i(LOG_TAG, "Enqueue " + call);
+            }
+            sCallbackHandler.executeOrSendMessage(PooledLambda.obtainMessage(
+                    Connection::executeCall, this, call));
         }
 
-        @MainThread
+        @WorkerThread
         private void executeCall(@NonNull Call call) {
             ensureBound();
             if (mService == null) {
+                if (DEBUG) {
+                    Slog.i(LOG_TAG, "Delaying until service connected: " + call);
+                }
                 mPendingCalls.offer(call);
                 return;
             }
@@ -159,24 +178,28 @@
             scheduleUnbind();
         }
 
-        @MainThread
+        @WorkerThread
         private void ensureBound() {
-            mMainHandler.removeCallbacks(mUnbindRunnable);
+            sCallbackHandler.removeCallbacks(mUnbindRunnable);
             if (!mBound) {
                 Intent intent = new Intent(RoleControllerService.SERVICE_INTERFACE);
                 intent.setPackage(mContext.getPackageManager()
                         .getPermissionControllerPackageName());
+                // Use direct handler to ensure onServiceConnected callback happens in the same
+                // call frame, as required by onGrantDefaultRoles
+                //
+                // Note that as a result, onServiceConnected may happen not on main thread!
                 mBound = mContext.bindServiceAsUser(intent, this, Context.BIND_AUTO_CREATE,
-                        UserHandle.of(mUserId));
+                        sCallbackHandler, UserHandle.of(mUserId));
             }
         }
 
         private void scheduleUnbind() {
-            mMainHandler.removeCallbacks(mUnbindRunnable);
-            mMainHandler.postDelayed(mUnbindRunnable, UNBIND_DELAY_MILLIS);
+            sCallbackHandler.removeCallbacks(mUnbindRunnable);
+            sCallbackHandler.postDelayed(mUnbindRunnable, UNBIND_DELAY_MILLIS);
         }
 
-        @MainThread
+        @WorkerThread
         private void unbind() {
             if (mBound) {
                 mService = null;
@@ -196,9 +219,6 @@
             private final IRoleManagerCallback mCallback;
 
             @NonNull
-            private final Handler mMainHandler = Handler.getMain();
-
-            @NonNull
             private final Runnable mTimeoutRunnable = () -> notifyCallback(false);
 
             private boolean mCallbackNotified;
@@ -209,10 +229,13 @@
                 mCallback = callback;
             }
 
-            @MainThread
+            @WorkerThread
             public void execute(IRoleControllerService service) {
+                if (DEBUG) {
+                    Slog.i(LOG_TAG, "Executing " + this);
+                }
                 try {
-                    mMainHandler.postDelayed(mTimeoutRunnable, TIMEOUT_MILLIS);
+                    sCallbackHandler.postDelayed(mTimeoutRunnable, TIMEOUT_MILLIS);
                     mCallExecutor.execute(service, new CallbackDelegate());
                 } catch (RemoteException e) {
                     Slog.e(LOG_TAG, "Error calling RoleControllerService", e);
@@ -220,13 +243,13 @@
                 }
             }
 
-            @MainThread
+            @WorkerThread
             private void notifyCallback(boolean success) {
                 if (mCallbackNotified) {
                     return;
                 }
                 mCallbackNotified = true;
-                mMainHandler.removeCallbacks(mTimeoutRunnable);
+                sCallbackHandler.removeCallbacks(mTimeoutRunnable);
                 try {
                     if (success) {
                         mCallback.onSuccess();
@@ -239,10 +262,15 @@
                 }
             }
 
+            @Override
+            public String toString() {
+                return "Call with callback: " + mCallback;
+            }
+
             @FunctionalInterface
             public interface CallExecutor {
 
-                @MainThread
+                @WorkerThread
                 void execute(IRoleControllerService service, IRoleManagerCallback callbackDelegate)
                         throws RemoteException;
             }
@@ -251,13 +279,14 @@
 
                 @Override
                 public void onSuccess() throws RemoteException {
-                    mMainHandler.post(PooledLambda.obtainRunnable(Call.this::notifyCallback, true));
+                    sCallbackHandler.sendMessage(PooledLambda.obtainMessage(
+                            Call::notifyCallback, Call.this, true));
                 }
 
                 @Override
                 public void onFailure() throws RemoteException {
-                    mMainHandler.post(PooledLambda.obtainRunnable(Call.this::notifyCallback,
-                            false));
+                    sCallbackHandler.sendMessage(PooledLambda.obtainMessage(
+                            Call::notifyCallback, Call.this, false));
                 }
             }
         }
diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java
index ded075d..d01e762 100644
--- a/services/core/java/com/android/server/role/RoleManagerService.java
+++ b/services/core/java/com/android/server/role/RoleManagerService.java
@@ -45,6 +45,10 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 /**
  * Service for role management.
@@ -105,17 +109,37 @@
     public void onStart() {
         publishBinderService(Context.ROLE_SERVICE, new Stub());
         //TODO add watch for new user creation and run default grants for them
+        //TODO add package update watch to detect PermissionController upgrade and run def. grants
     }
 
     @Override
     public void onStartUser(@UserIdInt int userId) {
         synchronized (mLock) {
             //TODO only call into PermissionController if it or system upgreaded (for boot time)
-            // (add package changes watch;
-            //     we can detect upgrade using build fingerprint and app version)
             getUserStateLocked(userId);
-            //TODO call permission grant policy here
+        }
+        //TODO consider calling grants only when certain conditions are met
+        // such as OS or PermissionController upgrade
+        if (RemoteRoleControllerService.DEBUG) {
             Slog.i(LOG_TAG, "Granting default permissions...");
+            CompletableFuture<Void> result = new CompletableFuture<>();
+            getControllerService(userId).onGrantDefaultRoles(
+                    new IRoleManagerCallback.Stub() {
+                        @Override
+                        public void onSuccess() {
+                            result.complete(null);
+                        }
+
+                        @Override
+                        public void onFailure() {
+                            result.completeExceptionally(new RuntimeException());
+                        }
+                    });
+            try {
+                result.get(5, TimeUnit.SECONDS);
+            } catch (InterruptedException | ExecutionException | TimeoutException e) {
+                Slog.e(LOG_TAG, "Failed to grant defaults for user " + userId, e);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 01d02d6..3050409 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -47,6 +47,7 @@
 import android.net.NetworkStats;
 import android.net.wifi.IWifiManager;
 import android.net.wifi.WifiActivityEnergyInfo;
+import android.os.BatteryStats;
 import android.os.BatteryStatsInternal;
 import android.os.Binder;
 import android.os.Bundle;
@@ -87,6 +88,8 @@
 import com.android.internal.app.procstats.IProcessStats;
 import com.android.internal.app.procstats.ProcessStats;
 import com.android.internal.net.NetworkStatsFactory;
+import com.android.internal.os.BatterySipper;
+import com.android.internal.os.BatteryStatsHelper;
 import com.android.internal.os.BinderCallsStats.ExportedCallStat;
 import com.android.internal.os.KernelCpuSpeedReader;
 import com.android.internal.os.KernelCpuThreadReader;
@@ -205,6 +208,10 @@
     @Nullable
     private final KernelCpuThreadReader mKernelCpuThreadReader;
 
+    private BatteryStatsHelper mBatteryStatsHelper = null;
+    private static final int MAX_BATTERY_STATS_HELPER_FREQUENCY_MS = 1000;
+    private long mBatteryStatsHelperTimestampMs = -MAX_BATTERY_STATS_HELPER_FREQUENCY_MS;
+
     private static IThermalService sThermalService;
     private File mBaseDir =
             new File(SystemServiceManager.ensureSystemDir(), "stats_companion");
@@ -366,6 +373,8 @@
         List<Integer> uids = new ArrayList<>();
         List<Long> versions = new ArrayList<>();
         List<String> apps = new ArrayList<>();
+        List<String> versionStrings = new ArrayList<>();
+        List<String> installers = new ArrayList<>();
 
         // Add in all the apps for every user/profile.
         for (UserInfo profile : users) {
@@ -373,14 +382,24 @@
                     pm.getInstalledPackagesAsUser(PackageManager.MATCH_KNOWN_PACKAGES, profile.id);
             for (int j = 0; j < pi.size(); j++) {
                 if (pi.get(j).applicationInfo != null) {
+                    String installer;
+                    try {
+                        installer = pm.getInstallerPackageName(pi.get(j).packageName);
+                    } catch (IllegalArgumentException e) {
+                        installer = "";
+                    }
+                    installers.add(installer == null ? "" : installer);
                     uids.add(pi.get(j).applicationInfo.uid);
                     versions.add(pi.get(j).getLongVersionCode());
+                    versionStrings.add(pi.get(j).versionName);
                     apps.add(pi.get(j).packageName);
                 }
             }
         }
-        sStatsd.informAllUidData(toIntArray(uids), toLongArray(versions), apps.toArray(new
-                String[apps.size()]));
+        sStatsd.informAllUidData(toIntArray(uids), toLongArray(versions),
+                versionStrings.toArray(new String[versionStrings.size()]),
+                apps.toArray(new String[apps.size()]),
+                installers.toArray(new String[installers.size()]));
         if (DEBUG) {
             Slog.d(TAG, "Sent data for " + uids.size() + " apps");
         }
@@ -422,7 +441,14 @@
                         int uid = b.getInt(Intent.EXTRA_UID);
                         String app = intent.getData().getSchemeSpecificPart();
                         PackageInfo pi = pm.getPackageInfo(app, PackageManager.MATCH_ANY_USER);
-                        sStatsd.informOnePackage(app, uid, pi.getLongVersionCode());
+                        String installer;
+                        try {
+                            installer = pm.getInstallerPackageName(app);
+                        } catch (IllegalArgumentException e) {
+                            installer = "";
+                        }
+                        sStatsd.informOnePackage(app, uid, pi.getLongVersionCode(), pi.versionName,
+                                installer == null ? "" : installer);
                     }
                 } catch (Exception e) {
                     Slog.w(TAG, "Failed to inform statsd of an app update", e);
@@ -1430,6 +1456,73 @@
         pulledData.add(e);
     }
 
+    private BatteryStatsHelper getBatteryStatsHelper() {
+        if (mBatteryStatsHelper == null) {
+            final long callingToken = Binder.clearCallingIdentity();
+            try {
+                // clearCallingIdentity required for BatteryStatsHelper.checkWifiOnly().
+                mBatteryStatsHelper = new BatteryStatsHelper(mContext, false);
+            } finally {
+                Binder.restoreCallingIdentity(callingToken);
+            }
+            mBatteryStatsHelper.create((Bundle) null);
+        }
+        long currentTime = SystemClock.elapsedRealtime();
+        if (currentTime - mBatteryStatsHelperTimestampMs >= MAX_BATTERY_STATS_HELPER_FREQUENCY_MS) {
+            // Load BatteryStats and do all the calculations.
+            mBatteryStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.USER_ALL);
+            // Calculations are done so we don't need to save the raw BatteryStats data in RAM.
+            mBatteryStatsHelper.clearStats();
+            mBatteryStatsHelperTimestampMs = currentTime;
+        }
+        return mBatteryStatsHelper;
+    }
+
+    private void pullDeviceCalculatedPowerUse(int tagId,
+            long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
+        BatteryStatsHelper bsHelper = getBatteryStatsHelper();
+        StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+        e.writeFloat((float) bsHelper.getComputedPower());
+        pulledData.add(e);
+    }
+
+    private void pullDeviceCalculatedPowerBlameUid(int tagId,
+            long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
+        final List<BatterySipper> sippers = getBatteryStatsHelper().getUsageList();
+        if (sippers == null) {
+            return;
+        }
+        for (BatterySipper bs : sippers) {
+            if (bs.drainType != bs.drainType.APP) {
+                continue;
+            }
+            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+            e.writeInt(bs.uidObj.getUid());
+            e.writeFloat((float) bs.totalPowerMah);
+            pulledData.add(e);
+        }
+    }
+
+    private void pullDeviceCalculatedPowerBlameOther(int tagId,
+            long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
+        final List<BatterySipper> sippers = getBatteryStatsHelper().getUsageList();
+        if (sippers == null) {
+            return;
+        }
+        for (BatterySipper bs : sippers) {
+            if (bs.drainType == bs.drainType.APP) {
+                continue; // This is a separate atom; see pullDeviceCalculatedPowerBlameUid().
+            }
+            if (bs.drainType == bs.drainType.USER) {
+                continue; // This is not supported. We purposefully calculate over USER_ALL.
+            }
+            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+            e.writeInt(bs.drainType.ordinal());
+            e.writeFloat((float) bs.totalPowerMah);
+            pulledData.add(e);
+        }
+    }
+
     private void pullDiskIo(int tagId, long elapsedNanos, final long wallClockNanos,
             List<StatsLogEventWrapper> pulledData) {
         mStoragedUidIoStatsReader.readAbsolute((uid, fgCharsRead, fgCharsWrite, fgBytesRead,
@@ -1655,6 +1748,18 @@
                 pullCpuTimePerThreadFreq(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+            case StatsLog.DEVICE_CALCULATED_POWER_USE: {
+                pullDeviceCalculatedPowerUse(tagId, elapsedNanos, wallClockNanos, ret);
+                break;
+            }
+            case StatsLog.DEVICE_CALCULATED_POWER_BLAME_UID: {
+                pullDeviceCalculatedPowerBlameUid(tagId, elapsedNanos, wallClockNanos, ret);
+                break;
+            }
+            case StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER: {
+                pullDeviceCalculatedPowerBlameOther(tagId, elapsedNanos, wallClockNanos, ret);
+                break;
+            }
             default:
                 Slog.w(TAG, "No such tagId data as " + tagId);
                 return null;
diff --git a/services/core/java/com/android/server/timezone/RulesManagerService.java b/services/core/java/com/android/server/timezone/RulesManagerService.java
index 23c4a33..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/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 61e1414..1c08d03 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -383,7 +383,7 @@
             return;
         }
 
-        if (launchedActivity != null && launchedActivity.nowVisible) {
+        if (launchedActivity != null && launchedActivity.mDrawn) {
             // Launched activity is already visible. We cannot measure windows drawn delay.
             reset(true /* abort */, info, "launched activity already visible");
             return;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 23f8125..c43e64e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -20,6 +20,7 @@
 import static android.app.ActivityManager.TaskDescription.ATTR_TASKDESCRIPTION_PREFIX;
 import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
 import static android.app.ActivityTaskManager.INVALID_STACK_ID;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
 import static android.app.WaitResult.INVALID_DELAY;
@@ -75,6 +76,25 @@
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
 
+import static com.android.server.am.ActivityRecordProto.CONFIGURATION_CONTAINER;
+import static com.android.server.am.ActivityRecordProto.FRONT_OF_TASK;
+import static com.android.server.am.ActivityRecordProto.IDENTIFIER;
+import static com.android.server.am.ActivityRecordProto.PROC_ID;
+import static com.android.server.am.ActivityRecordProto.STATE;
+import static com.android.server.am.ActivityRecordProto.TRANSLUCENT;
+import static com.android.server.am.ActivityRecordProto.VISIBLE;
+import static com.android.server.am.EventLogTags.AM_RELAUNCH_ACTIVITY;
+import static com.android.server.am.EventLogTags.AM_RELAUNCH_RESUME_ACTIVITY;
+import static com.android.server.wm.ActivityStack.ActivityState.INITIALIZING;
+import static com.android.server.wm.ActivityStack.ActivityState.PAUSED;
+import static com.android.server.wm.ActivityStack.ActivityState.PAUSING;
+import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
+import static com.android.server.wm.ActivityStack.ActivityState.STOPPED;
+import static com.android.server.wm.ActivityStack.ActivityState.STOPPING;
+import static com.android.server.wm.ActivityStack.LAUNCH_TICK;
+import static com.android.server.wm.ActivityStack.LAUNCH_TICK_MSG;
+import static com.android.server.wm.ActivityStack.PAUSE_TIMEOUT_MSG;
+import static com.android.server.wm.ActivityStack.STOP_TIMEOUT_MSG;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONFIGURATION;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_FOCUS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SAVED_STATE;
@@ -89,34 +109,14 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_VISIBILITY;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.am.ActivityRecordProto.CONFIGURATION_CONTAINER;
-import static com.android.server.am.ActivityRecordProto.FRONT_OF_TASK;
-import static com.android.server.am.ActivityRecordProto.IDENTIFIER;
-import static com.android.server.am.ActivityRecordProto.PROC_ID;
-import static com.android.server.am.ActivityRecordProto.STATE;
-import static com.android.server.am.ActivityRecordProto.TRANSLUCENT;
-import static com.android.server.am.ActivityRecordProto.VISIBLE;
-import static com.android.server.wm.ActivityStack.ActivityState.INITIALIZING;
-import static com.android.server.wm.ActivityStack.ActivityState.PAUSED;
-import static com.android.server.wm.ActivityStack.ActivityState.PAUSING;
-import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
-import static com.android.server.wm.ActivityStack.ActivityState.STOPPED;
-import static com.android.server.wm.ActivityStack.ActivityState.STOPPING;
-import static com.android.server.wm.ActivityStack.LAUNCH_TICK;
-import static com.android.server.wm.ActivityStack.LAUNCH_TICK_MSG;
-import static com.android.server.wm.ActivityStack.PAUSE_TIMEOUT_MSG;
-import static com.android.server.wm.ActivityStack.STOP_TIMEOUT_MSG;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
-import static com.android.server.am.EventLogTags.AM_RELAUNCH_ACTIVITY;
-import static com.android.server.am.EventLogTags.AM_RELAUNCH_RESUME_ACTIVITY;
-import static com.android.server.wm.TaskPersister.DEBUG;
-import static com.android.server.wm.TaskPersister.IMAGE_EXTENSION;
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static com.android.server.wm.IdentifierProto.HASH_CODE;
 import static com.android.server.wm.IdentifierProto.TITLE;
 import static com.android.server.wm.IdentifierProto.USER_ID;
+import static com.android.server.wm.TaskPersister.DEBUG;
+import static com.android.server.wm.TaskPersister.IMAGE_EXTENSION;
 
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
 import static org.xmlpull.v1.XmlPullParser.END_TAG;
@@ -179,9 +179,9 @@
 import com.android.server.AttributeCache.Entry;
 import com.android.server.am.AppTimeTracker;
 import com.android.server.am.PendingIntentRecord;
+import com.android.server.uri.UriPermissionOwner;
 import com.android.server.wm.ActivityMetricsLogger.WindowingModeTransitionInfoSnapshot;
 import com.android.server.wm.ActivityStack.ActivityState;
-import com.android.server.uri.UriPermissionOwner;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -303,6 +303,7 @@
                                         // process that it is hidden.
     boolean sleeping;       // have we told the activity to sleep?
     boolean nowVisible;     // is this activity's window visible?
+    boolean mDrawn;          // is this activity's window drawn?
     boolean mClientVisibilityDeferred;// was the visibility change message to client deferred?
     boolean idle;           // has the activity gone idle?
     boolean hasBeenLaunched;// has this activity ever been launched?
@@ -869,6 +870,7 @@
         inHistory = false;
         visible = false;
         nowVisible = false;
+        mDrawn = false;
         idle = false;
         hasBeenLaunched = false;
         mStackSupervisor = supervisor;
@@ -1944,8 +1946,12 @@
     }
 
     @Override
-    public void onWindowsDrawn(long timestamp) {
+    public void onWindowsDrawn(boolean drawn, long timestamp) {
         synchronized (service.mGlobalLock) {
+            mDrawn = drawn;
+            if (!drawn) {
+                return;
+            }
             final WindowingModeTransitionInfoSnapshot info = mStackSupervisor
                     .getActivityMetricsLogger().notifyWindowsDrawn(getWindowingMode(), timestamp);
             final int windowsDrawnDelayMs = info != null ? info.windowsDrawnDelayMs : INVALID_DELAY;
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 97eaafc..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;
@@ -145,6 +146,7 @@
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.power.V1_0.PowerHint;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Debug;
 import android.os.FactoryTest;
@@ -835,9 +837,13 @@
         }
 
         final boolean supportMultipleInstance = homeInfo.launchMode != LAUNCH_SINGLE_TASK
-                && homeInfo.launchMode != LAUNCH_SINGLE_INSTANCE;
+                && homeInfo.launchMode != LAUNCH_SINGLE_INSTANCE
+                && homeInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.Q;
         if (!supportMultipleInstance) {
-            // Can't launch home on other displays if it requested to be single instance.
+            // Can't launch home on other displays if it requested to be single instance. Also we
+            // don't allow home applications that target before Q to have multiple home activity
+            // instances because they may not be expected to have multiple home scenario and
+            // haven't explicitly request for single instance.
             return false;
         }
 
@@ -2400,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 */);
     }
 
     /**
@@ -2409,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;
@@ -2422,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
@@ -2441,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;
                 }
@@ -2477,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;
                 }
@@ -2518,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(
@@ -2547,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*/);
         }
@@ -2558,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()...
@@ -2639,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/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index 3cbb2577..bd1460a 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -82,6 +82,7 @@
     private final class H extends Handler {
         public static final int NOTIFY_WINDOWS_DRAWN = 1;
         public static final int NOTIFY_STARTING_WINDOW_DRAWN = 2;
+        public static final int NOTIFY_WINDOWS_NOTDRAWN = 3;
 
         public H(Looper looper) {
             super(looper);
@@ -96,16 +97,24 @@
                     }
                     if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting drawn in "
                             + AppWindowContainerController.this.mToken);
-                    mListener.onWindowsDrawn(msg.getWhen());
+                    mListener.onWindowsDrawn(true /* drawn */, msg.getWhen());
                     break;
                 case NOTIFY_STARTING_WINDOW_DRAWN:
                     if (mListener == null) {
                         return;
                     }
-                    if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting drawn in "
+                    if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting starting window drawn in "
                             + AppWindowContainerController.this.mToken);
                     mListener.onStartingWindowDrawn(msg.getWhen());
                     break;
+                case NOTIFY_WINDOWS_NOTDRAWN:
+                    if (mListener == null) {
+                        return;
+                    }
+                    if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting not drawn in "
+                            + AppWindowContainerController.this.mToken);
+                    mListener.onWindowsDrawn(false /* drawn */, msg.getWhen());
+                    break;
                 default:
                     break;
             }
@@ -762,6 +771,10 @@
         mHandler.sendMessage(mHandler.obtainMessage(H.NOTIFY_WINDOWS_DRAWN));
     }
 
+    void reportWindowsNotDrawn() {
+        mHandler.sendMessage(mHandler.obtainMessage(H.NOTIFY_WINDOWS_NOTDRAWN));
+    }
+
     void reportWindowsVisible() {
         mHandler.post(mOnWindowsVisible);
     }
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerListener.java b/services/core/java/com/android/server/wm/AppWindowContainerListener.java
index 8a39a74..ad27669 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerListener.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerListener.java
@@ -18,8 +18,8 @@
 
 /** Interface used by the creator of the controller to listen to changes with the container. */
 public interface AppWindowContainerListener extends WindowContainerListener {
-    /** Called when the windows associated app window container are drawn. */
-    void onWindowsDrawn(long timestamp);
+    /** Called when the windows associated app window container drawn state changes. */
+    void onWindowsDrawn(boolean drawn, long timestamp);
     /** Called when the windows associated app window container are visible. */
     void onWindowsVisible();
     /** Called when the windows associated app window container are no longer visible. */
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 3cece11..92944a0 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -95,6 +95,7 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.DisplayInfo;
 import android.view.IApplicationToken;
+import android.view.InputApplicationHandle;
 import android.view.RemoteAnimationDefinition;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
@@ -105,7 +106,6 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ToBooleanFunction;
-import com.android.server.input.InputApplicationHandle;
 import com.android.server.policy.WindowManagerPolicy.StartingSurface;
 import com.android.server.wm.WindowManagerService.H;
 
@@ -366,6 +366,10 @@
                 if (controller != null) {
                     controller.reportWindowsDrawn();
                 }
+            } else {
+                if (controller != null) {
+                    controller.reportWindowsNotDrawn();
+                }
             }
             reportedDrawn = nowDrawn;
         }
@@ -2015,9 +2019,7 @@
         clearThumbnail();
         setClientHidden(isHidden() && hiddenRequested);
 
-        if (mService.mInputMethodTarget != null && mService.mInputMethodTarget.mAppToken == this) {
-            getDisplayContent().computeImeTarget(true /* updateImeTarget */);
-        }
+        getDisplayContent().computeImeTargetIfNeeded(this);
 
         if (DEBUG_ANIM) Slog.v(TAG, "Animation done in " + this
                 + ": reportedVisible=" + reportedVisible
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 767a6ef..a3e8029 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -100,7 +100,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerService.CUSTOM_SCREEN_ROTATION;
-import static com.android.server.wm.WindowManagerService.H.REPORT_FOCUS_CHANGE;
 import static com.android.server.wm.WindowManagerService.H.REPORT_LOSING_FOCUS;
 import static com.android.server.wm.WindowManagerService.H.SEND_NEW_CONFIGURATION;
 import static com.android.server.wm.WindowManagerService.H.UPDATE_DOCKED_STACK_DIVIDER;
@@ -496,6 +495,15 @@
      */
     WindowState mInputMethodWindow;
 
+    /**
+     * This just indicates the window the input method is on top of, not
+     * necessarily the window its input is going to.
+     */
+    WindowState mInputMethodTarget;
+
+    /** If true hold off on modifying the animation layer of mInputMethodTarget */
+    boolean mInputMethodTargetWaitingAnim;
+
     private final PointerEventDispatcher mPointerEventDispatcher;
 
     private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> {
@@ -699,7 +707,7 @@
 
     private final Consumer<WindowState> mApplyPostLayoutPolicy =
             w -> mService.mPolicy.applyPostLayoutPolicyLw(w, w.mAttrs, w.getParentWindow(),
-                    mService.mInputMethodTarget);
+                    mInputMethodTarget);
 
     private final Consumer<WindowState> mApplySurfaceChangesTransaction = w -> {
         final WindowSurfacePlacer surfacePlacer = mService.mWindowPlacerLocked;
@@ -1928,7 +1936,7 @@
      * rather than directly above their target.
      */
     private boolean skipTraverseChild(WindowContainer child) {
-        if (child == mImeWindowsContainers && mService.mInputMethodTarget != null
+        if (child == mImeWindowsContainers && mInputMethodTarget != null
                 && !hasSplitScreenPrimaryStack()) {
             return true;
         }
@@ -2800,13 +2808,12 @@
         if (mCurrentFocus == newFocus) {
             return false;
         }
-        mService.mH.obtainMessage(REPORT_FOCUS_CHANGE, this).sendToTarget();
         boolean imWindowChanged = false;
         // TODO (b/111080190): Multi-Session IME
         if (!focusFound) {
             final WindowState imWindow = mInputMethodWindow;
             if (imWindow != null) {
-                final WindowState prevTarget = mService.mInputMethodTarget;
+                final WindowState prevTarget = mInputMethodTarget;
 
                 final WindowState newTarget = computeImeTarget(true /* updateImeTarget*/);
                 imWindowChanged = prevTarget != newTarget;
@@ -2998,13 +3005,13 @@
             // There isn't an IME so there shouldn't be a target...That was easy!
             if (updateImeTarget) {
                 if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from "
-                        + mService.mInputMethodTarget + " to null since mInputMethodWindow is null");
-                setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim);
+                        + mInputMethodTarget + " to null since mInputMethodWindow is null");
+                setInputMethodTarget(null, mInputMethodTargetWaitingAnim);
             }
             return null;
         }
 
-        final WindowState curTarget = mService.mInputMethodTarget;
+        final WindowState curTarget = mInputMethodTarget;
         if (!canUpdateImeTarget()) {
             if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Defer updating IME target");
             return curTarget;
@@ -3031,7 +3038,7 @@
         }
 
         if (DEBUG_INPUT_METHOD && updateImeTarget) Slog.v(TAG_WM,
-                "Proposed new IME target: " + target);
+                "Proposed new IME target: " + target + " for display: " + getDisplayId());
 
         // Now, a special case -- if the last target's window is in the process of exiting, but
         // not removed, and the new target is home, keep on the last target to avoid flicker.
@@ -3052,7 +3059,7 @@
                 if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from " + curTarget
                         + " to null." + (SHOW_STACK_CRAWLS ? " Callers="
                         + Debug.getCallers(4) : ""));
-                setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim);
+                setInputMethodTarget(null, mInputMethodTargetWaitingAnim);
             }
 
             return null;
@@ -3091,14 +3098,23 @@
         return target;
     }
 
+    /**
+     * Calling {@link #computeImeTarget(boolean)} to update the input method target window in
+     * the candidate app window token if needed.
+     */
+    void computeImeTargetIfNeeded(AppWindowToken candidate) {
+        if (mInputMethodTarget != null && mInputMethodTarget.mAppToken == candidate) {
+            computeImeTarget(true /* updateImeTarget */);
+        }
+    }
+
     private void setInputMethodTarget(WindowState target, boolean targetWaitingAnim) {
-        if (target == mService.mInputMethodTarget
-                && mService.mInputMethodTargetWaitingAnim == targetWaitingAnim) {
+        if (target == mInputMethodTarget && mInputMethodTargetWaitingAnim == targetWaitingAnim) {
             return;
         }
 
-        mService.mInputMethodTarget = target;
-        mService.mInputMethodTargetWaitingAnim = targetWaitingAnim;
+        mInputMethodTarget = target;
+        mInputMethodTargetWaitingAnim = targetWaitingAnim;
         assignWindowLayers(false /* setLayoutNeeded */);
     }
 
@@ -4487,7 +4503,7 @@
         mTaskStackContainers.assignLayer(t, 1);
         mAboveAppWindowsContainers.assignLayer(t, 2);
 
-        WindowState imeTarget = mService.mInputMethodTarget;
+        final WindowState imeTarget = mInputMethodTarget;
         boolean needAssignIme = true;
 
         // In the case where we have an IME target that is not in split-screen
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index ce8c979..7ed078a 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -36,7 +36,7 @@
 import android.view.View;
 
 import com.android.internal.util.Preconditions;
-import com.android.server.input.InputWindowHandle;
+import android.view.InputWindowHandle;
 import com.android.server.wm.WindowManagerInternal.IDragDropCallback;
 import java.util.concurrent.atomic.AtomicReference;
 
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 5483602..a379266 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -57,8 +57,8 @@
 
 import com.android.internal.view.IDragAndDropPermissions;
 import com.android.server.LocalServices;
-import com.android.server.input.InputApplicationHandle;
-import com.android.server.input.InputWindowHandle;
+import android.view.InputApplicationHandle;
+import android.view.InputWindowHandle;
 
 import java.util.ArrayList;
 
@@ -223,7 +223,7 @@
             mDragApplicationHandle.dispatchingTimeoutNanos =
                     WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
 
-            mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null, null,
+            mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null,
                     display.getDisplayId());
             mDragWindowHandle.name = "drag";
             mDragWindowHandle.inputChannel = mServerChannel;
diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java
index 585a4f5..49bedc9 100644
--- a/services/core/java/com/android/server/wm/InputConsumerImpl.java
+++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java
@@ -23,8 +23,8 @@
 import android.view.InputChannel;
 import android.view.WindowManager;
 
-import com.android.server.input.InputApplicationHandle;
-import com.android.server.input.InputWindowHandle;
+import android.view.InputApplicationHandle;
+import android.view.InputWindowHandle;
 
 import java.io.PrintWriter;
 
@@ -63,7 +63,7 @@
         mApplicationHandle.dispatchingTimeoutNanos =
                 WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
 
-        mWindowHandle = new InputWindowHandle(mApplicationHandle, null, null, displayId);
+        mWindowHandle = new InputWindowHandle(mApplicationHandle, null, displayId);
         mWindowHandle.name = name;
         mWindowHandle.inputChannel = mServerChannel;
         mWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index f823caa..92ea1a9 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -8,16 +8,19 @@
 
 import android.app.ActivityManager;
 import android.os.Debug;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 import android.view.KeyEvent;
 import android.view.WindowManager;
 
-import com.android.server.input.InputApplicationHandle;
+import android.view.InputApplicationHandle;
 import com.android.server.input.InputManagerService;
-import com.android.server.input.InputWindowHandle;
+import android.view.InputWindowHandle;
+import android.view.InputChannel;
 
 import java.io.PrintWriter;
+import java.util.HashMap;
 
 final class InputManagerCallback implements InputManagerService.WindowManagerCallbacks {
     private final WindowManagerService mService;
@@ -48,13 +51,13 @@
      * Called by the InputManager.
      */
     @Override
-    public void notifyInputChannelBroken(InputWindowHandle inputWindowHandle) {
-        if (inputWindowHandle == null) {
+    public void notifyInputChannelBroken(IBinder token) {
+        if (token == null) {
             return;
         }
 
         synchronized (mService.mGlobalLock) {
-            WindowState windowState = (WindowState) inputWindowHandle.windowState;
+            WindowState windowState = mService.windowForClientLocked(null, token, false);
             if (windowState != null) {
                 Slog.i(TAG_WM, "WINDOW DIED " + windowState);
                 windowState.removeIfPossible();
@@ -70,13 +73,13 @@
      */
     @Override
     public long notifyANR(InputApplicationHandle inputApplicationHandle,
-            InputWindowHandle inputWindowHandle, String reason) {
+            IBinder token, String reason) {
         AppWindowToken appWindowToken = null;
         WindowState windowState = null;
         boolean aboveSystem = false;
         synchronized (mService.mGlobalLock) {
-            if (inputWindowHandle != null) {
-                windowState = (WindowState) inputWindowHandle.windowState;
+            if (token != null) {
+                windowState = mService.windowForClientLocked(null, token, false);
                 if (windowState != null) {
                     appWindowToken = windowState.mAppToken;
                 }
@@ -188,8 +191,8 @@
      */
     @Override
     public long interceptKeyBeforeDispatching(
-            InputWindowHandle focus, KeyEvent event, int policyFlags) {
-        WindowState windowState = focus != null ? (WindowState) focus.windowState : null;
+            IBinder focus, KeyEvent event, int policyFlags) {
+        WindowState windowState = mService.windowForClientLocked(null, focus, false);
         return mService.mPolicy.interceptKeyBeforeDispatching(windowState, event, policyFlags);
     }
 
@@ -199,8 +202,8 @@
      */
     @Override
     public KeyEvent dispatchUnhandledKey(
-            InputWindowHandle focus, KeyEvent event, int policyFlags) {
-        WindowState windowState = focus != null ? (WindowState) focus.windowState : null;
+            IBinder focus, KeyEvent event, int policyFlags) {
+        WindowState windowState = mService.windowForClientLocked(null, focus, false);
         return mService.mPolicy.dispatchUnhandledKey(windowState, event, policyFlags);
     }
 
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 0e4ab53..61bc4e4 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -42,9 +42,12 @@
 import android.util.Slog;
 import android.view.InputChannel;
 import android.view.InputEventReceiver;
+import android.view.KeyEvent;
+import android.view.WindowManager;
+import android.view.InputApplicationHandle;
+import android.view.InputWindowHandle;
 
-import com.android.server.input.InputApplicationHandle;
-import com.android.server.input.InputWindowHandle;
+import com.android.server.input.InputManagerService;
 import com.android.server.policy.WindowManagerPolicy;
 
 import java.io.PrintWriter;
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index c4fbee9..6627c2d 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -43,6 +43,7 @@
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.util.proto.ProtoOutputStream;
+import android.view.InputWindowHandle;
 import android.view.IRecentsAnimationController;
 import android.view.IRecentsAnimationRunner;
 import android.view.RemoteAnimationTarget;
@@ -50,7 +51,6 @@
 import android.view.SurfaceControl.Transaction;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
-import com.android.server.input.InputWindowHandle;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 import com.android.server.wm.utils.InsetUtils;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 01b05c3..1baca32 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -122,7 +122,7 @@
 
     // The ID of the display which is responsible for receiving display-unspecified key and pointer
     // events.
-    private int mTopFocusedDisplayId = INVALID_DISPLAY;
+    int mTopFocusedDisplayId = INVALID_DISPLAY;
 
     // Only a seperate transaction until we seperate the apply surface changes
     // transaction from the global transaction.
@@ -156,7 +156,8 @@
     boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
         boolean changed = false;
         int topFocusedDisplayId = INVALID_DISPLAY;
-        for (int i = mChildren.size() - 1; i >= 0; i--) {
+
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
             final DisplayContent dc = mChildren.get(i);
             changed |= dc.updateFocusedWindowLocked(mode, updateInputWindows,
                     topFocusedDisplayId != INVALID_DISPLAY /* focusFound */);
@@ -167,12 +168,35 @@
         if (topFocusedDisplayId == INVALID_DISPLAY) {
             topFocusedDisplayId = DEFAULT_DISPLAY;
         }
+        // TODO(b/118865114): Review if need callback top focus display change to view component.
+        // (i.e. Activity or View)
+        // Currently we only tracked topFocusedDisplayChanged for notifying InputMethodManager via
+        // ViewRootImpl.windowFocusChanged to refocus IME window when top display focus changed
+        // but window focus remain the same case.
+        // It may need to review if any use case that need to add new callback for reporting
+        // this change.
+        final boolean topFocusedDisplayChanged =
+                mTopFocusedDisplayId != topFocusedDisplayId && mode == UPDATE_FOCUS_NORMAL;
         if (mTopFocusedDisplayId != topFocusedDisplayId) {
             mTopFocusedDisplayId = topFocusedDisplayId;
-            mService.mInputManager.setFocusedDisplay(topFocusedDisplayId);
+            mService.mInputManager.setFocusedDisplay(mTopFocusedDisplayId);
             if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "New topFocusedDisplayId="
-                    + topFocusedDisplayId);
+                    + mTopFocusedDisplayId);
         }
+
+        // Report window focus or top display focus changed through REPORT_FOCUS_CHANGE.
+        forAllDisplays((dc) -> {
+            final boolean windowFocusChanged =
+                    dc.mCurrentFocus != null && dc.mCurrentFocus != dc.mLastFocus;
+            final boolean isTopFocusedDisplay =
+                    topFocusedDisplayChanged && dc.getDisplayId() == mTopFocusedDisplayId;
+            if (windowFocusChanged || isTopFocusedDisplay) {
+                final Message msg = mService.mH.obtainMessage(
+                        WindowManagerService.H.REPORT_FOCUS_CHANGE, dc);
+                msg.arg1 = topFocusedDisplayChanged ? 1 : 0;
+                mService.mH.sendMessage(msg);
+            }
+        });
         final WindowState topFocusedWindow = getTopFocusedDisplayContent().mCurrentFocus;
         mService.mInputManager.setFocusedWindow(
                 topFocusedWindow != null ? topFocusedWindow.mInputWindowHandle : null);
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/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 66ebc9b..7182ad6 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -49,8 +49,9 @@
 import android.view.WindowManager;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.input.InputApplicationHandle;
-import com.android.server.input.InputWindowHandle;
+import android.view.InputApplicationHandle;
+import android.view.InputWindowHandle;
+import com.android.server.wm.WindowManagerService.H;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -265,7 +266,7 @@
         mDragApplicationHandle.dispatchingTimeoutNanos =
                 WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
 
-        mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null, null,
+        mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null,
                 display.getDisplayId());
         mDragWindowHandle.name = TAG;
         mDragWindowHandle.inputChannel = mServerChannel;
diff --git a/services/core/java/com/android/server/wm/TaskPositioningController.java b/services/core/java/com/android/server/wm/TaskPositioningController.java
index f2d3dca..51567a0 100644
--- a/services/core/java/com/android/server/wm/TaskPositioningController.java
+++ b/services/core/java/com/android/server/wm/TaskPositioningController.java
@@ -29,7 +29,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.input.InputManagerService;
-import com.android.server.input.InputWindowHandle;
+import android.view.InputWindowHandle;
 
 /**
  * Controller for task positioning by drag.
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 02904d4..c47b22f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -623,13 +623,6 @@
      */
     final Handler mAnimationHandler = new Handler(AnimationThread.getHandler().getLooper());
 
-    /** This just indicates the window the input method is on top of, not
-     * necessarily the window its input is going to. */
-    WindowState mInputMethodTarget = null;
-
-    /** If true hold off on modifying the animation layer of mInputMethodTarget */
-    boolean mInputMethodTargetWaitingAnim;
-
     boolean mHardKeyboardAvailable;
     WindowManagerInternal.OnHardKeyboardStatusChangeListener mHardKeyboardStatusChangeListener;
     SettingsObserver mSettingsObserver;
@@ -4400,6 +4393,7 @@
 
                     AccessibilityController accessibilityController = null;
 
+                    final boolean topFocusedDisplayChanged = msg.arg1 != 0;
                     synchronized (mGlobalLock) {
                         // TODO(multidisplay): Accessibility supported only of default desiplay.
                         if (mAccessibilityController != null && displayContent.isDefaultDisplay) {
@@ -4408,10 +4402,24 @@
 
                         lastFocus = displayContent.mLastFocus;
                         newFocus = displayContent.mCurrentFocus;
-                        if (lastFocus == newFocus) {
-                            // Focus is not changing, so nothing to do.
-                            return;
+                    }
+                    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();
                         }
+                        return;
+                    }
+                    synchronized (mGlobalLock) {
                         displayContent.mLastFocus = newFocus;
                         if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Focus moving from " + lastFocus +
                                 " to " + newFocus + " displayId=" + displayContent.getDisplayId());
@@ -4430,13 +4438,15 @@
 
                     if (newFocus != null) {
                         if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Gaining focus: " + newFocus);
-                        newFocus.reportFocusChangedSerialized(true, mInTouchMode);
+                        newFocus.reportFocusChangedSerialized(true, mInTouchMode,
+                                true /* reportToClient */);
                         notifyFocusChanged();
                     }
 
                     if (lastFocus != null) {
                         if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Losing focus: " + lastFocus);
-                        lastFocus.reportFocusChangedSerialized(false, mInTouchMode);
+                        lastFocus.reportFocusChangedSerialized(false, mInTouchMode,
+                                true /* reportToClient */);
                     }
                 } break;
 
@@ -4453,7 +4463,8 @@
                     for (int i = 0; i < N; i++) {
                         if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Losing delayed focus: " +
                                 losers.get(i));
-                        losers.get(i).reportFocusChangedSerialized(false, mInTouchMode);
+                        losers.get(i).reportFocusChangedSerialized(false, mInTouchMode,
+                                true /* reportToClient */);
                     }
                 } break;
 
@@ -5930,9 +5941,13 @@
         pw.print("  mGlobalConfiguration="); pw.println(mRoot.getConfiguration());
         pw.print("  mHasPermanentDpad="); pw.println(mHasPermanentDpad);
         mRoot.dumpTopFocusedDisplayId(pw);
-        if (mInputMethodTarget != null) {
-            pw.print("  mInputMethodTarget="); pw.println(mInputMethodTarget);
-        }
+        mRoot.forAllDisplays(dc -> {
+            final WindowState inputMethodTarget = dc.mInputMethodTarget;
+            if (inputMethodTarget != null) {
+                pw.print("  mInputMethodTarget in display# "); pw.print(dc.getDisplayId());
+                pw.print(' '); pw.println(inputMethodTarget);
+            }
+        });
         pw.print("  mInTouchMode="); pw.println(mInTouchMode);
         pw.print("  mLastDisplayFreezeDuration=");
                 TimeUtils.formatDuration(mLastDisplayFreezeDuration, pw);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 99f65c3..a117cf3 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -191,7 +191,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ToBooleanFunction;
-import com.android.server.input.InputWindowHandle;
+import android.view.InputWindowHandle;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
 import com.android.server.wm.utils.InsetUtils;
@@ -718,7 +718,7 @@
         mLastRequestedHeight = 0;
         mLayer = 0;
         mInputWindowHandle = new InputWindowHandle(
-                mAppToken != null ? mAppToken.mInputApplicationHandle : null, this, c,
+                mAppToken != null ? mAppToken.mInputApplicationHandle : null, c,
                     getDisplayId());
     }
 
@@ -2047,7 +2047,7 @@
             // Create dummy event receiver that simply reports all events as handled.
             mDeadWindowEventReceiver = new DeadWindowEventReceiver(mClientChannel);
         }
-        mService.mInputManager.registerInputChannel(mInputChannel, mInputWindowHandle);
+        mService.mInputManager.registerInputChannel(mInputChannel, mClient.asBinder());
     }
 
     void disposeInputChannel() {
@@ -2059,6 +2059,7 @@
         // unregister server channel first otherwise it complains about broken channel
         if (mInputChannel != null) {
             mService.mInputManager.unregisterInputChannel(mInputChannel);
+
             mInputChannel.dispose();
             mInputChannel = null;
         }
@@ -2841,12 +2842,13 @@
      * Report a focus change.  Must be called with no locks held, and consistently
      * from the same serialized thread (such as dispatched from a handler).
      */
-    void reportFocusChangedSerialized(boolean focused, boolean inTouchMode) {
+    void reportFocusChangedSerialized(boolean focused, boolean inTouchMode,
+            boolean reportToClient) {
         try {
-            mClient.windowFocusChanged(focused, inTouchMode);
+            mClient.windowFocusChanged(focused, inTouchMode, reportToClient);
         } catch (RemoteException e) {
         }
-        if (mFocusCallbacks != null) {
+        if (mFocusCallbacks != null && reportToClient) {
             final int N = mFocusCallbacks.beginBroadcast();
             for (int i=0; i<N; i++) {
                 IWindowFocusObserver obs = mFocusCallbacks.getBroadcastItem(i);
@@ -4476,8 +4478,9 @@
 
     @Override
     boolean needsZBoost() {
-        if (mIsImWindow && mService.mInputMethodTarget != null) {
-            final AppWindowToken appToken = mService.mInputMethodTarget.mAppToken;
+        final WindowState inputMethodTarget = getDisplayContent().mInputMethodTarget;
+        if (mIsImWindow && inputMethodTarget != null) {
+            final AppWindowToken appToken = inputMethodTarget.mAppToken;
             if (appToken != null) {
                 return appToken.needsZBoost();
             }
@@ -4607,7 +4610,7 @@
             // Likewise if we share a token with the Input method target and are ordered
             // above it but not necessarily a child (e.g. a Dialog) then we also need
             // this promotion.
-            final WindowState imeTarget = mService.mInputMethodTarget;
+            final WindowState imeTarget = getDisplayContent().mInputMethodTarget;
             boolean inTokenWithAndAboveImeTarget = imeTarget != null && imeTarget != this
                     && imeTarget.mToken == mToken && imeTarget.compareTo(this) <= 0;
             return inTokenWithAndAboveImeTarget;
@@ -4684,7 +4687,7 @@
 
     @Override
     public boolean isInputMethodTarget() {
-        return mService.mInputMethodTarget == this;
+        return getDisplayContent().mInputMethodTarget == this;
     }
 
     long getFrameNumber() {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 3943dba..e2db807 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -42,6 +42,8 @@
 #include <utils/Trace.h>
 #include <utils/SortedVector.h>
 
+#include <binder/IServiceManager.h>
+
 #include <input/PointerController.h>
 #include <input/SpriteController.h>
 
@@ -63,6 +65,7 @@
 #include "android_hardware_input_InputApplicationHandle.h"
 #include "android_hardware_input_InputWindowHandle.h"
 #include "android_hardware_display_DisplayViewport.h"
+#include "android_util_Binder.h"
 
 #include <vector>
 
@@ -153,15 +156,6 @@
             getInputApplicationHandleObjLocalRef(env);
 }
 
-static jobject getInputWindowHandleObjLocalRef(JNIEnv* env,
-        const sp<InputWindowHandle>& inputWindowHandle) {
-    if (inputWindowHandle == nullptr) {
-        return nullptr;
-    }
-    return static_cast<NativeInputWindowHandle*>(inputWindowHandle.get())->
-            getInputWindowHandleObjLocalRef(env);
-}
-
 static void loadSystemIconAsSpriteWithPointerIcon(JNIEnv* env, jobject contextObj, int32_t style,
         PointerIcon* outPointerIcon, SpriteIcon* outSpriteIcon) {
     status_t status = android_view_PointerIcon_loadSystemIcon(env,
@@ -216,8 +210,7 @@
 
     void setDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray);
 
-    status_t registerInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel,
-            const sp<InputWindowHandle>& inputWindowHandle, int32_t displayId);
+    status_t registerInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel, int32_t displayId);
     status_t unregisterInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel);
 
     void setInputWindows(JNIEnv* env, jobjectArray windowHandleObjArray, int32_t displayId);
@@ -253,17 +246,17 @@
             uint32_t policyFlags);
     virtual void notifyConfigurationChanged(nsecs_t when);
     virtual nsecs_t notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
-            const sp<InputWindowHandle>& inputWindowHandle,
+            const sp<IBinder>& token,
             const std::string& reason);
-    virtual void notifyInputChannelBroken(const sp<InputWindowHandle>& inputWindowHandle);
+    virtual void notifyInputChannelBroken(const sp<IBinder>& token);
     virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags);
     virtual void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig);
     virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags);
     virtual void interceptMotionBeforeQueueing(nsecs_t when, uint32_t& policyFlags);
     virtual nsecs_t interceptKeyBeforeDispatching(
-            const sp<InputWindowHandle>& inputWindowHandle,
+            const sp<IBinder>& token,
             const KeyEvent* keyEvent, uint32_t policyFlags);
-    virtual bool dispatchUnhandledKey(const sp<InputWindowHandle>& inputWindowHandle,
+    virtual bool dispatchUnhandledKey(const sp<IBinder>& token,
             const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent);
     virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType);
     virtual bool checkInjectEventsPermissionNonReentrant(
@@ -442,11 +435,10 @@
 }
 
 status_t NativeInputManager::registerInputChannel(JNIEnv* /* env */,
-        const sp<InputChannel>& inputChannel, const sp<InputWindowHandle>& inputWindowHandle,
-                int32_t displayId) {
+        const sp<InputChannel>& inputChannel, int32_t displayId) {
     ATRACE_CALL();
-    return mInputManager->getDispatcher()->registerInputChannel(inputChannel, inputWindowHandle,
-            displayId);
+    return mInputManager->getDispatcher()->registerInputChannel(
+            inputChannel, displayId);
 }
 
 status_t NativeInputManager::unregisterInputChannel(JNIEnv* /* env */,
@@ -657,7 +649,7 @@
 }
 
 nsecs_t NativeInputManager::notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
-        const sp<InputWindowHandle>& inputWindowHandle, const std::string& reason) {
+        const sp<IBinder>& token, const std::string& reason) {
 #if DEBUG_INPUT_DISPATCHER_POLICY
     ALOGD("notifyANR");
 #endif
@@ -667,12 +659,11 @@
 
     jobject inputApplicationHandleObj =
             getInputApplicationHandleObjLocalRef(env, inputApplicationHandle);
-    jobject inputWindowHandleObj =
-            getInputWindowHandleObjLocalRef(env, inputWindowHandle);
+    jobject tokenObj = javaObjectForIBinder(env, token);
     jstring reasonObj = env->NewStringUTF(reason.c_str());
 
     jlong newTimeout = env->CallLongMethod(mServiceObj,
-                gServiceClassInfo.notifyANR, inputApplicationHandleObj, inputWindowHandleObj,
+                gServiceClassInfo.notifyANR, inputApplicationHandleObj, tokenObj,
                 reasonObj);
     if (checkAndClearExceptionFromCallback(env, "notifyANR")) {
         newTimeout = 0; // abort dispatch
@@ -681,12 +672,11 @@
     }
 
     env->DeleteLocalRef(reasonObj);
-    env->DeleteLocalRef(inputWindowHandleObj);
     env->DeleteLocalRef(inputApplicationHandleObj);
     return newTimeout;
 }
 
-void NativeInputManager::notifyInputChannelBroken(const sp<InputWindowHandle>& inputWindowHandle) {
+void NativeInputManager::notifyInputChannelBroken(const sp<IBinder>& token) {
 #if DEBUG_INPUT_DISPATCHER_POLICY
     ALOGD("notifyInputChannelBroken");
 #endif
@@ -694,14 +684,11 @@
 
     JNIEnv* env = jniEnv();
 
-    jobject inputWindowHandleObj =
-            getInputWindowHandleObjLocalRef(env, inputWindowHandle);
-    if (inputWindowHandleObj) {
+    jobject tokenObj = javaObjectForIBinder(env, token);
+    if (tokenObj) {
         env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyInputChannelBroken,
-                inputWindowHandleObj);
+                tokenObj);
         checkAndClearExceptionFromCallback(env, "notifyInputChannelBroken");
-
-        env->DeleteLocalRef(inputWindowHandleObj);
     }
 }
 
@@ -1061,7 +1048,7 @@
 }
 
 nsecs_t NativeInputManager::interceptKeyBeforeDispatching(
-        const sp<InputWindowHandle>& inputWindowHandle,
+        const sp<IBinder>& token,
         const KeyEvent* keyEvent, uint32_t policyFlags) {
     ATRACE_CALL();
     // Policy:
@@ -1072,13 +1059,14 @@
     if (policyFlags & POLICY_FLAG_TRUSTED) {
         JNIEnv* env = jniEnv();
 
-        // Note: inputWindowHandle may be null.
-        jobject inputWindowHandleObj = getInputWindowHandleObjLocalRef(env, inputWindowHandle);
+        // Token may be null
+        jobject tokenObj = javaObjectForIBinder(env, token);
+
         jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
         if (keyEventObj) {
             jlong delayMillis = env->CallLongMethod(mServiceObj,
                     gServiceClassInfo.interceptKeyBeforeDispatching,
-                    inputWindowHandleObj, keyEventObj, policyFlags);
+                    tokenObj, keyEventObj, policyFlags);
             bool error = checkAndClearExceptionFromCallback(env, "interceptKeyBeforeDispatching");
             android_view_KeyEvent_recycle(env, keyEventObj);
             env->DeleteLocalRef(keyEventObj);
@@ -1092,12 +1080,11 @@
         } else {
             ALOGE("Failed to obtain key event object for interceptKeyBeforeDispatching.");
         }
-        env->DeleteLocalRef(inputWindowHandleObj);
     }
     return result;
 }
 
-bool NativeInputManager::dispatchUnhandledKey(const sp<InputWindowHandle>& inputWindowHandle,
+bool NativeInputManager::dispatchUnhandledKey(const sp<IBinder>& token,
         const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) {
     ATRACE_CALL();
     // Policy:
@@ -1106,13 +1093,13 @@
     if (policyFlags & POLICY_FLAG_TRUSTED) {
         JNIEnv* env = jniEnv();
 
-        // Note: inputWindowHandle may be null.
-        jobject inputWindowHandleObj = getInputWindowHandleObjLocalRef(env, inputWindowHandle);
+        // Note: tokenObj may be null.
+        jobject tokenObj = javaObjectForIBinder(env, token);
         jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
         if (keyEventObj) {
             jobject fallbackKeyEventObj = env->CallObjectMethod(mServiceObj,
                     gServiceClassInfo.dispatchUnhandledKey,
-                    inputWindowHandleObj, keyEventObj, policyFlags);
+                    tokenObj, keyEventObj, policyFlags);
             if (checkAndClearExceptionFromCallback(env, "dispatchUnhandledKey")) {
                 fallbackKeyEventObj = nullptr;
             }
@@ -1131,7 +1118,6 @@
         } else {
             ALOGE("Failed to obtain key event object for dispatchUnhandledKey.");
         }
-        env->DeleteLocalRef(inputWindowHandleObj);
     }
     return result;
 }
@@ -1316,7 +1302,7 @@
 }
 
 static void nativeRegisterInputChannel(JNIEnv* env, jclass /* clazz */,
-        jlong ptr, jobject inputChannelObj, jobject inputWindowHandleObj, jint displayId) {
+        jlong ptr, jobject inputChannelObj, jint displayId) {
     NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
 
     sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
@@ -1325,12 +1311,10 @@
         throwInputChannelNotInitialized(env);
         return;
     }
+    bool monitor = inputChannel->getToken() == nullptr && displayId != ADISPLAY_ID_NONE;
 
-    sp<InputWindowHandle> inputWindowHandle =
-            android_server_InputWindowHandle_getHandle(env, inputWindowHandleObj);
+    status_t status = im->registerInputChannel(env, inputChannel, displayId);
 
-    status_t status = im->registerInputChannel(
-            env, inputChannel, inputWindowHandle, displayId);
     if (status) {
         std::string message;
         message += StringPrintf("Failed to register input channel.  status=%d", status);
@@ -1339,7 +1323,7 @@
     }
 
     // If inputWindowHandle is null and displayId >= 0, treat inputChannel as monitor.
-    if (inputWindowHandle != nullptr || displayId == ADISPLAY_ID_NONE) {
+    if (!monitor) {
         android_view_InputChannel_setDisposeCallback(env, inputChannelObj,
                 handleInputChannelDisposed, im);
     }
@@ -1640,7 +1624,7 @@
     { "nativeHasKeys", "(JII[I[Z)Z",
             (void*) nativeHasKeys },
     { "nativeRegisterInputChannel",
-            "(JLandroid/view/InputChannel;Lcom/android/server/input/InputWindowHandle;I)V",
+            "(JLandroid/view/InputChannel;I)V",
             (void*) nativeRegisterInputChannel },
     { "nativeUnregisterInputChannel", "(JLandroid/view/InputChannel;)V",
             (void*) nativeUnregisterInputChannel },
@@ -1650,9 +1634,9 @@
             (void*) nativeInjectInputEvent },
     { "nativeToggleCapsLock", "(JI)V",
             (void*) nativeToggleCapsLock },
-    { "nativeSetInputWindows", "(J[Lcom/android/server/input/InputWindowHandle;I)V",
+    { "nativeSetInputWindows", "(J[Landroid/view/InputWindowHandle;I)V",
             (void*) nativeSetInputWindows },
-    { "nativeSetFocusedApplication", "(JILcom/android/server/input/InputApplicationHandle;)V",
+    { "nativeSetFocusedApplication", "(JILandroid/view/InputApplicationHandle;)V",
             (void*) nativeSetFocusedApplication },
     { "nativeSetFocusedDisplay", "(JI)V",
             (void*) nativeSetFocusedDisplay },
@@ -1731,11 +1715,11 @@
             "notifySwitch", "(JII)V");
 
     GET_METHOD_ID(gServiceClassInfo.notifyInputChannelBroken, clazz,
-            "notifyInputChannelBroken", "(Lcom/android/server/input/InputWindowHandle;)V");
+            "notifyInputChannelBroken", "(Landroid/os/IBinder;)V");
 
     GET_METHOD_ID(gServiceClassInfo.notifyANR, clazz,
             "notifyANR",
-            "(Lcom/android/server/input/InputApplicationHandle;Lcom/android/server/input/InputWindowHandle;Ljava/lang/String;)J");
+            "(Landroid/view/InputApplicationHandle;Landroid/os/IBinder;Ljava/lang/String;)J");
 
     GET_METHOD_ID(gServiceClassInfo.filterInputEvent, clazz,
             "filterInputEvent", "(Landroid/view/InputEvent;I)Z");
@@ -1748,11 +1732,11 @@
 
     GET_METHOD_ID(gServiceClassInfo.interceptKeyBeforeDispatching, clazz,
             "interceptKeyBeforeDispatching",
-            "(Lcom/android/server/input/InputWindowHandle;Landroid/view/KeyEvent;I)J");
+            "(Landroid/os/IBinder;Landroid/view/KeyEvent;I)J");
 
     GET_METHOD_ID(gServiceClassInfo.dispatchUnhandledKey, clazz,
             "dispatchUnhandledKey",
-            "(Lcom/android/server/input/InputWindowHandle;Landroid/view/KeyEvent;I)Landroid/view/KeyEvent;");
+            "(Landroid/os/IBinder;Landroid/view/KeyEvent;I)Landroid/view/KeyEvent;");
 
     GET_METHOD_ID(gServiceClassInfo.checkInjectEventsPermission, clazz,
             "checkInjectEventsPermission", "(II)Z");
diff --git a/services/intelligence/java/com/android/server/intelligence/ContentCaptureSession.java b/services/intelligence/java/com/android/server/intelligence/ContentCaptureSession.java
index 9cab1ed..b7d34d7 100644
--- a/services/intelligence/java/com/android/server/intelligence/ContentCaptureSession.java
+++ b/services/intelligence/java/com/android/server/intelligence/ContentCaptureSession.java
@@ -76,7 +76,7 @@
     }
 
     /**
-     * Cleans up the session and remove itself from the service.
+     * Cleans up the session and removes it from the service.
      *
      * @param notifyRemoteService whether it should trigger a {@link
      * IntelligenceService#onDestroyInteractionSession(InteractionSessionId)}
@@ -85,14 +85,30 @@
     @GuardedBy("mLock")
     public void removeSelfLocked(boolean notifyRemoteService) {
         try {
-            if (notifyRemoteService) {
-                mRemoteService.onSessionLifecycleRequest(/* context= */ null, mId);
-            }
+            destroyLocked(notifyRemoteService);
         } finally {
             mService.removeSessionLocked(mId);
         }
     }
 
+    /**
+     * Cleans up the session, but not removes it from the service.
+     *
+     * @param notifyRemoteService whether it should trigger a {@link
+     * IntelligenceService#onDestroyInteractionSession(InteractionSessionId)}
+     * request.
+     */
+    @GuardedBy("mLock")
+    public void destroyLocked(boolean notifyRemoteService) {
+        if (mService.isVerbose()) {
+            Slog.v(TAG, "destroyLocked(notifyRemoteService=" + notifyRemoteService + ")");
+        }
+        // TODO(b/111276913): must call client to set session as FINISHED_BY_SERVER
+        if (notifyRemoteService) {
+            mRemoteService.onSessionLifecycleRequest(/* context= */ null, mId);
+        }
+    }
+
     @Override // from RemoteScreenObservationServiceCallbacks
     public void onServiceDied(AbstractRemoteService service) {
         // TODO(b/111276913): implement (remove session from PerUserSession?)
diff --git a/services/intelligence/java/com/android/server/intelligence/IntelligenceManagerService.java b/services/intelligence/java/com/android/server/intelligence/IntelligenceManagerService.java
index 43d4a44..fcfd246 100644
--- a/services/intelligence/java/com/android/server/intelligence/IntelligenceManagerService.java
+++ b/services/intelligence/java/com/android/server/intelligence/IntelligenceManagerService.java
@@ -19,6 +19,7 @@
 import static android.content.Context.INTELLIGENCE_MANAGER_SERVICE;
 
 import android.annotation.NonNull;
+import android.annotation.UserIdInt;
 import android.app.ActivityManagerInternal;
 import android.content.ComponentName;
 import android.content.Context;
@@ -53,18 +54,20 @@
     @GuardedBy("mLock")
     private ActivityManagerInternal mAm;
 
+    private final LocalService mLocalService = new LocalService();
+
     public IntelligenceManagerService(Context context) {
         super(context, UserManager.DISALLOW_INTELLIGENCE_CAPTURE);
     }
 
-    @Override // from MasterSystemService
+    @Override // from AbstractMasterSystemService
     protected String getServiceSettingsProperty() {
         // TODO(b/111276913): STOPSHIP temporary settings, until it's set by resourcs + cmd
         return "intel_service";
     }
 
-    @Override // from MasterSystemService
-    protected IntelligencePerUserService newServiceLocked(int resolvedUserId,
+    @Override // from AbstractMasterSystemService
+    protected IntelligencePerUserService newServiceLocked(@UserIdInt int resolvedUserId,
             boolean disabled) {
         return new IntelligencePerUserService(this, mLock, resolvedUserId);
     }
@@ -73,6 +76,13 @@
     public void onStart() {
         publishBinderService(INTELLIGENCE_MANAGER_SERVICE,
                 new IntelligenceManagerServiceStub());
+        publishLocalService(IntelligenceManagerInternal.class, mLocalService);
+    }
+
+    @Override // from AbstractMasterSystemService
+    protected void onServiceRemoved(@NonNull IntelligencePerUserService service,
+            @UserIdInt int userId) {
+        service.destroyLocked();
     }
 
     private ActivityManagerInternal getAmInternal() {
@@ -139,4 +149,19 @@
             }
         }
     }
+
+    private final class LocalService extends IntelligenceManagerInternal {
+
+        @Override
+        public boolean isIntelligenceServiceForUser(int uid, int userId) {
+            synchronized (mLock) {
+                final IntelligencePerUserService service = peekServiceForUserLocked(userId);
+                if (service != null) {
+                    return service.isIntelligenceServiceForUserLocked(uid);
+                }
+            }
+
+            return false;
+        }
+    }
 }
diff --git a/services/intelligence/java/com/android/server/intelligence/IntelligencePerUserService.java b/services/intelligence/java/com/android/server/intelligence/IntelligencePerUserService.java
index 584b872..471b40f 100644
--- a/services/intelligence/java/com/android/server/intelligence/IntelligencePerUserService.java
+++ b/services/intelligence/java/com/android/server/intelligence/IntelligencePerUserService.java
@@ -88,12 +88,18 @@
             @NonNull ComponentName componentName, int taskId, int displayId,
             @NonNull InteractionSessionId sessionId, int flags,
             @NonNull IResultReceiver resultReceiver) {
+        if (!isEnabledLocked()) {
+            sendToClient(resultReceiver, IntelligenceManager.STATE_DISABLED);
+            return;
+        }
         final ComponentName serviceComponentName = getServiceComponentName();
         if (serviceComponentName == null) {
             // TODO(b/111276913): this happens when the system service is starting, we should
             // probably handle it in a more elegant way (like waiting for boot_complete or
             // something like that
-            Slog.w(TAG, "startSession(" + activityToken + "): hold your horses");
+            if (mMaster.debug) {
+                Slog.d(TAG, "startSession(" + activityToken + "): hold your horses");
+            }
             return;
         }
 
@@ -128,9 +134,15 @@
     // TODO(b/111276913): log metrics
     @GuardedBy("mLock")
     public void finishSessionLocked(@NonNull InteractionSessionId sessionId) {
+        if (!isEnabledLocked()) {
+            return;
+        }
+
         final ContentCaptureSession session = mSessions.get(sessionId);
         if (session == null) {
-            Slog.w(TAG, "finishSession(): no session with id" + sessionId);
+            if (mMaster.debug) {
+                Slog.d(TAG, "finishSession(): no session with id" + sessionId);
+            }
             return;
         }
         if (mMaster.verbose) {
@@ -139,12 +151,19 @@
         session.removeSelfLocked(true);
     }
 
+    // TODO(b/111276913): need to figure out why some events are sent before session is started;
+    // probably because IntelligenceManager is not buffering them until it gets the session back
     @GuardedBy("mLock")
     public void sendEventsLocked(@NonNull InteractionSessionId sessionId,
             @NonNull List<ContentCaptureEvent> events) {
+        if (!isEnabledLocked()) {
+            return;
+        }
         final ContentCaptureSession session = mSessions.get(sessionId);
         if (session == null) {
-            Slog.w(TAG, "sendEvents(): no session for " + sessionId);
+            if (mMaster.verbose) {
+                Slog.v(TAG, "sendEvents(): no session for " + sessionId);
+            }
             return;
         }
         if (mMaster.verbose) {
@@ -158,6 +177,27 @@
         mSessions.remove(sessionId);
     }
 
+    @GuardedBy("mLock")
+    public boolean isIntelligenceServiceForUserLocked(int uid) {
+        return uid == getServiceUidLocked();
+    }
+
+    /**
+     * Destroys the service and all state associated with it.
+     *
+     * <p>Called when the service was disabled (for example, if the settings change).
+     */
+    @GuardedBy("mLock")
+    public void destroyLocked() {
+        if (mMaster.debug) Slog.d(TAG, "destroyLocked()");
+        final int numSessions = mSessions.size();
+        for (int i = 0; i < numSessions; i++) {
+            final ContentCaptureSession session = mSessions.valueAt(i);
+            session.destroyLocked(true);
+        }
+        mSessions.clear();
+    }
+
     @Override
     protected void dumpLocked(String prefix, PrintWriter pw) {
         super.dumpLocked(prefix, pw);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 73990f8..f8ac41f 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -35,6 +35,7 @@
 import android.content.res.Resources.Theme;
 import android.database.sqlite.SQLiteCompatibilityWalFlags;
 import android.database.sqlite.SQLiteGlobal;
+import android.hardware.display.ColorDisplayManager;
 import android.hardware.display.DisplayManagerInternal;
 import android.os.BaseBundle;
 import android.os.Binder;
@@ -62,7 +63,6 @@
 import android.view.WindowManager;
 
 import com.android.internal.R;
-import com.android.internal.app.ColorDisplayController;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.BinderInternal;
@@ -150,6 +150,7 @@
 import java.util.concurrent.Future;
 
 public final class SystemServer {
+
     private static final String TAG = "SystemServer";
 
     // Tag for timing measurement of main thread.
@@ -263,9 +264,8 @@
     private static final int sMaxBinderThreads = 31;
 
     /**
-     * Default theme used by the system context. This is used to style
-     * system-provided dialogs, such as the Power Off dialog, and other
-     * visual content.
+     * Default theme used by the system context. This is used to style system-provided dialogs, such
+     * as the Power Off dialog, and other visual content.
      */
     private static final int DEFAULT_SYSTEM_THEME =
             com.android.internal.R.style.Theme_DeviceDefault_System;
@@ -306,8 +306,7 @@
     private static native void startSensorService();
 
     /**
-     * Start all HIDL services that are run inside the system server. This
-     * may take some time.
+     * Start all HIDL services that are run inside the system server. This may take some time.
      */
     private static native void startHidlServices();
 
@@ -343,7 +342,7 @@
             //
             // Default the timezone property to GMT if not set.
             //
-            String timezoneProperty =  SystemProperties.get("persist.sys.timezone");
+            String timezoneProperty = SystemProperties.get("persist.sys.timezone");
             if (timezoneProperty == null || timezoneProperty.isEmpty()) {
                 Slog.w(TAG, "Timezone not set; setting to GMT.");
                 SystemProperties.set("persist.sys.timezone", "GMT");
@@ -424,7 +423,7 @@
 
             // Prepare the main looper thread (this thread).
             android.os.Process.setThreadPriority(
-                android.os.Process.THREAD_PRIORITY_FOREGROUND);
+                    android.os.Process.THREAD_PRIORITY_FOREGROUND);
             android.os.Process.setCanSelfBackground(false);
             Looper.prepareMainLooper();
             Looper.getMainLooper().setSlowLogThresholdMs(
@@ -529,7 +528,7 @@
                     if (filename != null && filename.startsWith("/data")) {
                         if (!new File(BLOCK_MAP_FILE).exists()) {
                             Slog.e(TAG, "Can't find block map file, uncrypt failed or " +
-                                       "unexpected runtime restart?");
+                                    "unexpected runtime restart?");
                             return;
                         }
                     }
@@ -562,11 +561,10 @@
     }
 
     /**
-     * Starts the small tangle of critical services that are needed to get
-     * the system off the ground.  These services have complex mutual dependencies
-     * which is why we initialize them all in one place here.  Unless your service
-     * is also entwined in these dependencies, it should be initialized in one of
-     * the other functions.
+     * Starts the small tangle of critical services that are needed to get the system off the
+     * ground.  These services have complex mutual dependencies which is why we initialize them all
+     * in one place here.  Unless your service is also entwined in these dependencies, it should be
+     * initialized in one of the other functions.
      */
     private void startBootstrapServices() {
         Slog.i(TAG, "Reading configuration...");
@@ -783,8 +781,7 @@
     }
 
     /**
-     * Starts a miscellaneous grab bag of stuff that has yet to be refactored
-     * and organized.
+     * Starts a miscellaneous grab bag of stuff that has yet to be refactored and organized.
      */
     private void startOtherServices() {
         final Context context = mSystemContext;
@@ -795,7 +792,7 @@
         NetworkStatsService networkStats = null;
         NetworkPolicyManagerService networkPolicy = null;
         ConnectivityService connectivity = null;
-        NsdService serviceDiscovery= null;
+        NsdService serviceDiscovery = null;
         WindowManagerService wm = null;
         SerialService serial = null;
         NetworkTimeUpdateService networkTimeUpdater = null;
@@ -807,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",
@@ -984,7 +984,7 @@
             } else if (mFactoryTestMode == FactoryTest.FACTORY_TEST_LOW_LEVEL) {
                 Slog.i(TAG, "No Bluetooth Service (factory test)");
             } else if (!context.getPackageManager().hasSystemFeature
-                       (PackageManager.FEATURE_BLUETOOTH)) {
+                    (PackageManager.FEATURE_BLUETOOTH)) {
                 Slog.i(TAG, "No Bluetooth Service (Bluetooth Hardware Not Present)");
             } else {
                 traceBeginAndSlog("StartBluetoothService");
@@ -1098,7 +1098,7 @@
             try {
                 mSystemServiceManager.startService(LOCK_SETTINGS_SERVICE_CLASS);
                 lockSettings = ILockSettings.Stub.asInterface(
-                    ServiceManager.getService("lock_settings"));
+                        ServiceManager.getService("lock_settings"));
             } catch (Throwable e) {
                 reportWtf("starting LockSettingsService service", e);
             }
@@ -1139,6 +1139,15 @@
                 traceEnd();
             }
 
+            if (!disableIntelligence) {
+                traceBeginAndSlog("StartIntelligenceService");
+                mSystemServiceManager.startService(INTELLIGENCE_MANAGER_SERVICE_CLASS);
+                traceEnd();
+            } else {
+                Slog.d(TAG, "IntelligenceService disabled");
+            }
+
+            // NOTE: ClipboardService indirectly depends on IntelligenceService
             traceBeginAndSlog("StartClipboardService");
             mSystemServiceManager.startService(ClipboardService.class);
             traceEnd();
@@ -1167,7 +1176,8 @@
 
             if (!disableSystemTextClassifier) {
                 traceBeginAndSlog("StartTextClassificationManagerService");
-                mSystemServiceManager.startService(TextClassificationManagerService.Lifecycle.class);
+                mSystemServiceManager
+                        .startService(TextClassificationManagerService.Lifecycle.class);
                 traceEnd();
             }
 
@@ -1196,41 +1206,41 @@
 
             if (!mOnlyCore) {
                 if (context.getPackageManager().hasSystemFeature(
-                            PackageManager.FEATURE_WIFI)) {
+                        PackageManager.FEATURE_WIFI)) {
                     // Wifi Service must be started first for wifi-related services.
                     traceBeginAndSlog("StartWifi");
                     mSystemServiceManager.startService(WIFI_SERVICE_CLASS);
                     traceEnd();
                     traceBeginAndSlog("StartWifiScanning");
                     mSystemServiceManager.startService(
-                        "com.android.server.wifi.scanner.WifiScanningService");
+                            "com.android.server.wifi.scanner.WifiScanningService");
                     traceEnd();
                 }
 
                 if (context.getPackageManager().hasSystemFeature(
-                    PackageManager.FEATURE_WIFI_RTT)) {
+                        PackageManager.FEATURE_WIFI_RTT)) {
                     traceBeginAndSlog("StartRttService");
                     mSystemServiceManager.startService(
-                        "com.android.server.wifi.rtt.RttService");
+                            "com.android.server.wifi.rtt.RttService");
                     traceEnd();
                 }
 
                 if (context.getPackageManager().hasSystemFeature(
-                    PackageManager.FEATURE_WIFI_AWARE)) {
+                        PackageManager.FEATURE_WIFI_AWARE)) {
                     traceBeginAndSlog("StartWifiAware");
                     mSystemServiceManager.startService(WIFI_AWARE_SERVICE_CLASS);
                     traceEnd();
                 }
 
                 if (context.getPackageManager().hasSystemFeature(
-                    PackageManager.FEATURE_WIFI_DIRECT)) {
+                        PackageManager.FEATURE_WIFI_DIRECT)) {
                     traceBeginAndSlog("StartWifiP2P");
                     mSystemServiceManager.startService(WIFI_P2P_SERVICE_CLASS);
                     traceEnd();
                 }
 
                 if (context.getPackageManager().hasSystemFeature(
-                    PackageManager.FEATURE_LOWPAN)) {
+                        PackageManager.FEATURE_LOWPAN)) {
                     traceBeginAndSlog("StartLowpan");
                     mSystemServiceManager.startService(LOWPAN_SERVICE_CLASS);
                     traceEnd();
@@ -1238,7 +1248,7 @@
             }
 
             if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_ETHERNET) ||
-                mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) {
+                    mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) {
                 traceBeginAndSlog("StartEthernet");
                 mSystemServiceManager.startService(ETHERNET_SERVICE_CLASS);
                 traceEnd();
@@ -1247,10 +1257,10 @@
             traceBeginAndSlog("StartConnectivityService");
             try {
                 connectivity = new ConnectivityService(
-                    context, networkManagement, networkStats, networkPolicy);
+                        context, networkManagement, networkStats, networkPolicy);
                 ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity,
-                            /* allowIsolated= */ false,
-                    DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
+                        /* allowIsolated= */ false,
+                        DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
                 networkStats.bindConnectivityManager(connectivity);
                 networkPolicy.bindConnectivityManager(connectivity);
             } catch (Throwable e) {
@@ -1262,7 +1272,7 @@
             try {
                 serviceDiscovery = NsdService.create(context);
                 ServiceManager.addService(
-                    Context.NSD_SERVICE, serviceDiscovery);
+                        Context.NSD_SERVICE, serviceDiscovery);
             } catch (Throwable e) {
                 reportWtf("starting Service Discovery Service", e);
             }
@@ -1280,7 +1290,7 @@
             traceBeginAndSlog("StartUpdateLockService");
             try {
                 ServiceManager.addService(Context.UPDATE_LOCK_SERVICE,
-                    new UpdateLockService(context));
+                        new UpdateLockService(context));
             } catch (Throwable e) {
                 reportWtf("starting UpdateLockService", e);
             }
@@ -1398,9 +1408,9 @@
             }
 
             if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)
-                || mPackageManager.hasSystemFeature(
-                PackageManager.FEATURE_USB_ACCESSORY)
-                || isEmulator) {
+                    || mPackageManager.hasSystemFeature(
+                    PackageManager.FEATURE_USB_ACCESSORY)
+                    || isEmulator) {
                 // Manage USB host and device support
                 traceBeginAndSlog("StartUsbService");
                 mSystemServiceManager.startService(USB_SERVICE_CLASS);
@@ -1432,7 +1442,7 @@
             try {
                 hardwarePropertiesService = new HardwarePropertiesManagerService(context);
                 ServiceManager.addService(Context.HARDWARE_PROPERTIES_SERVICE,
-                    hardwarePropertiesService);
+                        hardwarePropertiesService);
             } catch (Throwable e) {
                 Slog.e(TAG, "Failure starting HardwarePropertiesManagerService", e);
             }
@@ -1442,8 +1452,8 @@
             mSystemServiceManager.startService(TwilightService.class);
             traceEnd();
 
-            if (ColorDisplayController.isAvailable(context)) {
-                traceBeginAndSlog("StartNightDisplay");
+            if (ColorDisplayManager.isNightDisplayAvailable(context)) {
+                traceBeginAndSlog("StartColorDisplay");
                 mSystemServiceManager.startService(ColorDisplayService.class);
                 traceEnd();
             }
@@ -1467,7 +1477,7 @@
             }
 
             if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)
-                || context.getResources().getBoolean(R.bool.config_enableAppWidgetService)) {
+                    || context.getResources().getBoolean(R.bool.config_enableAppWidgetService)) {
                 traceBeginAndSlog("StartAppWidgetService");
                 mSystemServiceManager.startService(APPWIDGET_SERVICE_CLASS);
                 traceEnd();
@@ -1553,7 +1563,7 @@
 
             traceBeginAndSlog("AddGraphicsStatsService");
             ServiceManager.addService(GraphicsStatsService.GRAPHICS_STATS_SERVICE,
-                new GraphicsStatsService(context));
+                    new GraphicsStatsService(context));
             traceEnd();
 
             if (CoverageService.ENABLED) {
@@ -1769,12 +1779,6 @@
             traceEnd();
         }
 
-        if (!disableIntelligence) {
-            traceBeginAndSlog("StartIntelligenceService");
-            mSystemServiceManager.startService(INTELLIGENCE_MANAGER_SERVICE_CLASS);
-            traceEnd();
-        }
-
         traceBeginAndSlog("AppServiceManager");
         mSystemServiceManager.startService(AppBindingService.Lifecycle.class);
         traceEnd();
@@ -1825,7 +1829,7 @@
         // propagate to it.
         final Configuration config = wm.computeNewConfiguration(DEFAULT_DISPLAY);
         DisplayMetrics metrics = new DisplayMetrics();
-        WindowManager w = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+        WindowManager w = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
         w.getDefaultDisplay().getMetrics(metrics);
         context.getResources().updateConfiguration(config, metrics);
 
@@ -1949,7 +1953,9 @@
             traceEnd();
             traceBeginAndSlog("MakeNetworkManagementServiceReady");
             try {
-                if (networkManagementF != null) networkManagementF.systemReady();
+                if (networkManagementF != null) {
+                    networkManagementF.systemReady();
+                }
             } catch (Throwable e) {
                 reportWtf("making Network Managment Service ready", e);
             }
@@ -1961,21 +1967,27 @@
             traceEnd();
             traceBeginAndSlog("MakeIpSecServiceReady");
             try {
-                if (ipSecServiceF != null) ipSecServiceF.systemReady();
+                if (ipSecServiceF != null) {
+                    ipSecServiceF.systemReady();
+                }
             } catch (Throwable e) {
                 reportWtf("making IpSec Service ready", e);
             }
             traceEnd();
             traceBeginAndSlog("MakeNetworkStatsServiceReady");
             try {
-                if (networkStatsF != null) networkStatsF.systemReady();
+                if (networkStatsF != null) {
+                    networkStatsF.systemReady();
+                }
             } catch (Throwable e) {
                 reportWtf("making Network Stats Service ready", e);
             }
             traceEnd();
             traceBeginAndSlog("MakeConnectivityServiceReady");
             try {
-                if (connectivityF != null) connectivityF.systemReady();
+                if (connectivityF != null) {
+                    connectivityF.systemReady();
+                }
             } catch (Throwable e) {
                 reportWtf("making Connectivity Service ready", e);
             }
@@ -2010,21 +2022,27 @@
 
             traceBeginAndSlog("MakeLocationServiceReady");
             try {
-                if (locationF != null) locationF.systemRunning();
+                if (locationF != null) {
+                    locationF.systemRunning();
+                }
             } catch (Throwable e) {
                 reportWtf("Notifying Location Service running", e);
             }
             traceEnd();
             traceBeginAndSlog("MakeCountryDetectionServiceReady");
             try {
-                if (countryDetectorF != null) countryDetectorF.systemRunning();
+                if (countryDetectorF != null) {
+                    countryDetectorF.systemRunning();
+                }
             } catch (Throwable e) {
                 reportWtf("Notifying CountryDetectorService running", e);
             }
             traceEnd();
             traceBeginAndSlog("MakeNetworkTimeUpdateReady");
             try {
-                if (networkTimeUpdaterF != null) networkTimeUpdaterF.systemRunning();
+                if (networkTimeUpdaterF != null) {
+                    networkTimeUpdaterF.systemRunning();
+                }
             } catch (Throwable e) {
                 reportWtf("Notifying NetworkTimeService running", e);
             }
@@ -2032,28 +2050,36 @@
             traceBeginAndSlog("MakeInputManagerServiceReady");
             try {
                 // TODO(BT) Pass parameter to input manager
-                if (inputManagerF != null) inputManagerF.systemRunning();
+                if (inputManagerF != null) {
+                    inputManagerF.systemRunning();
+                }
             } catch (Throwable e) {
                 reportWtf("Notifying InputManagerService running", e);
             }
             traceEnd();
             traceBeginAndSlog("MakeTelephonyRegistryReady");
             try {
-                if (telephonyRegistryF != null) telephonyRegistryF.systemRunning();
+                if (telephonyRegistryF != null) {
+                    telephonyRegistryF.systemRunning();
+                }
             } catch (Throwable e) {
                 reportWtf("Notifying TelephonyRegistry running", e);
             }
             traceEnd();
             traceBeginAndSlog("MakeMediaRouterServiceReady");
             try {
-                if (mediaRouterF != null) mediaRouterF.systemRunning();
+                if (mediaRouterF != null) {
+                    mediaRouterF.systemRunning();
+                }
             } catch (Throwable e) {
                 reportWtf("Notifying MediaRouterService running", e);
             }
             traceEnd();
             traceBeginAndSlog("MakeMmsServiceReady");
             try {
-                if (mmsServiceF != null) mmsServiceF.systemRunning();
+                if (mmsServiceF != null) {
+                    mmsServiceF.systemRunning();
+                }
             } catch (Throwable e) {
                 reportWtf("Notifying MmsService running", e);
             }
@@ -2065,7 +2091,9 @@
                 // in the build and should reliably be there.
                 final IIncidentManager incident = IIncidentManager.Stub.asInterface(
                         ServiceManager.getService(Context.INCIDENT_SERVICE));
-                if (incident != null) incident.systemRunning();
+                if (incident != null) {
+                    incident.systemRunning();
+                }
             } catch (Throwable e) {
                 reportWtf("Notifying incident daemon running", e);
             }
@@ -2076,7 +2104,7 @@
     static final void startSystemUi(Context context, WindowManagerService windowManager) {
         Intent intent = new Intent();
         intent.setComponent(new ComponentName("com.android.systemui",
-                    "com.android.systemui.SystemUIService"));
+                "com.android.systemui.SystemUIService"));
         intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
         //Slog.d(TAG, "Starting service: " + intent);
         context.startServiceAsUser(intent, UserHandle.SYSTEM);
diff --git a/services/robotests/Android.mk b/services/robotests/Android.mk
index 9ab06a1..565152c 100644
--- a/services/robotests/Android.mk
+++ b/services/robotests/Android.mk
@@ -27,7 +27,8 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     services.backup \
-    services.core
+    services.core \
+    services.net
 
 include $(BUILD_PACKAGE)
 
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/am/GlobalSettingsToPropertiesMapperTest.java b/services/tests/servicestests/src/com/android/server/am/GlobalSettingsToPropertiesMapperTest.java
deleted file mode 100644
index c162c3b..0000000
--- a/services/tests/servicestests/src/com/android/server/am/GlobalSettingsToPropertiesMapperTest.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.am;
-
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-
-import android.content.ContentResolver;
-import android.provider.Settings;
-import android.test.mock.MockContentResolver;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.util.Preconditions;
-import com.android.internal.util.test.FakeSettingsProvider;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Tests for {@link GlobalSettingsToPropertiesMapper}
- *
- * Build/Install/Run:
- *  atest FrameworksServicesTests:GlobalSettingsToPropertiesMapperTest
- */
-@SmallTest
-public class GlobalSettingsToPropertiesMapperTest {
-    private static final String[][] TEST_MAPPING = new String[][] {
-        {Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, "TestProperty"}
-    };
-
-    private TestMapper mTestMapper;
-    private MockContentResolver mMockContentResolver;
-
-    @Before
-    public void setup() {
-        // Use FakeSettingsProvider to not affect global state
-        mMockContentResolver = new MockContentResolver(getInstrumentation().getTargetContext());
-        mMockContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
-        mTestMapper = new TestMapper(mMockContentResolver);
-    }
-
-    @Test
-    public void testUpdatePropertiesFromGlobalSettings() {
-        Settings.Global.putString(mMockContentResolver,
-                Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, "testValue");
-
-        mTestMapper.updatePropertiesFromGlobalSettings();
-        String propValue = mTestMapper.systemPropertiesGet("TestProperty");
-        assertEquals("testValue", propValue);
-
-        Settings.Global.putString(mMockContentResolver,
-                Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, "testValue2");
-        mTestMapper.updatePropertyFromSetting(Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS,
-                "TestProperty");
-        propValue = mTestMapper.systemPropertiesGet("TestProperty");
-        assertEquals("testValue2", propValue);
-
-        Settings.Global.putString(mMockContentResolver,
-                Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, null);
-        mTestMapper.updatePropertyFromSetting(Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS,
-                "TestProperty");
-        propValue = mTestMapper.systemPropertiesGet("TestProperty");
-        assertEquals("", propValue);
-    }
-
-    @Test
-    public void testUpdatePropertiesFromGlobalSettings_PropertyAndSettingNotPresent() {
-        // Test that empty property will not not be set if setting is not set
-        mTestMapper.updatePropertiesFromGlobalSettings();
-        String propValue = mTestMapper.systemPropertiesGet("TestProperty");
-        assertNull("Property should not be set if setting is null", propValue);
-    }
-
-    private static class TestMapper extends GlobalSettingsToPropertiesMapper {
-        private final Map<String, String> mProps = new HashMap<>();
-
-        TestMapper(ContentResolver contentResolver) {
-            super(contentResolver, TEST_MAPPING);
-        }
-
-        @Override
-        protected String systemPropertiesGet(String key) {
-            Preconditions.checkNotNull(key);
-            return mProps.get(key);
-        }
-
-        @Override
-        protected void systemPropertiesSet(String key, String value) {
-            Preconditions.checkNotNull(value);
-            Preconditions.checkNotNull(key);
-            mProps.put(key, value);
-        }
-    }
-}
-
diff --git a/services/tests/servicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java b/services/tests/servicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
new file mode 100644
index 0000000..d965f8a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.content.ContentResolver;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
+import android.text.TextUtils;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.test.FakeSettingsProvider;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Tests for {@link SettingsToPropertiesMapper}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SettingsToPropertiesMapperTest {
+    private static final String NAME_VALID_CHARACTERS_REGEX = "^[\\w\\.\\-@:]*$";
+    private static final String[] TEST_MAPPING = new String[] {
+            Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS
+    };
+
+    private TestMapper mTestMapper;
+    private MockContentResolver mMockContentResolver;
+
+    @Before
+    public void setupForEach() {
+        // Use FakeSettingsProvider to not affect global state
+        mMockContentResolver = new MockContentResolver(InstrumentationRegistry.getContext());
+        mMockContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+        mTestMapper = new TestMapper(mMockContentResolver);
+    }
+
+    @Test
+    public void validateRegisteredGlobalSettings() {
+        HashSet<String> hashSet = new HashSet<>();
+        for (String globalSetting : SettingsToPropertiesMapper.sGlobalSettings) {
+            if (hashSet.contains(globalSetting)) {
+                Assert.fail("globalSetting "
+                        + globalSetting
+                        + " is registered more than once in "
+                        + "SettingsToPropertiesMapper.sGlobalSettings.");
+            }
+            hashSet.add(globalSetting);
+            if (TextUtils.isEmpty(globalSetting)) {
+                Assert.fail("empty globalSetting registered.");
+            }
+            if (!globalSetting.matches(NAME_VALID_CHARACTERS_REGEX)) {
+                Assert.fail(globalSetting + " contains invalid characters. "
+                        + "Only alphanumeric characters, '.', '-', '@', ':' and '_' are valid.");
+            }
+        }
+    }
+
+    @Test
+    public void testUpdatePropertiesFromSettings() {
+        Settings.Global.putString(mMockContentResolver,
+                Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, "testValue");
+
+        String systemPropertyName = "persist.device_config.global_settings."
+                + "sqlite_compatibility_wal_flags";
+
+        mTestMapper.updatePropertiesFromSettings();
+        String propValue = mTestMapper.systemPropertiesGet(systemPropertyName);
+        Assert.assertEquals("testValue", propValue);
+
+        Settings.Global.putString(mMockContentResolver,
+                Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, "testValue2");
+        mTestMapper.updatePropertyFromSetting(
+                Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS,
+                systemPropertyName,
+                true);
+        propValue = mTestMapper.systemPropertiesGet(systemPropertyName);
+        Assert.assertEquals("testValue2", propValue);
+
+        Settings.Global.putString(mMockContentResolver,
+                Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, null);
+        mTestMapper.updatePropertyFromSetting(
+                Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS,
+                systemPropertyName,
+                true);
+        propValue = mTestMapper.systemPropertiesGet(systemPropertyName);
+        Assert.assertEquals("", propValue);
+    }
+
+    @Test
+    public void testMakePropertyName() {
+        try {
+            Assert.assertEquals("persist.device_config.test_category.test_flag",
+                    SettingsToPropertiesMapper.makePropertyName("test_category", "test_flag"));
+        } catch (Exception e) {
+            Assert.fail("Unexpected exception: " + e.getMessage());
+        }
+
+        try {
+            Assert.assertEquals(null,
+                    SettingsToPropertiesMapper.makePropertyName("test_category!!!", "test_flag"));
+        } catch (Exception e) {
+            Assert.fail("Unexpected exception: " + e.getMessage());
+        }
+
+        try {
+            Assert.assertEquals(null,
+                    SettingsToPropertiesMapper.makePropertyName("test_category", ".test_flag"));
+        } catch (Exception e) {
+            Assert.fail("Unexpected exception: " + e.getMessage());
+        }
+    }
+
+    @Test
+    public void testUpdatePropertiesFromSettings_PropertyAndSettingNotPresent() {
+        // Test that empty property will not not be set if setting is not set
+        mTestMapper.updatePropertiesFromSettings();
+        String propValue = mTestMapper.systemPropertiesGet("TestProperty");
+        Assert.assertNull("Property should not be set if setting is null", propValue);
+    }
+
+    @Test
+    public void testIsNativeFlagsResetPerformed() {
+        mTestMapper.systemPropertiesSet("device_config.reset_performed", "true");
+        Assert.assertTrue(mTestMapper.isNativeFlagsResetPerformed());
+
+        mTestMapper.systemPropertiesSet("device_config.reset_performed", "false");
+        Assert.assertFalse(mTestMapper.isNativeFlagsResetPerformed());
+
+        mTestMapper.systemPropertiesSet("device_config.reset_performed", "");
+        Assert.assertFalse(mTestMapper.isNativeFlagsResetPerformed());
+    }
+
+    @Test
+    public void testGetResetNativeCategories() {
+        mTestMapper.systemPropertiesSet("device_config.reset_performed", "");
+        Assert.assertEquals(mTestMapper.getResetNativeCategories().length, 0);
+
+        mTestMapper.systemPropertiesSet("device_config.reset_performed", "true");
+        mTestMapper.setFileContent("");
+        Assert.assertEquals(mTestMapper.getResetNativeCategories().length, 0);
+
+        mTestMapper.systemPropertiesSet("device_config.reset_performed", "true");
+        mTestMapper.setFileContent("persist.device_config.category1.flag;"
+                + "persist.device_config.category2.flag;persist.device_config.category3.flag;"
+                + "persist.device_config.category3.flag2");
+        List<String> categories = Arrays.asList(mTestMapper.getResetNativeCategories());
+        Assert.assertEquals(3, categories.size());
+        Assert.assertTrue(categories.contains("category1"));
+        Assert.assertTrue(categories.contains("category2"));
+        Assert.assertTrue(categories.contains("category3"));
+    }
+
+    private static class TestMapper extends SettingsToPropertiesMapper {
+        private final Map<String, String> mProps = new HashMap<>();
+
+        private String mFileContent = "";
+
+        TestMapper(ContentResolver contentResolver) {
+            super(contentResolver, TEST_MAPPING, new String[] {});
+        }
+
+        @Override
+        protected String systemPropertiesGet(String key) {
+            Preconditions.checkNotNull(key);
+            return mProps.get(key);
+        }
+
+        @Override
+        protected void systemPropertiesSet(String key, String value) {
+            Preconditions.checkNotNull(value);
+            Preconditions.checkNotNull(key);
+            mProps.put(key, value);
+        }
+
+        protected void setFileContent(String fileContent) {
+            mFileContent = fileContent;
+        }
+
+        @Override
+        protected String getResetFlagsFileContent() {
+            return mFileContent;
+        }
+    }
+}
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/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
index f4da4b3..c1655bc 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -40,6 +40,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.doNothing;
@@ -108,7 +109,7 @@
         final WindowState imeAppTarget =
                 createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget");
 
-        mWm.mInputMethodTarget = imeAppTarget;
+        mDisplayContent.mInputMethodTarget = imeAppTarget;
 
         assertForAllWindowsOrder(Arrays.asList(
                 mWallpaperWindow,
@@ -124,8 +125,8 @@
     }
 
     @Test
-    public void testForAllWindows_WithChildWindowImeTarget() {
-        mWm.mInputMethodTarget = mChildAppWindowAbove;
+    public void testForAllWindows_WithChildWindowImeTarget() throws Exception {
+        mDisplayContent.mInputMethodTarget = mChildAppWindowAbove;
 
         assertForAllWindowsOrder(Arrays.asList(
                 mWallpaperWindow,
@@ -140,8 +141,8 @@
     }
 
     @Test
-    public void testForAllWindows_WithStatusBarImeTarget() {
-        mWm.mInputMethodTarget = mStatusBarWindow;
+    public void testForAllWindows_WithStatusBarImeTarget() throws Exception {
+        mDisplayContent.mInputMethodTarget = mStatusBarWindow;
 
         assertForAllWindowsOrder(Arrays.asList(
                 mWallpaperWindow,
@@ -568,6 +569,32 @@
         assertFalse(isOptionsPanelAtRight(landscapeDisplay.getDisplayId()));
     }
 
+    @Test
+    public void testInputMethodTargetUpdateWhenSwitchingOnDisplays() {
+        final DisplayContent newDisplay = createNewDisplay();
+
+        final WindowState appWin = createWindow(null, TYPE_APPLICATION, mDisplayContent, "appWin");
+        final WindowState appWin1 = createWindow(null, TYPE_APPLICATION, newDisplay, "appWin1");
+        appWin.setHasSurface(true);
+        appWin1.setHasSurface(true);
+
+        // Set current input method window on default display, make sure the input method target
+        // is appWin & null on the other display.
+        mDisplayContent.setInputMethodWindowLocked(mImeWindow);
+        newDisplay.setInputMethodWindowLocked(null);
+        assertTrue("appWin should be IME target window",
+                appWin.equals(mDisplayContent.mInputMethodTarget));
+        assertNull("newDisplay Ime target: ", newDisplay.mInputMethodTarget);
+
+        // Switch input method window on new display & make sure the input method target also
+        // switched as expected.
+        newDisplay.setInputMethodWindowLocked(mImeWindow);
+        mDisplayContent.setInputMethodWindowLocked(null);
+        assertTrue("appWin1 should be IME target window",
+                appWin1.equals(newDisplay.mInputMethodTarget));
+        assertNull("default display Ime target: ", mDisplayContent.mInputMethodTarget);
+    }
+
     private boolean isOptionsPanelAtRight(int displayId) {
         return (mWm.getPreferredOptionsPanelGravity(displayId) & Gravity.RIGHT) == Gravity.RIGHT;
     }
diff --git a/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java b/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java
index e8d0a06..99deeb9 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java
@@ -53,7 +53,8 @@
     }
 
     @Override
-    public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) throws RemoteException {
+    public void windowFocusChanged(boolean hasFocus, boolean inTouchMode, boolean reportToClient)
+            throws RemoteException {
     }
 
     @Override
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTraversalTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTraversalTests.java
index 2b8b934..fcde08e 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTraversalTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTraversalTests.java
@@ -52,7 +52,7 @@
                 WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD,
                 TYPE_BASE_APPLICATION, mDisplayContent, "splitScreenSecondaryWindow");
 
-        mWm.mInputMethodTarget = splitScreenWindow;
+        mDisplayContent.mInputMethodTarget = splitScreenWindow;
 
         Consumer<WindowState> c = mock(Consumer.class);
         mDisplayContent.forAllWindows(c, false);
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 2abe64d..53858c7 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -192,7 +192,7 @@
                 mWm.getDefaultDisplayContentLocked().mAppTransition
                         .removeAppTransitionTimeoutCallbacks();
                 mWm.mH.removeMessages(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT);
-                mWm.mInputMethodTarget = null;
+                mDisplayContent.mInputMethodTarget = null;
             }
 
             // Wait until everything is really cleaned up.
diff --git a/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java
index 3c8ae3c..3dcea75 100644
--- a/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java
@@ -209,7 +209,7 @@
 
     @Test
     public void testAssignWindowLayers_ForImeWithNoTarget() {
-        mWm.mInputMethodTarget = null;
+        mDisplayContent.mInputMethodTarget = null;
         mDisplayContent.assignChildLayers(mTransaction);
 
         // The Ime has an higher base layer than app windows and lower base layer than system
@@ -227,7 +227,7 @@
     @Test
     public void testAssignWindowLayers_ForImeWithAppTarget() {
         final WindowState imeAppTarget = createWindow("imeAppTarget");
-        mWm.mInputMethodTarget = imeAppTarget;
+        mDisplayContent.mInputMethodTarget = imeAppTarget;
 
         mDisplayContent.assignChildLayers(mTransaction);
 
@@ -253,7 +253,7 @@
                 TYPE_APPLICATION_MEDIA_OVERLAY, imeAppTarget.mToken,
                 "imeAppTargetChildBelowWindow");
 
-        mWm.mInputMethodTarget = imeAppTarget;
+        mDisplayContent.mInputMethodTarget = imeAppTarget;
         mDisplayContent.assignChildLayers(mTransaction);
 
         // Ime should be above all app windows except for child windows that are z-ordered above it
@@ -275,7 +275,7 @@
         final WindowState imeAppTarget = createWindow("imeAppTarget");
         final WindowState appAboveImeTarget = createWindow("appAboveImeTarget");
 
-        mWm.mInputMethodTarget = imeAppTarget;
+        mDisplayContent.mInputMethodTarget = imeAppTarget;
         mDisplayContent.assignChildLayers(mTransaction);
 
         // Ime should be above all app windows except for non-fullscreen app window above it and
@@ -298,7 +298,7 @@
                 mDisplayContent, "imeSystemOverlayTarget",
                 true /* ownerCanAddInternalSystemWindow */);
 
-        mWm.mInputMethodTarget = imeSystemOverlayTarget;
+        mDisplayContent.mInputMethodTarget = imeSystemOverlayTarget;
         mDisplayContent.assignChildLayers(mTransaction);
 
         // The IME target base layer is higher than all window except for the nav bar window, so the
@@ -321,7 +321,7 @@
 
     @Test
     public void testAssignWindowLayers_ForStatusBarImeTarget() {
-        mWm.mInputMethodTarget = mStatusBarWindow;
+        mDisplayContent.mInputMethodTarget = mStatusBarWindow;
         mDisplayContent.assignChildLayers(mTransaction);
 
         assertWindowHigher(mImeWindow, mChildAppWindowAbove);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
index f17a30d..410ab87 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
@@ -72,7 +72,7 @@
         assertTrue(r.getGroupKey().contains(GroupHelper.AUTOGROUP_KEY));
         assertEquals(people, r.getPeopleOverride());
         assertEquals(snoozeCriteria, r.getSnoozeCriteria());
-        assertEquals(smartActions, r.getSmartActions());
+        assertEquals(smartActions, r.getSystemGeneratedSmartActions());
     }
 
     @Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index 9b41fdd..8690110 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -708,14 +708,14 @@
                 true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
                 false /* lights */, false /* defaultLights */, groupId /* group */);
         NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
-        assertNull(record.getSmartActions());
+        assertNull(record.getSystemGeneratedSmartActions());
 
         ArrayList<Notification.Action> smartActions = new ArrayList<>();
         smartActions.add(new Notification.Action.Builder(
                 Icon.createWithResource(getContext(), R.drawable.btn_default),
                 "text", null).build());
-        record.setSmartActions(smartActions);
-        assertEquals(smartActions, record.getSmartActions());
+        record.setSystemGeneratedSmartActions(smartActions);
+        assertEquals(smartActions, record.getSystemGeneratedSmartActions());
     }
 
     @Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 3fe381b..1a218b2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -27,7 +27,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -98,7 +97,7 @@
     private static final UserHandle USER = UserHandle.of(0);
     private static final int UID_O = 1111;
     private static final String SYSTEM_PKG = "android";
-    private static final int SYSTEM_UID= 1000;
+    private static final int SYSTEM_UID = 1000;
     private static final UserHandle USER2 = UserHandle.of(10);
     private static final String TEST_CHANNEL_ID = "test_channel_id";
     private static final String TEST_AUTHORITY = "test";
@@ -1091,6 +1090,158 @@
     }
 
     @Test
+    public void testGetChannelsBypassingDndCount_noChannelsBypassing() throws Exception {
+        assertEquals(0, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
+                USER.getIdentifier()).getList().size());
+    }
+
+    @Test
+    public void testGetChannelsBypassingDnd_noChannelsForUserIdBypassing()
+            throws Exception {
+        int user = 9;
+        NotificationChannel channel = new NotificationChannel("id", "name",
+                NotificationManager.IMPORTANCE_MAX);
+        channel.setBypassDnd(true);
+        mHelper.createNotificationChannel(PKG_N_MR1, 111, channel, true, true);
+
+        assertEquals(0, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
+                user).getList().size());
+    }
+
+    @Test
+    public void testGetChannelsBypassingDndCount_oneChannelBypassing_groupBlocked() {
+        int user = USER.getIdentifier();
+        NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
+        NotificationChannel channel1 = new NotificationChannel("id1", "name1",
+                NotificationManager.IMPORTANCE_MAX);
+        channel1.setBypassDnd(true);
+        channel1.setGroup(ncg.getId());
+        mHelper.createNotificationChannelGroup(PKG_N_MR1, user, ncg,  /* fromTargetApp */ true);
+        mHelper.createNotificationChannel(PKG_N_MR1, user, channel1, true, /*has DND access*/ true);
+
+        assertEquals(1, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
+                user).getList().size());
+
+        // disable group
+        ncg.setBlocked(true);
+        mHelper.createNotificationChannelGroup(PKG_N_MR1, user, ncg,  /* fromTargetApp */ false);
+        assertEquals(0, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
+                user).getList().size());
+    }
+
+    @Test
+    public void testGetChannelsBypassingDndCount_multipleChannelsBypassing() {
+        int user = USER.getIdentifier();
+        NotificationChannel channel1 = new NotificationChannel("id1", "name1",
+                NotificationManager.IMPORTANCE_MAX);
+        NotificationChannel channel2 = new NotificationChannel("id2", "name2",
+                NotificationManager.IMPORTANCE_MAX);
+        NotificationChannel channel3 = new NotificationChannel("id3", "name3",
+                NotificationManager.IMPORTANCE_MAX);
+        channel1.setBypassDnd(true);
+        channel2.setBypassDnd(true);
+        channel3.setBypassDnd(true);
+        // has DND access, so can set bypassDnd attribute
+        mHelper.createNotificationChannel(PKG_N_MR1, user, channel1, true, /*has DND access*/ true);
+        mHelper.createNotificationChannel(PKG_N_MR1, user, channel2, true, true);
+        mHelper.createNotificationChannel(PKG_N_MR1, user, channel3, true, true);
+        assertEquals(3, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
+                user).getList().size());
+
+        // block notifications from this app
+        mHelper.setEnabled(PKG_N_MR1, user, false);
+        assertEquals(0, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
+                user).getList().size());
+
+        // re-enable notifications from this app
+        mHelper.setEnabled(PKG_N_MR1, user, true);
+        assertEquals(3, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
+                user).getList().size());
+
+        // setBypassDnd false for some channels
+        channel1.setBypassDnd(false);
+        channel2.setBypassDnd(false);
+        assertEquals(1, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
+                user).getList().size());
+
+        // setBypassDnd false for rest of the channels
+        channel3.setBypassDnd(false);
+        assertEquals(0, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
+                user).getList().size());
+    }
+
+    @Test
+    public void testGetAppsBypassingDndCount_noAppsBypassing() throws Exception {
+        assertEquals(0, mHelper.getAppsBypassingDndCount(USER.getIdentifier()));
+    }
+
+    @Test
+    public void testGetAppsBypassingDndCount_noAppsForUserIdBypassing() throws Exception {
+        int user = 9;
+        NotificationChannel channel = new NotificationChannel("id", "name",
+                NotificationManager.IMPORTANCE_MAX);
+        channel.setBypassDnd(true);
+        mHelper.createNotificationChannel(PKG_N_MR1, 111, channel, true, true);
+
+        assertEquals(0, mHelper.getAppsBypassingDndCount(user));
+    }
+
+    @Test
+    public void testGetAppsBypassingDndCount_oneChannelBypassing_groupBlocked() {
+        int user = USER.getIdentifier();
+        NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
+        NotificationChannel channel1 = new NotificationChannel("id1", "name1",
+                NotificationManager.IMPORTANCE_MAX);
+        channel1.setBypassDnd(true);
+        channel1.setGroup(ncg.getId());
+        mHelper.createNotificationChannelGroup(PKG_N_MR1, user, ncg,  /* fromTargetApp */ true);
+        mHelper.createNotificationChannel(PKG_N_MR1, user, channel1, true, /*has DND access*/ true);
+
+        assertEquals(1, mHelper.getAppsBypassingDndCount(user));
+
+        // disable group
+        ncg.setBlocked(true);
+        mHelper.createNotificationChannelGroup(PKG_N_MR1, user, ncg,  /* fromTargetApp */ false);
+        assertEquals(0, mHelper.getAppsBypassingDndCount(user));
+    }
+
+    @Test
+    public void testGetAppsBypassingDndCount_oneAppBypassing() {
+        int user = USER.getIdentifier();
+        NotificationChannel channel1 = new NotificationChannel("id1", "name1",
+                NotificationManager.IMPORTANCE_MAX);
+        NotificationChannel channel2 = new NotificationChannel("id2", "name2",
+                NotificationManager.IMPORTANCE_MAX);
+        NotificationChannel channel3 = new NotificationChannel("id3", "name3",
+                NotificationManager.IMPORTANCE_MAX);
+        channel1.setBypassDnd(true);
+        channel2.setBypassDnd(true);
+        channel3.setBypassDnd(true);
+        // has DND access, so can set bypassDnd attribute
+        mHelper.createNotificationChannel(PKG_N_MR1, user, channel1, true, /*has DND access*/ true);
+        mHelper.createNotificationChannel(PKG_N_MR1, user, channel2, true, true);
+        mHelper.createNotificationChannel(PKG_N_MR1, user, channel3, true, true);
+        assertEquals(1, mHelper.getAppsBypassingDndCount(user));
+
+        // block notifications from this app
+        mHelper.setEnabled(PKG_N_MR1, user, false);
+        assertEquals(0, mHelper.getAppsBypassingDndCount(user)); // no apps can bypass dnd
+
+        // re-enable notifications from this app
+        mHelper.setEnabled(PKG_N_MR1, user, true);
+        assertEquals(1, mHelper.getAppsBypassingDndCount(user));
+
+        // setBypassDnd false for some channels
+        channel1.setBypassDnd(false);
+        channel2.setBypassDnd(false);
+        assertEquals(1, mHelper.getAppsBypassingDndCount(user));
+
+        // setBypassDnd false for rest of the channels
+        channel3.setBypassDnd(false);
+        assertEquals(0, mHelper.getAppsBypassingDndCount(user));
+    }
+
+    @Test
     public void testCreateAndDeleteCanChannelsBypassDnd() throws Exception {
         // create notification channel that can't bypass dnd
         // expected result: areChannelsBypassingDnd = false
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java
index f692a57..16dd92f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java
@@ -25,6 +25,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE;
+import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.server.wm.ActivityDisplay.POSITION_TOP;
@@ -57,6 +58,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.graphics.Rect;
+import android.os.Build;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.MediumTest;
@@ -433,6 +435,26 @@
                 eq(activity), eq(null /* targetOptions */));
     }
 
+    /**
+     * Tests home activities that targeted sdk before Q cannot start on secondary display.
+     */
+    @Test
+    public void testStartHomeTargetSdkBeforeQ() throws Exception {
+        final TestActivityDisplay secondDisplay = spy(createNewActivityDisplay());
+        mSupervisor.addChild(secondDisplay, POSITION_TOP);
+        doReturn(true).when(secondDisplay).supportsSystemDecorations();
+
+        final ActivityInfo info = new ActivityInfo();
+        info.launchMode = LAUNCH_MULTIPLE;
+        info.applicationInfo = new ApplicationInfo();
+        info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.Q;
+        assertTrue(mSupervisor.canStartHomeOnDisplay(info, secondDisplay.mDisplayId,
+                false /* allowInstrumenting */));
+
+        info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.P;
+        assertFalse(mSupervisor.canStartHomeOnDisplay(info, secondDisplay.mDisplayId,
+                false /* allowInstrumenting */));
+    }
 
     /**
      * Tests that home activities can be started on the displays that supports system decorations.
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/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 32875da..94d7dbb 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -915,8 +915,12 @@
                 return "SCREEN_INTERACTIVE";
             case UsageEvents.Event.SCREEN_NON_INTERACTIVE:
                 return "SCREEN_NON_INTERACTIVE";
+            case UsageEvents.Event.KEYGUARD_SHOWN:
+                return "KEYGUARD_SHOWN";
+            case UsageEvents.Event.KEYGUARD_HIDDEN:
+                return "KEYGUARD_HIDDEN";
             default:
-                return "UNKNOWN";
+                return "UNKNOWN_TYPE_" + eventType;
         }
     }
 
diff --git a/startop/view_compiler/dex_builder.cc b/startop/view_compiler/dex_builder.cc
index 33df6f9..906d64c 100644
--- a/startop/view_compiler/dex_builder.cc
+++ b/startop/view_compiler/dex_builder.cc
@@ -49,18 +49,27 @@
     case Instruction::Op::kReturn:
       out << "kReturn";
       return out;
+    case Instruction::Op::kReturnObject:
+      out << "kReturnObject";
+      return out;
     case Instruction::Op::kMove:
       out << "kMove";
       return out;
     case Instruction::Op::kInvokeVirtual:
       out << "kInvokeVirtual";
       return out;
+    case Instruction::Op::kInvokeDirect:
+      out << "kInvokeDirect";
+      return out;
     case Instruction::Op::kBindLabel:
       out << "kBindLabel";
       return out;
     case Instruction::Op::kBranchEqz:
       out << "kBranchEqz";
       return out;
+    case Instruction::Op::kNew:
+      out << "kNew";
+      return out;
   }
 }
 
@@ -137,6 +146,9 @@
     entry = Alloc<ir::String>();
     // +1 for null terminator
     entry->data = slicer::MemView{buffer.get(), header_length + string.size() + 1};
+    ::dex::u4 const new_index = dex_file_->strings_indexes.AllocateIndex();
+    dex_file_->strings_map[new_index] = entry;
+    entry->orig_index = new_index;
     string_data_.push_back(std::move(buffer));
   }
   return entry;
@@ -161,6 +173,8 @@
   ir::Type* type = Alloc<ir::Type>();
   type->descriptor = GetOrAddString(descriptor);
   types_by_descriptor_[descriptor] = type;
+  type->orig_index = dex_file_->types_indexes.AllocateIndex();
+  dex_file_->types_map[type->orig_index] = type;
   return type;
 }
 
@@ -217,9 +231,10 @@
       decl_->prototype->param_types != nullptr ? decl_->prototype->param_types->types.size() : 0;
   code->registers = num_registers_ + num_args;
   code->ins_count = num_args;
-  code->outs_count = decl_->prototype->return_type == dex_->GetOrAddType("V") ? 0 : 1;
   EncodeInstructions();
   code->instructions = slicer::ArrayView<const ::dex::u2>(buffer_.data(), buffer_.size());
+  size_t const return_count = decl_->prototype->return_type == dex_->GetOrAddType("V") ? 0 : 1;
+  code->outs_count = std::max(return_count, max_args_);
   method->code = code;
 
   class_->direct_methods.push_back(method);
@@ -240,8 +255,9 @@
 
 void MethodBuilder::BuildReturn() { AddInstruction(Instruction::OpNoArgs(Op::kReturn)); }
 
-void MethodBuilder::BuildReturn(Value src) {
-  AddInstruction(Instruction::OpWithArgs(Op::kReturn, /*destination=*/{}, src));
+void MethodBuilder::BuildReturn(Value src, bool is_object) {
+  AddInstruction(Instruction::OpWithArgs(
+      is_object ? Op::kReturnObject : Op::kReturn, /*destination=*/{}, src));
 }
 
 void MethodBuilder::BuildConst4(Value target, int value) {
@@ -249,6 +265,11 @@
   AddInstruction(Instruction::OpWithArgs(Op::kMove, target, Value::Immediate(value)));
 }
 
+void MethodBuilder::BuildConstString(Value target, const std::string& value) {
+  const ir::String* const dex_string = dex_->GetOrAddString(value);
+  AddInstruction(Instruction::OpWithArgs(Op::kMove, target, Value::String(dex_string->orig_index)));
+}
+
 void MethodBuilder::EncodeInstructions() {
   buffer_.clear();
   for (const auto& instruction : instructions_) {
@@ -259,27 +280,32 @@
 void MethodBuilder::EncodeInstruction(const Instruction& instruction) {
   switch (instruction.opcode()) {
     case Instruction::Op::kReturn:
-      return EncodeReturn(instruction);
+      return EncodeReturn(instruction, ::art::Instruction::RETURN);
+    case Instruction::Op::kReturnObject:
+      return EncodeReturn(instruction, ::art::Instruction::RETURN_OBJECT);
     case Instruction::Op::kMove:
       return EncodeMove(instruction);
     case Instruction::Op::kInvokeVirtual:
-      return EncodeInvokeVirtual(instruction);
+      return EncodeInvoke(instruction, art::Instruction::INVOKE_VIRTUAL);
+    case Instruction::Op::kInvokeDirect:
+      return EncodeInvoke(instruction, art::Instruction::INVOKE_DIRECT);
     case Instruction::Op::kBindLabel:
       return BindLabel(instruction.args()[0]);
     case Instruction::Op::kBranchEqz:
       return EncodeBranch(art::Instruction::IF_EQZ, instruction);
+    case Instruction::Op::kNew:
+      return EncodeNew(instruction);
   }
 }
 
-void MethodBuilder::EncodeReturn(const Instruction& instruction) {
-  DCHECK_EQ(Instruction::Op::kReturn, instruction.opcode());
+void MethodBuilder::EncodeReturn(const Instruction& instruction, ::art::Instruction::Code opcode) {
   DCHECK(!instruction.dest().has_value());
   if (instruction.args().size() == 0) {
-    buffer_.push_back(art::Instruction::RETURN_VOID);
+    Encode10x(art::Instruction::RETURN_VOID);
   } else {
-    DCHECK(instruction.args().size() == 1);
+    DCHECK_EQ(1, instruction.args().size());
     size_t source = RegisterValue(instruction.args()[0]);
-    buffer_.push_back(art::Instruction::RETURN | source << 8);
+    Encode11x(opcode, source);
   }
 }
 
@@ -294,31 +320,43 @@
   if (source.is_immediate()) {
     // TODO: support more registers
     DCHECK_LT(RegisterValue(*instruction.dest()), 16);
-    DCHECK_LT(source.value(), 16);
-    buffer_.push_back(art::Instruction::CONST_4 | (source.value() << 12) |
-                      (RegisterValue(*instruction.dest()) << 8));
+    Encode11n(art::Instruction::CONST_4, RegisterValue(*instruction.dest()), source.value());
+  } else if (source.is_string()) {
+    constexpr size_t kMaxRegisters = 256;
+    DCHECK_LT(RegisterValue(*instruction.dest()), kMaxRegisters);
+    DCHECK_LT(source.value(), 65536);  // make sure we don't need a jumbo string
+    Encode21c(::art::Instruction::CONST_STRING, RegisterValue(*instruction.dest()), source.value());
   } else {
     UNIMPLEMENTED(FATAL);
   }
 }
 
-void MethodBuilder::EncodeInvokeVirtual(const Instruction& instruction) {
-  DCHECK_EQ(Instruction::Op::kInvokeVirtual, instruction.opcode());
+void MethodBuilder::EncodeInvoke(const Instruction& instruction, ::art::Instruction::Code opcode) {
+  constexpr size_t kMaxArgs = 5;
 
-  // TODO: support more than one argument (i.e. the this argument) and change this to DCHECK_GE
-  DCHECK_EQ(1, instruction.args().size());
+  CHECK_LE(instruction.args().size(), kMaxArgs);
 
-  const Value& this_arg = instruction.args()[0];
-
-  size_t real_reg = RegisterValue(this_arg) & 0xf;
-  buffer_.push_back(1 << 12 | art::Instruction::INVOKE_VIRTUAL);
-  buffer_.push_back(instruction.method_id());
-  buffer_.push_back(real_reg);
-
-  if (instruction.dest().has_value()) {
-    real_reg = RegisterValue(*instruction.dest());
-    buffer_.push_back(real_reg << 8 | art::Instruction::MOVE_RESULT);
+  uint8_t arguments[kMaxArgs]{};
+  for (size_t i = 0; i < instruction.args().size(); ++i) {
+    CHECK(instruction.args()[i].is_variable());
+    arguments[i] = RegisterValue(instruction.args()[i]);
   }
+
+  Encode35c(opcode,
+            instruction.args().size(),
+            instruction.method_id(),
+            arguments[0],
+            arguments[1],
+            arguments[2],
+            arguments[3],
+            arguments[4]);
+
+  // If there is a return value, add a move-result instruction
+  if (instruction.dest().has_value()) {
+    Encode11x(art::Instruction::MOVE_RESULT, RegisterValue(*instruction.dest()));
+  }
+
+  max_args_ = std::max(max_args_, instruction.args().size());
 }
 
 // Encodes a conditional branch that tests a single argument.
@@ -331,9 +369,21 @@
   CHECK(branch_target.is_label());
 
   size_t instruction_offset = buffer_.size();
-  buffer_.push_back(op | (RegisterValue(test_value) << 8));
-  size_t field_offset = buffer_.size();
-  buffer_.push_back(LabelValue(branch_target, instruction_offset, field_offset));
+  size_t field_offset = buffer_.size() + 1;
+  Encode21c(
+      op, RegisterValue(test_value), LabelValue(branch_target, instruction_offset, field_offset));
+}
+
+void MethodBuilder::EncodeNew(const Instruction& instruction) {
+  DCHECK_EQ(Instruction::Op::kNew, instruction.opcode());
+  DCHECK(instruction.dest().has_value());
+  DCHECK(instruction.dest()->is_variable());
+  DCHECK_EQ(1, instruction.args().size());
+
+  const Value& type = instruction.args()[0];
+  DCHECK_LT(RegisterValue(*instruction.dest()), 256);
+  DCHECK(type.is_type());
+  Encode21c(::art::Instruction::NEW_INSTANCE, RegisterValue(*instruction.dest()), type.value());
 }
 
 size_t MethodBuilder::RegisterValue(const Value& value) const {
diff --git a/startop/view_compiler/dex_builder.h b/startop/view_compiler/dex_builder.h
index 0744151..adf82bf 100644
--- a/startop/view_compiler/dex_builder.h
+++ b/startop/view_compiler/dex_builder.h
@@ -110,18 +110,22 @@
   static constexpr Value Local(size_t id) { return Value{id, Kind::kLocalRegister}; }
   static constexpr Value Parameter(size_t id) { return Value{id, Kind::kParameter}; }
   static constexpr Value Immediate(size_t value) { return Value{value, Kind::kImmediate}; }
+  static constexpr Value String(size_t value) { return Value{value, Kind::kString}; }
   static constexpr Value Label(size_t id) { return Value{id, Kind::kLabel}; }
+  static constexpr Value Type(size_t id) { return Value{id, Kind::kType}; }
 
   bool is_register() const { return kind_ == Kind::kLocalRegister; }
   bool is_parameter() const { return kind_ == Kind::kParameter; }
   bool is_variable() const { return is_register() || is_parameter(); }
   bool is_immediate() const { return kind_ == Kind::kImmediate; }
+  bool is_string() const { return kind_ == Kind::kString; }
   bool is_label() const { return kind_ == Kind::kLabel; }
+  bool is_type() const { return kind_ == Kind::kType; }
 
   size_t value() const { return value_; }
 
  private:
-  enum class Kind { kLocalRegister, kParameter, kImmediate, kLabel };
+  enum class Kind { kLocalRegister, kParameter, kImmediate, kString, kLabel, kType };
 
   const size_t value_;
   const Kind kind_;
@@ -137,7 +141,16 @@
  public:
   // The operation performed by this instruction. These are virtual instructions that do not
   // correspond exactly to DEX instructions.
-  enum class Op { kReturn, kMove, kInvokeVirtual, kBindLabel, kBranchEqz };
+  enum class Op {
+    kReturn,
+    kReturnObject,
+    kMove,
+    kInvokeVirtual,
+    kInvokeDirect,
+    kBindLabel,
+    kBranchEqz,
+    kNew
+  };
 
   ////////////////////////
   // Named Constructors //
@@ -158,6 +171,12 @@
                                           Value this_arg, T... args) {
     return Instruction{Op::kInvokeVirtual, method_id, dest, this_arg, args...};
   }
+  // For direct calls (basically, constructors).
+  template <typename... T>
+  static inline Instruction InvokeDirect(size_t method_id, std::optional<const Value> dest,
+                                         Value this_arg, T... args) {
+    return Instruction{Op::kInvokeDirect, method_id, dest, this_arg, args...};
+  }
 
   ///////////////
   // Accessors //
@@ -187,6 +206,12 @@
 // Needed for CHECK_EQ, DCHECK_EQ, etc.
 std::ostream& operator<<(std::ostream& out, const Instruction::Op& opcode);
 
+// Keeps track of information needed to manipulate or call a method.
+struct MethodDeclData {
+  size_t id;
+  ir::MethodDecl* decl;
+};
+
 // Tools to help build methods and their bodies.
 class MethodBuilder {
  public:
@@ -210,19 +235,74 @@
 
   // return-void
   void BuildReturn();
-  void BuildReturn(Value src);
+  void BuildReturn(Value src, bool is_object = false);
   // const/4
   void BuildConst4(Value target, int value);
+  void BuildConstString(Value target, const std::string& value);
+  template <typename... T>
+  void BuildNew(Value target, TypeDescriptor type, Prototype constructor, T... args);
 
   // TODO: add builders for more instructions
 
  private:
   void EncodeInstructions();
   void EncodeInstruction(const Instruction& instruction);
-  void EncodeReturn(const Instruction& instruction);
+
+  // Encodes a return instruction. For instructions with no return value, the opcode field is
+  // ignored. Otherwise, this specifies which return instruction will be used (return,
+  // return-object, etc.)
+  void EncodeReturn(const Instruction& instruction, ::art::Instruction::Code opcode);
+
   void EncodeMove(const Instruction& instruction);
-  void EncodeInvokeVirtual(const Instruction& instruction);
+  void EncodeInvoke(const Instruction& instruction, ::art::Instruction::Code opcode);
   void EncodeBranch(art::Instruction::Code op, const Instruction& instruction);
+  void EncodeNew(const Instruction& instruction);
+
+  // Low-level instruction format encoding. See
+  // https://source.android.com/devices/tech/dalvik/instruction-formats for documentation of
+  // formats.
+
+  inline void Encode10x(art::Instruction::Code opcode) {
+    // 00|op
+    buffer_.push_back(opcode);
+  }
+
+  inline void Encode11x(art::Instruction::Code opcode, uint8_t a) {
+    // aa|op
+    buffer_.push_back((a << 8) | opcode);
+  }
+
+  inline void Encode11n(art::Instruction::Code opcode, uint8_t a, int8_t b) {
+    // b|a|op
+
+    // Make sure the fields are in bounds (4 bits for a, 4 bits for b).
+    CHECK_LT(a, 16);
+    CHECK_LE(-8, b);
+    CHECK_LT(b, 8);
+
+    buffer_.push_back(((b & 0xf) << 12) | (a << 8) | opcode);
+  }
+
+  inline void Encode21c(art::Instruction::Code opcode, uint8_t a, uint16_t b) {
+    // aa|op|bbbb
+    buffer_.push_back((a << 8) | opcode);
+    buffer_.push_back(b);
+  }
+
+  inline void Encode35c(art::Instruction::Code opcode, size_t a, uint16_t b, uint8_t c, uint8_t d,
+                        uint8_t e, uint8_t f, uint8_t g) {
+    // a|g|op|bbbb|f|e|d|c
+
+    CHECK_LE(a, 5);
+    CHECK_LT(c, 16);
+    CHECK_LT(d, 16);
+    CHECK_LT(e, 16);
+    CHECK_LT(f, 16);
+    CHECK_LT(g, 16);
+    buffer_.push_back((a << 12) | (g << 8) | opcode);
+    buffer_.push_back(b);
+    buffer_.push_back((f << 12) | (e << 8) | (d << 4) | c);
+  }
 
   // Converts a register or parameter to its DEX register number.
   size_t RegisterValue(const Value& value) const;
@@ -262,6 +342,10 @@
   };
 
   std::vector<LabelData> labels_;
+
+  // During encoding, keep track of the largest number of arguments needed, so we can use it for our
+  // outs count
+  size_t max_args_{0};
 };
 
 // A helper to build class definitions.
@@ -281,12 +365,6 @@
   ir::Class* const class_;
 };
 
-// Keeps track of information needed to manipulate or call a method.
-struct MethodDeclData {
-  size_t id;
-  ir::MethodDecl* decl;
-};
-
 // Builds Dex files from scratch.
 class DexBuilder {
  public:
@@ -355,6 +433,17 @@
   std::map<Prototype, ir::Proto*> proto_map_;
 };
 
+template <typename... T>
+void MethodBuilder::BuildNew(Value target, TypeDescriptor type, Prototype constructor, T... args) {
+  MethodDeclData constructor_data{dex_->GetOrDeclareMethod(type, "<init>", constructor)};
+  // allocate the object
+  ir::Type* type_def = dex_->GetOrAddType(type.descriptor());
+  AddInstruction(
+      Instruction::OpWithArgs(Instruction::Op::kNew, target, Value::Type(type_def->orig_index)));
+  // call the constructor
+  AddInstruction(Instruction::InvokeDirect(constructor_data.id, /*dest=*/{}, target, args...));
+};
+
 }  // namespace dex
 }  // namespace startop
 
diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java
index 169c633..e20f3a9 100644
--- a/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java
+++ b/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java
@@ -50,6 +50,14 @@
   }
 
   @Test
+  public void returnInteger5() throws Exception {
+    ClassLoader loader = loadDexFile("simple.dex");
+    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
+    Method method = clazz.getMethod("returnInteger5");
+    Assert.assertEquals(5, method.invoke(null));
+  }
+
+  @Test
   public void returnParam() throws Exception {
     ClassLoader loader = loadDexFile("simple.dex");
     Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
@@ -82,4 +90,38 @@
     Method method = clazz.getMethod("backwardsBranch");
     Assert.assertEquals(2, method.invoke(null));
   }
+
+  @Test
+  public void returnNull() throws Exception {
+    ClassLoader loader = loadDexFile("simple.dex");
+    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
+    Method method = clazz.getMethod("returnNull");
+    Assert.assertEquals(null, method.invoke(null));
+  }
+
+  @Test
+  public void makeString() throws Exception {
+    ClassLoader loader = loadDexFile("simple.dex");
+    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
+    Method method = clazz.getMethod("makeString");
+    Assert.assertEquals("Hello, World!", method.invoke(null));
+  }
+
+  @Test
+  public void returnStringIfZeroAB() throws Exception {
+    ClassLoader loader = loadDexFile("simple.dex");
+    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
+    Method method = clazz.getMethod("returnStringIfZeroAB", int.class);
+    Assert.assertEquals("a", method.invoke(null, 0));
+    Assert.assertEquals("b", method.invoke(null, 1));
+  }
+
+  @Test
+  public void returnStringIfZeroBA() throws Exception {
+    ClassLoader loader = loadDexFile("simple.dex");
+    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
+    Method method = clazz.getMethod("returnStringIfZeroBA", int.class);
+    Assert.assertEquals("b", method.invoke(null, 0));
+    Assert.assertEquals("a", method.invoke(null, 1));
+  }
 }
diff --git a/startop/view_compiler/dex_testcase_generator.cc b/startop/view_compiler/dex_testcase_generator.cc
index c521bf2..e2bf43bc 100644
--- a/startop/view_compiler/dex_testcase_generator.cc
+++ b/startop/view_compiler/dex_testcase_generator.cc
@@ -53,6 +53,19 @@
   }
   return5.Encode();
 
+  // int return5() { return 5; }
+  auto integer_type{TypeDescriptor::FromClassname("java.lang.Integer")};
+  auto returnInteger5{cbuilder.CreateMethod("returnInteger5", Prototype{integer_type})};
+  [&](MethodBuilder& method) {
+    Value five{method.MakeRegister()};
+    method.BuildConst4(five, 5);
+    Value object{method.MakeRegister()};
+    method.BuildNew(
+        object, integer_type, Prototype{TypeDescriptor::Void(), TypeDescriptor::Int()}, five);
+    method.BuildReturn(object, /*is_object=*/true);
+  }(returnInteger5);
+  returnInteger5.Encode();
+
   // // int returnParam(int x) { return x; }
   auto returnParam{cbuilder.CreateMethod("returnParam",
                                          Prototype{TypeDescriptor::Int(), TypeDescriptor::Int()})};
@@ -138,6 +151,71 @@
   }(backwardsBranch);
   backwardsBranch.Encode();
 
+  // Test that we can make a null value. Basically:
+  //
+  // public static String returnNull() { return null; }
+  MethodBuilder returnNull{cbuilder.CreateMethod("returnNull", Prototype{string_type})};
+  [](MethodBuilder& method) {
+    Value zero = method.MakeRegister();
+    method.BuildConst4(zero, 0);
+    method.BuildReturn(zero, /*is_object=*/true);
+  }(returnNull);
+  returnNull.Encode();
+
+  // Test that we can make String literals. Basically:
+  //
+  // public static String makeString() { return "Hello, World!"; }
+  MethodBuilder makeString{cbuilder.CreateMethod("makeString", Prototype{string_type})};
+  [](MethodBuilder& method) {
+    Value string = method.MakeRegister();
+    method.BuildConstString(string, "Hello, World!");
+    method.BuildReturn(string, /*is_object=*/true);
+  }(makeString);
+  makeString.Encode();
+
+  // Make sure strings are sorted correctly.
+  //
+  // int returnStringIfZeroAB(int x) { if (x == 0) { return "a"; } else { return "b"; } }
+  MethodBuilder returnStringIfZeroAB{
+      cbuilder.CreateMethod("returnStringIfZeroAB", Prototype{string_type, TypeDescriptor::Int()})};
+  [&](MethodBuilder& method) {
+    Value resultIfZero{method.MakeRegister()};
+    Value else_target{method.MakeLabel()};
+    method.AddInstruction(Instruction::OpWithArgs(
+        Instruction::Op::kBranchEqz, /*dest=*/{}, Value::Parameter(0), else_target));
+    // else branch
+    method.BuildConstString(resultIfZero, "b");
+    method.AddInstruction(
+        Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero));
+    // then branch
+    method.AddInstruction(
+        Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, else_target));
+    method.BuildConstString(resultIfZero, "a");
+    method.AddInstruction(
+        Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero));
+    method.Encode();
+  }(returnStringIfZeroAB);
+  // int returnStringIfZeroAB(int x) { if (x == 0) { return "b"; } else { return "a"; } }
+  MethodBuilder returnStringIfZeroBA{
+      cbuilder.CreateMethod("returnStringIfZeroBA", Prototype{string_type, TypeDescriptor::Int()})};
+  [&](MethodBuilder& method) {
+    Value resultIfZero{method.MakeRegister()};
+    Value else_target{method.MakeLabel()};
+    method.AddInstruction(Instruction::OpWithArgs(
+        Instruction::Op::kBranchEqz, /*dest=*/{}, Value::Parameter(0), else_target));
+    // else branch
+    method.BuildConstString(resultIfZero, "a");
+    method.AddInstruction(
+        Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero));
+    // then branch
+    method.AddInstruction(
+        Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, else_target));
+    method.BuildConstString(resultIfZero, "b");
+    method.AddInstruction(
+        Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero));
+    method.Encode();
+  }(returnStringIfZeroBA);
+
   slicer::MemView image{dex_file.CreateImage()};
   std::ofstream out_file(outdir + "/simple.dex");
   out_file.write(image.ptr<const char>(), image.size());
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index fbc54ae6..c7ecdfa 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1320,18 +1320,13 @@
     public static final String KEY_MMS_CLOSE_CONNECTION_BOOL = "mmsCloseConnection";
 
     /**
-     * If carriers require differentiate un-provisioned status: cold sim or out of credit sim
-     * a package name and activity name can be provided to launch a supported carrier application
-     * that check the sim provisioning status
-     * The first element is the package name and the second element is the activity name
-     * of the provisioning app
-     * example:
-     * <item>com.google.android.carrierPackageName</item>
-     * <item>com.google.android.carrierPackageName.CarrierActivityName</item>
-     * The ComponentName of the carrier activity that can setup the device and activate with the
-     * network as part of the Setup Wizard flow.
+     * The flatten {@link android.content.ComponentName componentName} of the activity that can
+     * setup the device and activate with the network per carrier requirements.
+     *
+     * e.g, com.google.android.carrierPackageName/.CarrierActivityName
      * @hide
      */
+    @SystemApi
     public static final String KEY_CARRIER_SETUP_APP_STRING = "carrier_setup_app_string";
 
     /**
@@ -2297,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;
 
@@ -2653,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/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java
index f5dff20..3c5ad84 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -21,16 +21,18 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.Looper;
-import android.os.Message;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.IPhoneStateListener;
 
 import java.lang.ref.WeakReference;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
  * A listener class for monitoring changes in specific telephony states
@@ -231,34 +233,35 @@
     public static final int LISTEN_CARRIER_NETWORK_CHANGE                   = 0x00010000;
 
     /**
-     *  Listen for changes to the sim voice activation state
-     *  @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATING
-     *  @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED
-     *  @see TelephonyManager#SIM_ACTIVATION_STATE_DEACTIVATED
-     *  @see TelephonyManager#SIM_ACTIVATION_STATE_RESTRICTED
-     *  @see TelephonyManager#SIM_ACTIVATION_STATE_UNKNOWN
-     *  {@more}
-     *  Example: TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED indicates voice service has been
-     *  fully activated
+     * Listen for changes to the sim voice activation state
+     * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATING
+     * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED
+     * @see TelephonyManager#SIM_ACTIVATION_STATE_DEACTIVATED
+     * @see TelephonyManager#SIM_ACTIVATION_STATE_RESTRICTED
+     * @see TelephonyManager#SIM_ACTIVATION_STATE_UNKNOWN
+     * {@more}
+     * Example: TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED indicates voice service has been
+     * fully activated
      *
-     *  @see #onVoiceActivationStateChanged
-     *  @hide
+     * @see #onVoiceActivationStateChanged
+     * @hide
      */
+    @SystemApi
     public static final int LISTEN_VOICE_ACTIVATION_STATE                   = 0x00020000;
 
     /**
-     *  Listen for changes to the sim data activation state
-     *  @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATING
-     *  @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED
-     *  @see TelephonyManager#SIM_ACTIVATION_STATE_DEACTIVATED
-     *  @see TelephonyManager#SIM_ACTIVATION_STATE_RESTRICTED
-     *  @see TelephonyManager#SIM_ACTIVATION_STATE_UNKNOWN
-     *  {@more}
-     *  Example: TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED indicates data service has been
-     *  fully activated
+     * Listen for changes to the sim data activation state
+     * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATING
+     * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED
+     * @see TelephonyManager#SIM_ACTIVATION_STATE_DEACTIVATED
+     * @see TelephonyManager#SIM_ACTIVATION_STATE_RESTRICTED
+     * @see TelephonyManager#SIM_ACTIVATION_STATE_UNKNOWN
+     * {@more}
+     * Example: TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED indicates data service has been
+     * fully activated
      *
-     *  @see #onDataActivationStateChanged
-     *  @hide
+     * @see #onDataActivationStateChanged
+     * @hide
      */
     public static final int LISTEN_DATA_ACTIVATION_STATE                   = 0x00040000;
 
@@ -320,7 +323,12 @@
     @UnsupportedAppUsage
     protected Integer mSubId;
 
-    private final Handler mHandler;
+    /**
+     * @hide
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    @UnsupportedAppUsage
+    public final IPhoneStateListener callback;
 
     /**
      * Create a PhoneStateListener for the Phone with the default subscription.
@@ -358,95 +366,27 @@
      */
     @UnsupportedAppUsage
     public PhoneStateListener(Integer subId, Looper looper) {
-        if (DBG) log("ctor: subId=" + subId + " looper=" + looper);
+        this(subId, new HandlerExecutor(new Handler(looper)));
+    }
+
+    /**
+     * Create a PhoneStateListener for the Phone using the specified Executor
+     *
+     * <p>Create a PhoneStateListener with a specified Executor for handling necessary callbacks.
+     * The Executor must not be null.
+     *
+     * @param executor a non-null Executor that will execute callbacks for the PhoneStateListener.
+     */
+    public PhoneStateListener(@NonNull Executor executor) {
+        this(null, executor);
+    }
+
+    private PhoneStateListener(Integer subId, Executor e) {
+        if (e == null) {
+            throw new IllegalArgumentException("PhoneStateListener Executor must be non-null");
+        }
         mSubId = subId;
-        mHandler = new Handler(looper) {
-            public void handleMessage(Message msg) {
-                if (DBG) {
-                    log("mSubId=" + mSubId + " what=0x" + Integer.toHexString(msg.what)
-                            + " msg=" + msg);
-                }
-                switch (msg.what) {
-                    case LISTEN_SERVICE_STATE:
-                        PhoneStateListener.this.onServiceStateChanged((ServiceState)msg.obj);
-                        break;
-                    case LISTEN_SIGNAL_STRENGTH:
-                        PhoneStateListener.this.onSignalStrengthChanged(msg.arg1);
-                        break;
-                    case LISTEN_MESSAGE_WAITING_INDICATOR:
-                        PhoneStateListener.this.onMessageWaitingIndicatorChanged(msg.arg1 != 0);
-                        break;
-                    case LISTEN_CALL_FORWARDING_INDICATOR:
-                        PhoneStateListener.this.onCallForwardingIndicatorChanged(msg.arg1 != 0);
-                        break;
-                    case LISTEN_CELL_LOCATION:
-                        PhoneStateListener.this.onCellLocationChanged((CellLocation)msg.obj);
-                        break;
-                    case LISTEN_CALL_STATE:
-                        PhoneStateListener.this.onCallStateChanged(msg.arg1, (String)msg.obj);
-                        break;
-                    case LISTEN_DATA_CONNECTION_STATE:
-                        PhoneStateListener.this.onDataConnectionStateChanged(msg.arg1, msg.arg2);
-                        PhoneStateListener.this.onDataConnectionStateChanged(msg.arg1);
-                        break;
-                    case LISTEN_DATA_ACTIVITY:
-                        PhoneStateListener.this.onDataActivity(msg.arg1);
-                        break;
-                    case LISTEN_SIGNAL_STRENGTHS:
-                        PhoneStateListener.this.onSignalStrengthsChanged((SignalStrength)msg.obj);
-                        break;
-                    case LISTEN_OTASP_CHANGED:
-                        PhoneStateListener.this.onOtaspChanged(msg.arg1);
-                        break;
-                    case LISTEN_CELL_INFO:
-                        PhoneStateListener.this.onCellInfoChanged((List<CellInfo>)msg.obj);
-                        break;
-                    case LISTEN_PRECISE_CALL_STATE:
-                        PhoneStateListener.this.onPreciseCallStateChanged((PreciseCallState)msg.obj);
-                        break;
-                    case LISTEN_PRECISE_DATA_CONNECTION_STATE:
-                        PhoneStateListener.this.onPreciseDataConnectionStateChanged(
-                                (PreciseDataConnectionState)msg.obj);
-                        break;
-                    case LISTEN_DATA_CONNECTION_REAL_TIME_INFO:
-                        PhoneStateListener.this.onDataConnectionRealTimeInfoChanged(
-                                (DataConnectionRealTimeInfo)msg.obj);
-                        break;
-                    case LISTEN_SRVCC_STATE_CHANGED:
-                        PhoneStateListener.this.onSrvccStateChanged((int) msg.obj);
-                        break;
-                    case LISTEN_VOICE_ACTIVATION_STATE:
-                        PhoneStateListener.this.onVoiceActivationStateChanged((int)msg.obj);
-                        break;
-                    case LISTEN_DATA_ACTIVATION_STATE:
-                        PhoneStateListener.this.onDataActivationStateChanged((int)msg.obj);
-                        break;
-                    case LISTEN_USER_MOBILE_DATA_STATE:
-                        PhoneStateListener.this.onUserMobileDataStateChanged((boolean)msg.obj);
-                        break;
-                    case LISTEN_OEM_HOOK_RAW_EVENT:
-                        PhoneStateListener.this.onOemHookRawEvent((byte[])msg.obj);
-                        break;
-                    case LISTEN_CARRIER_NETWORK_CHANGE:
-                        PhoneStateListener.this.onCarrierNetworkChange((boolean)msg.obj);
-                        break;
-                    case LISTEN_PHYSICAL_CHANNEL_CONFIGURATION:
-                        PhoneStateListener.this.onPhysicalChannelConfigurationChanged(
-                                (List<PhysicalChannelConfig>)msg.obj);
-                        break;
-                    case LISTEN_PHONE_CAPABILITY_CHANGE:
-                        PhoneStateListener.this.onPhoneCapabilityChanged(
-                                (PhoneCapability) msg.obj);
-                        break;
-                    case LISTEN_PREFERRED_DATA_SUBID_CHANGE:
-                        PhoneStateListener.this.onPreferredDataSubIdChanged((int) msg.obj);
-                        break;
-                    case LISTEN_RADIO_POWER_STATE_CHANGED:
-                        PhoneStateListener.this.onRadioPowerStateChanged((int) msg.obj);
-                        break;
-                }
-            }
-        };
+        callback = new IPhoneStateListenerStub(this, e);
     }
 
     /**
@@ -630,8 +570,8 @@
      * @param state is the current SIM voice activation state
      * @hide
      */
-    public void onVoiceActivationStateChanged(int state) {
-
+    @SystemApi
+    public void onVoiceActivationStateChanged(@TelephonyManager.SimActivationState int state) {
     }
 
     /**
@@ -639,8 +579,7 @@
      * @param state is the current SIM data activation state
      * @hide
      */
-    public void onDataActivationStateChanged(int state) {
-
+    public void onDataActivationStateChanged(@TelephonyManager.SimActivationState int state) {
     }
 
     /**
@@ -735,127 +674,217 @@
      */
     private static class IPhoneStateListenerStub extends IPhoneStateListener.Stub {
         private WeakReference<PhoneStateListener> mPhoneStateListenerWeakRef;
+        private Executor mExecutor;
 
-        public IPhoneStateListenerStub(PhoneStateListener phoneStateListener) {
+        IPhoneStateListenerStub(PhoneStateListener phoneStateListener, Executor executor) {
             mPhoneStateListenerWeakRef = new WeakReference<PhoneStateListener>(phoneStateListener);
-        }
-
-        private void send(int what, int arg1, int arg2, Object obj) {
-            PhoneStateListener listener = mPhoneStateListenerWeakRef.get();
-            if (listener != null) {
-                Message.obtain(listener.mHandler, what, arg1, arg2, obj).sendToTarget();
-            }
+            mExecutor = executor;
         }
 
         public void onServiceStateChanged(ServiceState serviceState) {
-            send(LISTEN_SERVICE_STATE, 0, 0, serviceState);
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(() -> psl.onServiceStateChanged(serviceState)));
         }
 
         public void onSignalStrengthChanged(int asu) {
-            send(LISTEN_SIGNAL_STRENGTH, asu, 0, null);
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(() -> psl.onSignalStrengthChanged(asu)));
         }
 
         public void onMessageWaitingIndicatorChanged(boolean mwi) {
-            send(LISTEN_MESSAGE_WAITING_INDICATOR, mwi ? 1 : 0, 0, null);
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(() -> psl.onMessageWaitingIndicatorChanged(mwi)));
         }
 
         public void onCallForwardingIndicatorChanged(boolean cfi) {
-            send(LISTEN_CALL_FORWARDING_INDICATOR, cfi ? 1 : 0, 0, null);
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(() -> psl.onCallForwardingIndicatorChanged(cfi)));
         }
 
         public void onCellLocationChanged(Bundle bundle) {
             CellLocation location = CellLocation.newFromBundle(bundle);
-            send(LISTEN_CELL_LOCATION, 0, 0, location);
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(() -> psl.onCellLocationChanged(location)));
         }
 
         public void onCallStateChanged(int state, String incomingNumber) {
-            send(LISTEN_CALL_STATE, state, 0, incomingNumber);
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(() -> psl.onCallStateChanged(state, incomingNumber)));
         }
 
         public void onDataConnectionStateChanged(int state, int networkType) {
-            send(LISTEN_DATA_CONNECTION_STATE, state, networkType, null);
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(
+                            () -> psl.onDataConnectionStateChanged(state, networkType)));
         }
 
         public void onDataActivity(int direction) {
-            send(LISTEN_DATA_ACTIVITY, direction, 0, null);
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(() -> psl.onDataActivity(direction)));
         }
 
         public void onSignalStrengthsChanged(SignalStrength signalStrength) {
-            send(LISTEN_SIGNAL_STRENGTHS, 0, 0, signalStrength);
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(() -> psl.onSignalStrengthsChanged(signalStrength)));
         }
 
         public void onOtaspChanged(int otaspMode) {
-            send(LISTEN_OTASP_CHANGED, otaspMode, 0, null);
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(() -> psl.onOtaspChanged(otaspMode)));
         }
 
         public void onCellInfoChanged(List<CellInfo> cellInfo) {
-            send(LISTEN_CELL_INFO, 0, 0, cellInfo);
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(() -> psl.onCellInfoChanged(cellInfo)));
         }
 
         public void onPreciseCallStateChanged(PreciseCallState callState) {
-            send(LISTEN_PRECISE_CALL_STATE, 0, 0, callState);
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(() -> psl.onPreciseCallStateChanged(callState)));
         }
 
         public void onPreciseDataConnectionStateChanged(
                 PreciseDataConnectionState dataConnectionState) {
-            send(LISTEN_PRECISE_DATA_CONNECTION_STATE, 0, 0, dataConnectionState);
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(
+                            () -> psl.onPreciseDataConnectionStateChanged(dataConnectionState)));
         }
 
-        public void onDataConnectionRealTimeInfoChanged(
-                DataConnectionRealTimeInfo dcRtInfo) {
-            send(LISTEN_DATA_CONNECTION_REAL_TIME_INFO, 0, 0, dcRtInfo);
+        public void onDataConnectionRealTimeInfoChanged(DataConnectionRealTimeInfo dcRtInfo) {
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(
+                            () -> psl.onDataConnectionRealTimeInfoChanged(dcRtInfo)));
         }
 
         public void onSrvccStateChanged(int state) {
-            send(LISTEN_SRVCC_STATE_CHANGED, 0, 0, state);
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(() -> psl.onSrvccStateChanged(state)));
         }
 
         public void onVoiceActivationStateChanged(int activationState) {
-            send(LISTEN_VOICE_ACTIVATION_STATE, 0, 0, activationState);
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(
+                            () -> psl.onVoiceActivationStateChanged(activationState)));
         }
 
         public void onDataActivationStateChanged(int activationState) {
-            send(LISTEN_DATA_ACTIVATION_STATE, 0, 0, activationState);
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(
+                            () -> psl.onDataActivationStateChanged(activationState)));
         }
 
         public void onUserMobileDataStateChanged(boolean enabled) {
-            send(LISTEN_USER_MOBILE_DATA_STATE, 0, 0, enabled);
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(
+                            () -> psl.onUserMobileDataStateChanged(enabled)));
         }
 
         public void onOemHookRawEvent(byte[] rawData) {
-            send(LISTEN_OEM_HOOK_RAW_EVENT, 0, 0, rawData);
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(() -> psl.onOemHookRawEvent(rawData)));
         }
 
         public void onCarrierNetworkChange(boolean active) {
-            send(LISTEN_CARRIER_NETWORK_CHANGE, 0, 0, active);
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(() -> psl.onCarrierNetworkChange(active)));
         }
 
         public void onPhysicalChannelConfigurationChanged(List<PhysicalChannelConfig> configs) {
-            send(LISTEN_PHYSICAL_CHANNEL_CONFIGURATION, 0, 0, configs);
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(
+                            () -> psl.onPhysicalChannelConfigurationChanged(configs)));
         }
 
         public void onPhoneCapabilityChanged(PhoneCapability capability) {
-            send(LISTEN_PHONE_CAPABILITY_CHANGE, 0, 0, capability);
-        }
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
 
-        public void onPreferredDataSubIdChanged(int subId) {
-            send(LISTEN_PREFERRED_DATA_SUBID_CHANGE, 0, 0, subId);
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(() -> psl.onPhoneCapabilityChanged(capability)));
         }
 
         public void onRadioPowerStateChanged(@TelephonyManager.RadioPowerState int state) {
-            send(LISTEN_RADIO_POWER_STATE_CHANGED, 0, 0, state);
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(() -> psl.onRadioPowerStateChanged(state)));
         }
 
+        public void onPreferredDataSubIdChanged(int subId) {
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(() -> psl.onPreferredDataSubIdChanged(subId)));
+        }
     }
 
-    /**
-     * @hide
-     */
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    @UnsupportedAppUsage
-    public final IPhoneStateListener callback = new IPhoneStateListenerStub(this);
 
     private void log(String s) {
         Rlog.d(LOG_TAG, s);
     }
-}
\ No newline at end of file
+}
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 0937b10..ab80e25 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 31770ec..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>
      *
@@ -2290,7 +2298,7 @@
      * subscription dynamically in multi-SIM devices.
      *
      * @param subId which subscription is preferred to for cellular data. If it's
-     *              {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}, it means
+     *              {@link SubscriptionManager#DEFAULT_SUBSCRIPTION_ID}, it means
      *              it's unset and {@link SubscriptionManager#getDefaultDataSubscriptionId()}
      *              is used to determine which modem is preferred.
      * @hide
@@ -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/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java
index c9cf473..e06c372 100644
--- a/telephony/java/android/telephony/ims/ImsMmTelManager.java
+++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java
@@ -292,7 +292,7 @@
      * Create an instance of ImsManager for the subscription id specified.
      *
      * @param context
-     * @param subId The ID of the subscription that this ImsManager will use.
+     * @param subId The ID of the subscription that this ImsMmTelManager will use.
      * @see android.telephony.SubscriptionManager#getActiveSubscriptionInfoList()
      * @throws IllegalArgumentException if the subscription is invalid or
      *         the subscription ID is not an active subscription.
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
new file mode 100644
index 0000000..916e282
--- /dev/null
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.ims;
+
+import android.Manifest;
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.ims.aidl.IImsConfigCallback;
+import android.telephony.ims.stub.ImsConfigImplBase;
+
+import com.android.internal.telephony.ITelephony;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Manages IMS provisioning and configuration parameters, as well as callbacks for apps to listen
+ * to changes in these configurations.
+ *
+ * Note: IMS provisioning keys are defined per carrier or OEM using OMA-DM or other provisioning
+ * applications and may vary.
+ * @hide
+ */
+@SystemApi
+public class ProvisioningManager {
+
+    /**
+     * Callback for IMS provisioning changes.
+     */
+    public static class Callback {
+
+        private static class CallbackBinder extends IImsConfigCallback.Stub {
+
+            private final Callback mLocalConfigurationCallback;
+            private Executor mExecutor;
+
+            private CallbackBinder(Callback localConfigurationCallback) {
+                mLocalConfigurationCallback = localConfigurationCallback;
+            }
+
+            @Override
+            public final void onIntConfigChanged(int item, int value) {
+                Binder.withCleanCallingIdentity(() ->
+                        mExecutor.execute(() ->
+                                mLocalConfigurationCallback.onProvisioningIntChanged(item, value)));
+            }
+
+            @Override
+            public final void onStringConfigChanged(int item, String value) {
+                Binder.withCleanCallingIdentity(() ->
+                        mExecutor.execute(() ->
+                                mLocalConfigurationCallback.onProvisioningStringChanged(item,
+                                        value)));
+            }
+
+            private void setExecutor(Executor executor) {
+                mExecutor = executor;
+            }
+        }
+
+        private final CallbackBinder mBinder = new CallbackBinder(this);
+
+        /**
+         * Called when a provisioning item has changed.
+         * @param item the IMS provisioning key constant, as defined by the OEM.
+         * @param value the new integer value of the IMS provisioning key.
+         */
+        public void onProvisioningIntChanged(int item, int value) {
+            // Base Implementation
+        }
+
+        /**
+         * Called when a provisioning item has changed.
+         * @param item the IMS provisioning key constant, as defined by the OEM.
+         * @param value the new String value of the IMS configuration constant.
+         */
+        public void onProvisioningStringChanged(int item, String value) {
+            // Base Implementation
+        }
+
+        /**@hide*/
+        public final IImsConfigCallback getBinder() {
+            return mBinder;
+        }
+
+        /**@hide*/
+        public void setExecutor(Executor executor) {
+            mBinder.setExecutor(executor);
+        }
+    }
+
+    private int mSubId;
+
+    /**
+     * Create a new {@link ProvisioningManager} for the subscription specified.
+     * @param context The context that this manager will use.
+     * @param subId The ID of the subscription that this ProvisioningManager will use.
+     * @see android.telephony.SubscriptionManager#getActiveSubscriptionInfoList()
+     * @throws IllegalArgumentException if the subscription is invalid or
+     *         the subscription ID is not an active subscription.
+     */
+    public static ProvisioningManager createForSubscriptionId(Context context, int subId) {
+        if (!SubscriptionManager.isValidSubscriptionId(subId)
+                || !getSubscriptionManager(context).isActiveSubscriptionId(subId)) {
+            throw new IllegalArgumentException("Invalid subscription ID");
+        }
+
+        return new ProvisioningManager(subId);
+    }
+
+    private ProvisioningManager(int subId) {
+        mSubId = subId;
+    }
+
+    /**
+     * Register a new {@link Callback} to listen to changes to changes in
+     * IMS provisioning. Use {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to
+     * Subscription changed events and call
+     * {@link #unregisterProvisioningChangedCallback(Callback)} to clean up after a
+     * subscription is removed.
+     * @param executor The {@link Executor} to call the callback methods on
+     * @param callback The provisioning callbackto be registered.
+     * @see #unregisterProvisioningChangedCallback(Callback)
+     * @see SubscriptionManager.OnSubscriptionsChangedListener
+     */
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public void registerProvisioningChangedCallback(@CallbackExecutor Executor executor,
+            @NonNull Callback callback) {
+        callback.setExecutor(executor);
+        try {
+            getITelephony().registerImsProvisioningChangedCallback(mSubId,
+                    callback.getBinder());
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Unregister an existing {@link Callback}. Ensure to call this method when cleaning
+     * up to avoid memory leaks or when the subscription is removed.
+     * @param callback The existing {@link Callback} to be removed.
+     * @see #registerProvisioningChangedCallback(Executor, Callback)
+     */
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public void unregisterProvisioningChangedCallback(@NonNull Callback callback) {
+        try {
+            getITelephony().unregisterImsProvisioningChangedCallback(mSubId,
+                    callback.getBinder());
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Query for the integer value associated with the provided key.
+     * @param key An integer that represents the provisioning key, which is defined by the OEM.
+     * @return an integer value for the provided key.
+     * @throws IllegalArgumentException if the key provided was invalid.
+     */
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public int getProvisioningIntValue(int key) {
+        try {
+            return getITelephony().getImsProvisioningInt(mSubId, key);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Query for the String value associated with the provided key.
+     * @param key An integer that represents the provisioning key, which is defined by the OEM.
+     * @return a String value for the provided key, or {@code null} if the key doesn't exist.
+     * @throws IllegalArgumentException if the key provided was invalid.
+     */
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public String getProvisioningStringValue(int key) {
+        try {
+            return getITelephony().getImsProvisioningString(mSubId, key);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Set the integer value associated with the provided key.
+     * @param key An integer that represents the provisioning key, which is defined by the OEM.
+     * @param value a integer value for the provided key.
+     * @return the result of setting the configuration value.
+     */
+    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    public @ImsConfigImplBase.SetConfigResult int setProvisioningIntValue(int key, int value) {
+        try {
+            return getITelephony().setImsProvisioningInt(mSubId, key, value);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Set the String value associated with the provided key.
+     *
+     * @param key An integer that represents the provisioning key, which is defined by the OEM.
+     * @param value a String value for the provided key.
+     * @return the result of setting the configuration value.
+     */
+    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    public @ImsConfigImplBase.SetConfigResult int setProvisioningStringValue(int key,
+            String value) {
+        try {
+            return getITelephony().setImsProvisioningString(mSubId, key, value);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    private static SubscriptionManager getSubscriptionManager(Context context) {
+        SubscriptionManager manager = context.getSystemService(SubscriptionManager.class);
+        if (manager == null) {
+            throw new RuntimeException("Could not find SubscriptionManager.");
+        }
+        return manager;
+    }
+
+    private static ITelephony getITelephony() {
+        ITelephony binder = ITelephony.Stub.asInterface(
+                ServiceManager.getService(Context.TELEPHONY_SERVICE));
+        if (binder == null) {
+            throw new RuntimeException("Could not find Telephony Service.");
+        }
+        return binder;
+    }
+}
diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
index dcd7ea7..321bfff 100644
--- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
@@ -16,9 +16,9 @@
 
 package android.telephony.ims.stub;
 
+import android.annotation.IntDef;
 import android.annotation.SystemApi;
 import android.content.Context;
-import android.content.Intent;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.telephony.ims.aidl.IImsConfig;
@@ -28,6 +28,8 @@
 import com.android.ims.ImsConfig;
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.util.HashMap;
 
@@ -215,41 +217,6 @@
     }
 
     /**
-     * Callback that the framework uses for receiving Configuration change updates.
-     * {@hide}
-     */
-    public static class Callback extends IImsConfigCallback.Stub {
-
-        @Override
-        public final void onIntConfigChanged(int item, int value) throws RemoteException {
-            onConfigChanged(item, value);
-        }
-
-        @Override
-        public final void onStringConfigChanged(int item, String value) throws RemoteException {
-            onConfigChanged(item, value);
-        }
-
-        /**
-         * Called when the IMS configuration has changed.
-         * @param item the IMS configuration key constant, as defined in ImsConfig.
-         * @param value the new integer value of the IMS configuration constant.
-         */
-        public void onConfigChanged(int item, int value) {
-            // Base Implementation
-        }
-
-        /**
-         * Called when the IMS configuration has changed.
-         * @param item the IMS configuration key constant, as defined in ImsConfig.
-         * @param value the new String value of the IMS configuration constant.
-         */
-        public void onConfigChanged(int item, String value) {
-            // Base Implementation
-        }
-    }
-
-    /**
      * The configuration requested resulted in an unknown result. This may happen if the
      * IMS configurations are unavailable.
      */
@@ -263,6 +230,16 @@
      */
     public static final int CONFIG_RESULT_FAILED =  1;
 
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "CONFIG_RESULT_", value = {
+            CONFIG_RESULT_SUCCESS,
+            CONFIG_RESULT_FAILED
+    })
+    public @interface SetConfigResult {}
+
     private final RemoteCallbackList<IImsConfigCallback> mCallbacks = new RemoteCallbackList<>();
     ImsConfigStub mImsConfigStub;
 
@@ -279,17 +256,16 @@
     }
 
     /**
-     * Adds a {@link Callback} to the list of callbacks notified when a value in the configuration
-     * changes.
+     * Adds a {@link android.telephony.ims.ProvisioningManager.Callback} to the list of callbacks
+     * notified when a value in the configuration changes.
      * @param c callback to add.
      */
     private void addImsConfigCallback(IImsConfigCallback c) {
         mCallbacks.register(c);
     }
     /**
-     * Removes a {@link Callback} to the list of callbacks notified when a value in the
-     * configuration changes.
-     *
+     * Removes a {@link android.telephony.ims.ProvisioningManager.Callback} to the list of callbacks
+     * notified when a value in the configuration changes.
      * @param c callback to remove.
      */
     private void removeImsConfigCallback(IImsConfigCallback c) {
@@ -370,10 +346,9 @@
      *
      * @param item an integer key.
      * @param value an integer containing the configuration value.
-     * @return the result of setting the configuration value, defined as either
-     * {@link #CONFIG_RESULT_FAILED} or {@link #CONFIG_RESULT_SUCCESS}.
+     * @return the result of setting the configuration value.
      */
-    public int setConfig(int item, int value) {
+    public @SetConfigResult int setConfig(int item, int value) {
         // Base Implementation - To be overridden.
         return CONFIG_RESULT_FAILED;
     }
@@ -383,10 +358,9 @@
      *
      * @param item an integer key.
      * @param value a String containing the new configuration value.
-     * @return Result of setting the configuration value, defined as either
-     * {@link #CONFIG_RESULT_FAILED} or {@link #CONFIG_RESULT_SUCCESS}.
+     * @return Result of setting the configuration value.
      */
-    public int setConfig(int item, String value) {
+    public @SetConfigResult int setConfig(int item, String value) {
         // Base Implementation - To be overridden.
         return CONFIG_RESULT_FAILED;
     }
diff --git a/telephony/java/com/android/ims/ImsConfig.java b/telephony/java/com/android/ims/ImsConfig.java
index 90e9880..71a2174 100644
--- a/telephony/java/com/android/ims/ImsConfig.java
+++ b/telephony/java/com/android/ims/ImsConfig.java
@@ -16,12 +16,17 @@
 
 package com.android.ims;
 
-import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.telephony.Rlog;
 import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ProvisioningManager;
 import android.telephony.ims.aidl.IImsConfig;
-import android.telephony.ims.stub.ImsConfigImplBase;
+import android.telephony.ims.aidl.IImsConfigCallback;
+
+import java.util.concurrent.Executor;
 
 /**
  * Provides APIs to get/set the IMS service feature/capability/parameters.
@@ -29,8 +34,10 @@
  * 1) Items provisioned by the operator.
  * 2) Items configured by user. Mainly service feature class.
  *
+ * @deprecated Use {@link  ProvisioningManager} to change these configurations in the ImsService.
  * @hide
  */
+@Deprecated
 public class ImsConfig {
     private static final String TAG = "ImsConfig";
     private boolean DBG = true;
@@ -46,7 +53,7 @@
 
     /**
      * Broadcast action: the configuration was changed
-     * @deprecated Use {@link ImsConfig#addConfigCallback(ImsConfigImplBase.Callback)} instead.
+     * @deprecated Use {@link android.telephony.ims.ProvisioningManager.Callback} instead.
      * @hide
      */
     public static final String ACTION_IMS_CONFIG_CHANGED =
@@ -673,13 +680,25 @@
     }
 
     /**
-     * Adds a {@link ImsConfigImplBase.Callback} to the ImsService to notify when a Configuration
+     * Adds a {@link ProvisioningManager.Callback} to the ImsService to notify when a Configuration
      * item has changed.
      *
-     * Make sure to call {@link #removeConfigCallback(ImsConfigImplBase.Callback)} when finished
+     * Make sure to call {@link #removeConfigCallback(IImsConfigCallback)} when finished
      * using this callback.
      */
-    public void addConfigCallback(ImsConfigImplBase.Callback callback) throws ImsException {
+    public void addConfigCallback(ProvisioningManager.Callback callback) throws ImsException {
+        callback.setExecutor(getThreadExecutor());
+        addConfigCallback(callback.getBinder());
+    }
+
+    /**
+     * Adds a {@link IImsConfigCallback} to the ImsService to notify when a Configuration
+     * item has changed.
+     *
+     * Make sure to call {@link #removeConfigCallback(IImsConfigCallback)} when finished
+     * using this callback.
+     */
+    public void addConfigCallback(IImsConfigCallback callback) throws ImsException {
         if (DBG) Rlog.d(TAG, "addConfigCallback: " + callback);
         try {
             miConfig.addImsConfigCallback(callback);
@@ -690,10 +709,9 @@
     }
 
     /**
-     * Removes a {@link ImsConfigImplBase.Callback} from the ImsService that was previously added
-     * by {@link #addConfigCallback(ImsConfigImplBase.Callback)}.
+     * Removes an existing {@link IImsConfigCallback} from the ImsService.
      */
-    public void removeConfigCallback(ImsConfigImplBase.Callback callback) throws ImsException {
+    public void removeConfigCallback(IImsConfigCallback callback) throws ImsException {
         if (DBG) Rlog.d(TAG, "removeConfigCallback: " + callback);
         try {
             miConfig.removeImsConfigCallback(callback);
@@ -709,4 +727,11 @@
     public boolean isBinderAlive() {
         return miConfig.asBinder().isBinderAlive();
     }
+
+    private Executor getThreadExecutor() {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+        return new HandlerExecutor(new Handler(Looper.myLooper()));
+    }
 }
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 3aaa323..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;
@@ -40,6 +42,7 @@
 import android.telephony.VisualVoicemailSmsFilterSettings;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
 import android.telephony.ims.aidl.IImsConfig;
+import android.telephony.ims.aidl.IImsConfigCallback;
 import android.telephony.ims.aidl.IImsMmTelFeature;
 import android.telephony.ims.aidl.IImsRcsFeature;
 import android.telephony.ims.aidl.IImsRegistration;
@@ -506,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);
@@ -1569,24 +1587,24 @@
     /**
      * Adds an IMS registration status callback for the subscription id specified.
      */
-    oneway void addImsRegistrationCallback(int subId, IImsRegistrationCallback c,
+    void addImsRegistrationCallback(int subId, IImsRegistrationCallback c,
             String callingPackage);
      /**
       * Removes an existing IMS registration status callback for the subscription specified.
       */
-    oneway void removeImsRegistrationCallback(int subId, IImsRegistrationCallback c,
+    void removeImsRegistrationCallback(int subId, IImsRegistrationCallback c,
             String callingPackage);
 
     /**
      * Adds an IMS MmTel capabilities callback for the subscription specified.
      */
-    oneway void addMmTelCapabilityCallback(int subId, IImsCapabilityCallback c,
+    void addMmTelCapabilityCallback(int subId, IImsCapabilityCallback c,
             String callingPackage);
 
     /**
      * Removes an existing IMS MmTel capabilities callback for the subscription specified.
      */
-    oneway void removeMmTelCapabilityCallback(int subId, IImsCapabilityCallback c,
+    void removeMmTelCapabilityCallback(int subId, IImsCapabilityCallback c,
             String callingPackage);
 
     /**
@@ -1691,4 +1709,34 @@
      * Return a list of certs in hex string from loaded carrier privileges access rules.
      */
     List<String> getCertsFromCarrierPrivilegeAccessRules(int subId);
+
+    /**
+     * Register an IMS provisioning change callback with Telephony.
+     */
+    void registerImsProvisioningChangedCallback(int subId, IImsConfigCallback callback);
+
+    /**
+     * unregister an existing IMS provisioning change callback.
+     */
+    void unregisterImsProvisioningChangedCallback(int subId, IImsConfigCallback callback);
+
+    /**
+     * Return an integer containing the provisioning value for the specified provisioning key.
+     */
+    int getImsProvisioningInt(int subId, int key);
+
+    /**
+     * return a String containing the provisioning value for the provisioning key specified.
+     */
+    String getImsProvisioningString(int subId, int key);
+
+    /**
+     * Set the integer provisioning value for the provisioning key specified.
+     */
+    int setImsProvisioningInt(int subId, int key, int value);
+
+    /**
+     * Set the String provisioning value for the provisioning key specified.
+     */
+    int setImsProvisioningString(int subId, int key, String value);
 }
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 954b51f..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
@@ -4256,27 +4284,21 @@
 
     /**
      * @return true if this device supports WPA3-Personal SAE
-     * @hide
      */
-    @SystemApi
     public boolean isWpa3SaeSupported() {
         return isFeatureSupported(WIFI_FEATURE_WPA3_SAE);
     }
 
     /**
      * @return true if this device supports WPA3-Enterprise Suite-B-192
-     * @hide
      */
-    @SystemApi
     public boolean isWpa3SuiteBSupported() {
         return isFeatureSupported(WIFI_FEATURE_WPA3_SUITE_B);
     }
 
     /**
      * @return true if this device supports Wi-Fi Enhanced Open (OWE)
-     * @hide
      */
-    @SystemApi
     public boolean isOweSupported() {
         return isFeatureSupported(WIFI_FEATURE_OWE);
     }
diff --git a/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java b/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java
index 55fde4ca..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/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
index 71d4173..26bdb18 100644
--- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -20,10 +20,10 @@
 import android.net.wifi.hotspot2.pps.HomeSp;
 import android.net.wifi.hotspot2.pps.Policy;
 import android.net.wifi.hotspot2.pps.UpdateParameter;
+import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.Log;
-import android.os.Parcel;
 
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
@@ -467,24 +467,54 @@
     }
 
     /**
-     * Validate the configuration data.
+     * Validate the R1 configuration data.
      *
      * @return true on success or false on failure
      * @hide
      */
     public boolean validate() {
-        if (mHomeSp == null || !mHomeSp.validate()) {
-            return false;
-        }
-        if (mCredential == null || !mCredential.validate()) {
-            return false;
-        }
-        if (mPolicy != null && !mPolicy.validate()) {
-            return false;
-        }
+        // Optional: PerProviderSubscription/<X+>/SubscriptionUpdate
         if (mSubscriptionUpdate != null && !mSubscriptionUpdate.validate()) {
             return false;
         }
+        return validateForCommonR1andR2(true);
+    }
+
+    /**
+     * Validate the R2 configuration data.
+     *
+     * @return true on success or false on failure
+     * @hide
+     */
+    public boolean validateForR2() {
+        // Required: PerProviderSubscription/UpdateIdentifier
+        if (mUpdateIdentifier == Integer.MIN_VALUE) {
+            return false;
+        }
+
+        // Required: PerProviderSubscription/<X+>/SubscriptionUpdate
+        if (mSubscriptionUpdate == null || !mSubscriptionUpdate.validate()) {
+            return false;
+        }
+        return validateForCommonR1andR2(false);
+    }
+
+    private boolean validateForCommonR1andR2(boolean isR1) {
+        // Required: PerProviderSubscription/<X+>/HomeSP
+        if (mHomeSp == null || !mHomeSp.validate()) {
+            return false;
+        }
+
+        // Required: PerProviderSubscription/<X+>/Credential
+        if (mCredential == null || !mCredential.validate(isR1)) {
+            return false;
+        }
+
+        // Optional: PerProviderSubscription/<X+>/Policy
+        if (mPolicy != null && !mPolicy.validate()) {
+            return false;
+        }
+
         if (mTrustRootCertList != null) {
             for (Map.Entry<String, byte[]> entry : mTrustRootCertList.entrySet()) {
                 String url = entry.getKey();
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
index e8fcbfd..7689fc3 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
@@ -18,8 +18,8 @@
 
 import android.net.wifi.EAPConstants;
 import android.net.wifi.ParcelUtil;
-import android.os.Parcelable;
 import android.os.Parcel;
+import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -1019,10 +1019,11 @@
     /**
      * Validate the configuration data.
      *
+     * @param isR1 {@code true} if the configuration is for R1
      * @return true on success or false on failure
      * @hide
      */
-    public boolean validate() {
+    public boolean validate(boolean isR1) {
         if (TextUtils.isEmpty(mRealm)) {
             Log.d(TAG, "Missing realm");
             return false;
@@ -1035,11 +1036,11 @@
 
         // Verify the credential.
         if (mUserCredential != null) {
-            if (!verifyUserCredential()) {
+            if (!verifyUserCredential(isR1)) {
                 return false;
             }
         } else if (mCertCredential != null) {
-            if (!verifyCertCredential()) {
+            if (!verifyCertCredential(isR1)) {
                 return false;
             }
         } else if (mSimCredential != null) {
@@ -1081,9 +1082,10 @@
     /**
      * Verify user credential.
      *
+     * @param isR1 {@code true} if credential is for R1
      * @return true if user credential is valid, false otherwise.
      */
-    private boolean verifyUserCredential() {
+    private boolean verifyUserCredential(boolean isR1) {
         if (mUserCredential == null) {
             Log.d(TAG, "Missing user credential");
             return false;
@@ -1095,7 +1097,10 @@
         if (!mUserCredential.validate()) {
             return false;
         }
-        if (mCaCertificate == null) {
+
+        // CA certificate is required for R1 Passpoint profile.
+        // For R2, it is downloaded using cert URL provided in PPS MO after validation completes.
+        if (isR1 && mCaCertificate == null) {
             Log.d(TAG, "Missing CA Certificate for user credential");
             return false;
         }
@@ -1106,9 +1111,10 @@
      * Verify certificate credential, which is used for EAP-TLS.  This will verify
      * that the necessary client key and certificates are provided.
      *
+     * @param isR1 {@code true} if credential is for R1
      * @return true if certificate credential is valid, false otherwise.
      */
-    private boolean verifyCertCredential() {
+    private boolean verifyCertCredential(boolean isR1) {
         if (mCertCredential == null) {
             Log.d(TAG, "Missing certificate credential");
             return false;
@@ -1123,7 +1129,9 @@
         }
 
         // Verify required key and certificates for certificate credential.
-        if (mCaCertificate == null) {
+        // CA certificate is required for R1 Passpoint profile.
+        // For R2, it is downloaded using cert URL provided in PPS MO after validation completes.
+        if (isR1 && mCaCertificate == null) {
             Log.d(TAG, "Missing CA Certificate for certificate credential");
             return false;
         }
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
index e6892be..f58a006 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
@@ -491,6 +491,17 @@
     /** @hide */
     public static final int FACTORY_RESET_SUCCEEDED                 = BASE + 84;
 
+    /** @hide */
+    public static final int REQUEST_ONGOING_PEER_CONFIG             = BASE + 85;
+    /** @hide */
+    public static final int RESPONSE_ONGOING_PEER_CONFIG            = BASE + 86;
+    /** @hide */
+    public static final int SET_ONGOING_PEER_CONFIG                 = BASE + 87;
+    /** @hide */
+    public static final int SET_ONGOING_PEER_CONFIG_FAILED          = BASE + 88;
+    /** @hide */
+    public static final int SET_ONGOING_PEER_CONFIG_SUCCEEDED       = BASE + 89;
+
     /**
      * Create a new WifiP2pManager instance. Applications use
      * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
@@ -680,6 +691,18 @@
     }
 
     /**
+     * Interface for callback invocation when ongoing peer info is available
+     * @hide
+     */
+    public interface OngoingPeerInfoListener {
+        /**
+         * The requested ongoing WifiP2pConfig is available
+         * @param peerConfig WifiP2pConfig for current connecting session
+         */
+        void onOngoingPeerAvailable(WifiP2pConfig peerConfig);
+    }
+
+    /**
      * A channel that connects the application to the Wifi p2p framework.
      * Most p2p operations require a Channel as an argument. An instance of Channel is obtained
      * by doing a call on {@link #initialize}
@@ -787,6 +810,7 @@
                     case SET_CHANNEL_FAILED:
                     case REPORT_NFC_HANDOVER_FAILED:
                     case FACTORY_RESET_FAILED:
+                    case SET_ONGOING_PEER_CONFIG_FAILED:
                         if (listener != null) {
                             ((ActionListener) listener).onFailure(message.arg1);
                         }
@@ -814,6 +838,7 @@
                     case SET_CHANNEL_SUCCEEDED:
                     case REPORT_NFC_HANDOVER_SUCCEEDED:
                     case FACTORY_RESET_SUCCEEDED:
+                    case SET_ONGOING_PEER_CONFIG_SUCCEEDED:
                         if (listener != null) {
                             ((ActionListener) listener).onSuccess();
                         }
@@ -857,6 +882,13 @@
                                     .onHandoverMessageAvailable(handoverMessage);
                         }
                         break;
+                    case RESPONSE_ONGOING_PEER_CONFIG:
+                        WifiP2pConfig peerConfig = (WifiP2pConfig) message.obj;
+                        if (listener != null) {
+                            ((OngoingPeerInfoListener) listener)
+                                    .onOngoingPeerAvailable(peerConfig);
+                        }
+                        break;
                     default:
                         Log.d(TAG, "Ignored " + message);
                         break;
@@ -1536,6 +1568,7 @@
 
     /**
      * Removes all saved p2p groups.
+     *
      * @param c is the channel created at {@link #initialize}.
      * @param listener for callback on success or failure. Can be null.
      * @hide
@@ -1550,4 +1583,37 @@
                 callingPackage);
     }
 
+    /**
+     * Request saved WifiP2pConfig which used for an ongoing peer connection
+     *
+     * @param c is the channel created at {@link #initialize}
+     * @param listener for callback when ongoing peer config updated. Can't be null.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
+    public void requestOngoingPeerConfig(@NonNull Channel c,
+            @NonNull OngoingPeerInfoListener listener) {
+        checkChannel(c);
+        c.mAsyncChannel.sendMessage(REQUEST_ONGOING_PEER_CONFIG,
+                Binder.getCallingUid(), c.putListener(listener));
+    }
+
+     /**
+     * Set saved WifiP2pConfig which used for an ongoing peer connection
+     *
+     * @param c is the channel created at {@link #initialize}
+     * @param config used for change an ongoing peer connection
+     * @param listener for callback when ongoing peer config updated. Can be null.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
+    public void setOngoingPeerConfig(@NonNull Channel c, @NonNull WifiP2pConfig config,
+            @Nullable ActionListener listener) {
+        checkChannel(c);
+        checkP2pConfig(config);
+        c.mAsyncChannel.sendMessage(SET_ONGOING_PEER_CONFIG, 0,
+                c.putListener(listener), config);
+    }
 }
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();
 
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
index 940adc8..775ce21 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
@@ -146,6 +146,7 @@
      */
     private static PasspointConfiguration createConfig() {
         PasspointConfiguration config = new PasspointConfiguration();
+        config.setUpdateIdentifier(1234);
         config.setHomeSp(createHomeSp());
         config.setCredential(createCredential());
         config.setPolicy(createPolicy());
@@ -273,18 +274,37 @@
     @Test
     public void validateDefaultConfig() throws Exception {
         PasspointConfiguration config = new PasspointConfiguration();
+
         assertFalse(config.validate());
+        assertFalse(config.validateForR2());
     }
 
     /**
-     * Verify that a configuration contained all fields is valid.
+     * Verify that a configuration containing all fields is valid for R1/R2.
      *
      * @throws Exception
      */
     @Test
     public void validateFullConfig() throws Exception {
         PasspointConfiguration config = createConfig();
+
         assertTrue(config.validate());
+        assertTrue(config.validateForR2());
+    }
+
+    /**
+     * Verify that a configuration containing all fields except for UpdateIdentifier is valid for
+     * R1, but invalid for R2.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateFullConfigWithoutUpdateIdentifier() throws Exception {
+        PasspointConfiguration config = createConfig();
+        config.setUpdateIdentifier(Integer.MIN_VALUE);
+
+        assertTrue(config.validate());
+        assertFalse(config.validateForR2());
     }
 
     /**
@@ -296,7 +316,9 @@
     public void validateConfigWithoutCredential() throws Exception {
         PasspointConfiguration config = createConfig();
         config.setCredential(null);
+
         assertFalse(config.validate());
+        assertFalse(config.validateForR2());
     }
 
     /**
@@ -308,12 +330,14 @@
     public void validateConfigWithoutHomeSp() throws Exception {
         PasspointConfiguration config = createConfig();
         config.setHomeSp(null);
+
         assertFalse(config.validate());
+        assertFalse(config.validateForR2());
     }
 
     /**
      * Verify that a configuration without Policy is valid, since Policy configurations
-     * are optional (applied for Hotspot 2.0 Release only).
+     * are optional for R1 and R2.
      *
      * @throws Exception
      */
@@ -321,12 +345,14 @@
     public void validateConfigWithoutPolicy() throws Exception {
         PasspointConfiguration config = createConfig();
         config.setPolicy(null);
+
         assertTrue(config.validate());
+        assertTrue(config.validateForR2());
     }
 
     /**
-     * Verify that a configuration without subscription update is valid, since subscription
-     * update configurations are optional (applied for Hotspot 2.0 Release only).
+     * Verify that a configuration without subscription update is valid for R1 and invalid for R2,
+     * since subscription update configuration is only applicable for R2.
      *
      * @throws Exception
      */
@@ -334,7 +360,9 @@
     public void validateConfigWithoutSubscriptionUpdate() throws Exception {
         PasspointConfiguration config = createConfig();
         config.setSubscriptionUpdate(null);
+
         assertTrue(config.validate());
+        assertFalse(config.validateForR2());
     }
 
     /**
@@ -352,12 +380,15 @@
         trustRootCertList.put(new String(rawUrlBytes, StandardCharsets.UTF_8),
                 new byte[CERTIFICATE_FINGERPRINT_BYTES]);
         config.setTrustRootCertList(trustRootCertList);
+
         assertFalse(config.validate());
 
         trustRootCertList = new HashMap<>();
         trustRootCertList.put(null, new byte[CERTIFICATE_FINGERPRINT_BYTES]);
         config.setTrustRootCertList(trustRootCertList);
+
         assertFalse(config.validate());
+        assertFalse(config.validateForR2());
     }
 
     /**
@@ -382,6 +413,7 @@
         trustRootCertList.put("test.cert.com", null);
         config.setTrustRootCertList(trustRootCertList);
         assertFalse(config.validate());
+        assertFalse(config.validateForR2());
     }
 
     /**
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
index c5ad7de..c07db6c 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
@@ -24,14 +24,13 @@
 import android.os.Parcel;
 import android.support.test.filters.SmallTest;
 
+import org.junit.Test;
+
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.X509Certificate;
-import java.util.Arrays;
-
-import org.junit.Test;
 
 /**
  * Unit tests for {@link android.net.wifi.hotspot2.pps.CredentialTest}.
@@ -169,7 +168,12 @@
     @Test
     public void validateUserCredential() throws Exception {
         Credential cred = createCredentialWithUserCredential();
-        assertTrue(cred.validate());
+
+        // For R1 validation
+        assertTrue(cred.validate(true));
+
+        // For R2 validation
+        assertTrue(cred.validate(false));
     }
 
     /**
@@ -181,7 +185,12 @@
     public void validateUserCredentialWithoutCaCert() throws Exception {
         Credential cred = createCredentialWithUserCredential();
         cred.setCaCertificate(null);
-        assertFalse(cred.validate());
+
+        // For R1 validation
+        assertFalse(cred.validate(true));
+
+        // For R2 validation
+        assertTrue(cred.validate(false));
     }
 
     /**
@@ -193,7 +202,12 @@
     public void validateUserCredentialWithEapTls() throws Exception {
         Credential cred = createCredentialWithUserCredential();
         cred.getUserCredential().setEapType(EAPConstants.EAP_TLS);
-        assertFalse(cred.validate());
+
+        // For R1 validation
+        assertFalse(cred.validate(true));
+
+        // For R2 validation
+        assertFalse(cred.validate(false));
     }
 
 
@@ -206,7 +220,12 @@
     public void validateUserCredentialWithoutRealm() throws Exception {
         Credential cred = createCredentialWithUserCredential();
         cred.setRealm(null);
-        assertFalse(cred.validate());
+
+        // For R1 validation
+        assertFalse(cred.validate(true));
+
+        // For R2 validation
+        assertFalse(cred.validate(false));
     }
 
     /**
@@ -218,7 +237,12 @@
     public void validateUserCredentialWithoutUsername() throws Exception {
         Credential cred = createCredentialWithUserCredential();
         cred.getUserCredential().setUsername(null);
-        assertFalse(cred.validate());
+
+        // For R1 validation
+        assertFalse(cred.validate(true));
+
+        // For R2 validation
+        assertFalse(cred.validate(false));
     }
 
     /**
@@ -230,7 +254,12 @@
     public void validateUserCredentialWithoutPassword() throws Exception {
         Credential cred = createCredentialWithUserCredential();
         cred.getUserCredential().setPassword(null);
-        assertFalse(cred.validate());
+
+        // For R1 validation
+        assertFalse(cred.validate(true));
+
+        // For R2 validation
+        assertFalse(cred.validate(false));
     }
 
     /**
@@ -242,7 +271,12 @@
     public void validateUserCredentialWithoutAuthMethod() throws Exception {
         Credential cred = createCredentialWithUserCredential();
         cred.getUserCredential().setNonEapInnerMethod(null);
-        assertFalse(cred.validate());
+
+        // For R1 validation
+        assertFalse(cred.validate(true));
+
+        // For R2 validation
+        assertFalse(cred.validate(false));
     }
 
     /**
@@ -255,7 +289,12 @@
     @Test
     public void validateCertCredential() throws Exception {
         Credential cred = createCredentialWithCertificateCredential();
-        assertTrue(cred.validate());
+
+        // For R1 validation
+        assertTrue(cred.validate(true));
+
+        // For R2 validation
+        assertTrue(cred.validate(true));
     }
 
     /**
@@ -267,7 +306,12 @@
     public void validateCertCredentialWithoutCaCert() throws Exception {
         Credential cred = createCredentialWithCertificateCredential();
         cred.setCaCertificate(null);
-        assertFalse(cred.validate());
+
+        // For R1 validation
+        assertFalse(cred.validate(true));
+
+        // For R2 validation
+        assertTrue(cred.validate(false));
     }
 
     /**
@@ -279,7 +323,12 @@
     public void validateCertCredentialWithoutClientCertChain() throws Exception {
         Credential cred = createCredentialWithCertificateCredential();
         cred.setClientCertificateChain(null);
-        assertFalse(cred.validate());
+
+        // For R1 validation
+        assertFalse(cred.validate(true));
+
+        // For R2 validation
+        assertFalse(cred.validate(false));
     }
 
     /**
@@ -291,7 +340,12 @@
     public void validateCertCredentialWithoutClientPrivateKey() throws Exception {
         Credential cred = createCredentialWithCertificateCredential();
         cred.setClientPrivateKey(null);
-        assertFalse(cred.validate());
+
+        // For R1 validation
+        assertFalse(cred.validate(true));
+
+        // For R2 validation
+        assertFalse(cred.validate(false));
     }
 
     /**
@@ -304,7 +358,12 @@
     public void validateCertCredentialWithMismatchFingerprint() throws Exception {
         Credential cred = createCredentialWithCertificateCredential();
         cred.getCertCredential().setCertSha256Fingerprint(new byte[32]);
-        assertFalse(cred.validate());
+
+        // For R1 validation
+        assertFalse(cred.validate(true));
+
+        // For R2 validation
+        assertFalse(cred.validate(false));
     }
 
     /**
@@ -315,7 +374,12 @@
     @Test
     public void validateSimCredentialWithEapSim() throws Exception {
         Credential cred = createCredentialWithSimCredential();
-        assertTrue(cred.validate());
+
+        // For R1 validation
+        assertTrue(cred.validate(true));
+
+        // For R2 validation
+        assertTrue(cred.validate(false));
     }
 
     /**
@@ -327,7 +391,12 @@
     public void validateSimCredentialWithEapAka() throws Exception {
         Credential cred = createCredentialWithSimCredential();
         cred.getSimCredential().setEapType(EAPConstants.EAP_AKA);
-        assertTrue(cred.validate());
+
+        // For R1 validation
+        assertTrue(cred.validate(true));
+
+        // For R2 validation
+        assertTrue(cred.validate(false));
     }
 
     /**
@@ -339,7 +408,12 @@
     public void validateSimCredentialWithEapAkaPrime() throws Exception {
         Credential cred = createCredentialWithSimCredential();
         cred.getSimCredential().setEapType(EAPConstants.EAP_AKA_PRIME);
-        assertTrue(cred.validate());
+
+        // For R1 validation
+        assertTrue(cred.validate(true));
+
+        // For R2 validation
+        assertTrue(cred.validate(false));
     }
 
     /**
@@ -351,7 +425,12 @@
     public void validateSimCredentialWithoutIMSI() throws Exception {
         Credential cred = createCredentialWithSimCredential();
         cred.getSimCredential().setImsi(null);
-        assertFalse(cred.validate());
+
+        // For R1 validation
+        assertFalse(cred.validate(true));
+
+        // For R2 validation
+        assertFalse(cred.validate(false));
     }
 
     /**
@@ -363,7 +442,12 @@
     public void validateSimCredentialWithInvalidIMSI() throws Exception {
         Credential cred = createCredentialWithSimCredential();
         cred.getSimCredential().setImsi("dummy");
-        assertFalse(cred.validate());
+
+        // For R1 validation
+        assertFalse(cred.validate(true));
+
+        // For R2 validation
+        assertFalse(cred.validate(false));
     }
 
     /**
@@ -375,7 +459,12 @@
     public void validateSimCredentialWithEapTls() throws Exception {
         Credential cred = createCredentialWithSimCredential();
         cred.getSimCredential().setEapType(EAPConstants.EAP_TLS);
-        assertFalse(cred.validate());
+
+        // For R1 validation
+        assertFalse(cred.validate(true));
+
+        // For R2 validation
+        assertFalse(cred.validate(false));
     }
 
     /**
@@ -391,7 +480,12 @@
         simCredential.setImsi("1234*");
         simCredential.setEapType(EAPConstants.EAP_SIM);
         cred.setSimCredential(simCredential);
-        assertFalse(cred.validate());
+
+        // For R1 validation
+        assertFalse(cred.validate(true));
+
+        // For R2 validation
+        assertFalse(cred.validate(false));
     }
 
     /**