Merge "add missing annotation for TelephonyManager APIs"
diff --git a/api/current.txt b/api/current.txt
index 0b4bc44..bc7ae3c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -22858,7 +22858,6 @@
     method public float getBearing();
     method public float getBearingAccuracyDegrees();
     method public long getElapsedRealtimeNanos();
-    method public long getElapsedRealtimeUncertaintyNanos();
     method public android.os.Bundle getExtras();
     method public double getLatitude();
     method public double getLongitude();
@@ -22871,7 +22870,6 @@
     method public boolean hasAltitude();
     method public boolean hasBearing();
     method public boolean hasBearingAccuracy();
-    method public boolean hasElapsedRealtimeUncertaintyNanos();
     method public boolean hasSpeed();
     method public boolean hasSpeedAccuracy();
     method public boolean hasVerticalAccuracy();
@@ -22887,7 +22885,6 @@
     method public void setBearing(float);
     method public void setBearingAccuracyDegrees(float);
     method public void setElapsedRealtimeNanos(long);
-    method public void setElapsedRealtimeUncertaintyNanos(long);
     method public void setExtras(android.os.Bundle);
     method public void setLatitude(double);
     method public void setLongitude(double);
@@ -23571,7 +23568,6 @@
     method @Deprecated public void addOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener, android.os.Handler);
     method public int attachAuxEffect(int);
     method @NonNull public android.media.VolumeShaper createVolumeShaper(@NonNull android.media.VolumeShaper.Configuration);
-    method @Deprecated public static void deprecateStreamTypeForPlayback(int, @NonNull String, @NonNull String) throws java.lang.IllegalArgumentException;
     method protected void finalize();
     method public void flush();
     method @NonNull public android.media.AudioAttributes getAudioAttributes();
@@ -25328,7 +25324,6 @@
     method public static android.media.MediaPlayer create(android.content.Context, int);
     method public static android.media.MediaPlayer create(android.content.Context, int, android.media.AudioAttributes, int);
     method @NonNull public android.media.VolumeShaper createVolumeShaper(@NonNull android.media.VolumeShaper.Configuration);
-    method @Deprecated public static void deprecateStreamTypeForPlayback(int, @NonNull String, @NonNull String) throws java.lang.IllegalArgumentException;
     method public void deselectTrack(int) throws java.lang.IllegalStateException;
     method protected void finalize();
     method public int getAudioSessionId();
@@ -26423,7 +26418,6 @@
     ctor @Deprecated public SoundPool(int, int, int);
     method public final void autoPause();
     method public final void autoResume();
-    method @Deprecated public static void deprecateStreamTypeForPlayback(int, @NonNull String, @NonNull String) throws java.lang.IllegalArgumentException;
     method protected void finalize();
     method public int load(String, int);
     method public int load(android.content.Context, int, int);
diff --git a/api/system-current.txt b/api/system-current.txt
index 276befc..752640b 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -181,6 +181,7 @@
     field public static final String SHUTDOWN = "android.permission.SHUTDOWN";
     field public static final String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES";
     field public static final String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME";
+    field public static final String SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON = "android.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON";
     field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
     field public static final String TETHER_PRIVILEGED = "android.permission.TETHER_PRIVILEGED";
     field public static final String TV_INPUT_HARDWARE = "android.permission.TV_INPUT_HARDWARE";
@@ -1187,7 +1188,7 @@
     method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getAppStandbyBucket(String);
     method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public java.util.Map<java.lang.String,java.lang.Integer> getAppStandbyBuckets();
     method public int getUsageSource();
-    method @RequiresPermission(allOf={android.Manifest.permission.SUSPEND_APPS, android.Manifest.permission.OBSERVE_APP_USAGE}) public void registerAppUsageLimitObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, @Nullable android.app.PendingIntent);
+    method @RequiresPermission(allOf={android.Manifest.permission.SUSPEND_APPS, android.Manifest.permission.OBSERVE_APP_USAGE}) public void registerAppUsageLimitObserver(int, @NonNull String[], @NonNull java.time.Duration, @NonNull java.time.Duration, @Nullable android.app.PendingIntent);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerAppUsageObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerUsageSessionObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent, @Nullable android.app.PendingIntent);
     method public void reportUsageStart(@NonNull android.app.Activity, @NonNull String);
@@ -3540,7 +3541,7 @@
     method public void stop();
   }
 
-  public static class HwAudioSource.Builder {
+  public static final class HwAudioSource.Builder {
     ctor public HwAudioSource.Builder();
     method @NonNull public android.media.HwAudioSource build();
     method @NonNull public android.media.HwAudioSource.Builder setAudioAttributes(@NonNull android.media.AudioAttributes);
@@ -3678,7 +3679,7 @@
     method @Nullable public android.media.audiopolicy.AudioProductStrategy getProductStrategyForAudioAttributes(@NonNull android.media.AudioAttributes);
     method public int getVolumeGroupIdForAttributes(@NonNull android.media.AudioAttributes);
     method public int getVolumeGroupIdForLegacyStreamType(int);
-    method public java.util.Iterator<android.media.audiopolicy.AudioProductStrategy> iterator();
+    method @NonNull public java.util.Iterator<android.media.audiopolicy.AudioProductStrategy> iterator();
     method public int size();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.audiopolicy.AudioProductStrategies> CREATOR;
@@ -3695,7 +3696,7 @@
 
   public final class AudioVolumeGroup implements android.os.Parcelable {
     method public int describeContents();
-    method public java.util.List<android.media.AudioAttributes> getAudioAttributes();
+    method @NonNull public java.util.List<android.media.AudioAttributes> getAudioAttributes();
     method public int getId();
     method @NonNull public int[] getLegacyStreamTypes();
     method @NonNull public String name();
@@ -3707,7 +3708,7 @@
     ctor public AudioVolumeGroups();
     method public int describeContents();
     method @Nullable public android.media.audiopolicy.AudioVolumeGroup getById(int);
-    method public java.util.Iterator<android.media.audiopolicy.AudioVolumeGroup> iterator();
+    method @NonNull public java.util.Iterator<android.media.audiopolicy.AudioVolumeGroup> iterator();
     method public int size();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.media.audiopolicy.AudioVolumeGroups> CREATOR;
@@ -5688,6 +5689,7 @@
     method @NonNull public abstract java.util.List<android.permission.RuntimePermissionUsageInfo> onGetPermissionUsages(boolean, long);
     method public abstract void onGetRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.OutputStream);
     method public abstract boolean onIsApplicationQualifiedForRole(@NonNull String, @NonNull String);
+    method public abstract boolean onIsRoleVisible(@NonNull String);
     method @BinderThread public abstract boolean onRestoreDelayedRuntimePermissionsBackup(@NonNull String, @NonNull android.os.UserHandle);
     method @BinderThread public abstract void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream);
     method public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String);
@@ -8450,7 +8452,7 @@
     method public void callSessionResumeFailed(android.telephony.ims.ImsReasonInfo);
     method public void callSessionResumeReceived(android.telephony.ims.ImsCallProfile);
     method public void callSessionResumed(android.telephony.ims.ImsCallProfile);
-    method public void callSessionRttAudioIndicatorChanged(android.telephony.ims.ImsStreamMediaProfile);
+    method public void callSessionRttAudioIndicatorChanged(@NonNull android.telephony.ims.ImsStreamMediaProfile);
     method public void callSessionRttMessageReceived(String);
     method public void callSessionRttModifyRequestReceived(android.telephony.ims.ImsCallProfile);
     method public void callSessionRttModifyResponseReceived(int);
@@ -8754,12 +8756,12 @@
   public final class ImsSsData implements android.os.Parcelable {
     ctor public ImsSsData(int, int, int, int, int);
     method public int describeContents();
-    method public android.telephony.ims.ImsCallForwardInfo[] getCallForwardInfo();
+    method @Nullable public java.util.List<android.telephony.ims.ImsCallForwardInfo> getCallForwardInfo();
     method public int getRequestType();
     method public int getResult();
     method public int getServiceClass();
     method public int getServiceType();
-    method @NonNull public android.telephony.ims.ImsSsInfo[] getSuppServiceInfo();
+    method @NonNull public java.util.List<android.telephony.ims.ImsSsInfo> getSuppServiceInfo();
     method public int getTeleserviceType();
     method public boolean isTypeBarring();
     method public boolean isTypeCf();
@@ -8819,11 +8821,11 @@
     field public static final int SS_WAIT = 12; // 0xc
   }
 
-  public static class ImsSsData.Builder {
+  public static final class ImsSsData.Builder {
     ctor public ImsSsData.Builder(int, int, int, int, int);
     method @NonNull public android.telephony.ims.ImsSsData build();
-    method @NonNull public android.telephony.ims.ImsSsData.Builder setCallForwardingInfo(@NonNull android.telephony.ims.ImsCallForwardInfo[]);
-    method @NonNull public android.telephony.ims.ImsSsData.Builder setSuppServiceInfo(@NonNull android.telephony.ims.ImsSsInfo[]);
+    method @NonNull public android.telephony.ims.ImsSsData.Builder setCallForwardingInfo(@NonNull java.util.List<android.telephony.ims.ImsCallForwardInfo>);
+    method @NonNull public android.telephony.ims.ImsSsData.Builder setSuppServiceInfo(@NonNull java.util.List<android.telephony.ims.ImsSsInfo>);
   }
 
   public final class ImsSsInfo implements android.os.Parcelable {
@@ -8832,7 +8834,7 @@
     method public int getClirInterrogationStatus();
     method public int getClirOutgoingState();
     method @Deprecated public String getIcbNum();
-    method public String getIncomingCommunicationBarringNumber();
+    method @Nullable public String getIncomingCommunicationBarringNumber();
     method public int getProvisionStatus();
     method public int getStatus();
     method public void writeToParcel(android.os.Parcel, int);
@@ -8853,7 +8855,7 @@
     field public static final int SERVICE_PROVISIONING_UNKNOWN = -1; // 0xffffffff
   }
 
-  public static class ImsSsInfo.Builder {
+  public static final class ImsSsInfo.Builder {
     ctor public ImsSsInfo.Builder(int);
     method @NonNull public android.telephony.ims.ImsSsInfo build();
     method @NonNull public android.telephony.ims.ImsSsInfo.Builder setClirInterrogationStatus(int);
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index b72ec39..62b24e9 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -60,7 +60,7 @@
             in PendingIntent sessionEndCallbackIntent, String callingPackage);
     void unregisterUsageSessionObserver(int sessionObserverId, String callingPackage);
     void registerAppUsageLimitObserver(int observerId, in String[] packages, long timeLimitMs,
-            in PendingIntent callback, String callingPackage);
+            long timeRemainingMs, in PendingIntent callback, String callingPackage);
     void unregisterAppUsageLimitObserver(int observerId, String callingPackage);
     void reportUsageStart(in IBinder activity, String token, String callingPackage);
     void reportPastUsageStart(in IBinder activity, String token, long timeAgoMs,
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index d34e6d3..cee6b87 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -35,6 +35,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -738,6 +739,23 @@
     }
 
     /**
+     * @deprecated use
+     * {@link #registerAppUsageLimitObserver(int, String[], Duration, Duration, PendingIntent)}.
+     *
+     * @removed
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    // STOPSHIP b/126917290: remove this method once ag/6591106 is merged and it's not being used.
+    public void registerAppUsageLimitObserver(int observerId, @NonNull String[] observedEntities,
+            long timeLimit, @NonNull TimeUnit timeUnit, @Nullable PendingIntent callbackIntent) {
+        final Duration timeLimitDuration = Duration.ofMillis(timeUnit.toMillis(timeLimit));
+        registerAppUsageLimitObserver(observerId, observedEntities,
+                timeLimitDuration, timeLimitDuration, callbackIntent);
+    }
+
+    /**
      * Register a usage limit observer that receives a callback on the provided intent when the
      * sum of usages of apps and tokens in the provided {@code observedEntities} array exceeds the
      * {@code timeLimit} specified. The structure of a token is a {@link String} with the reporting
@@ -759,19 +777,21 @@
      * @see android.content.pm.LauncherApps#getAppUsageLimit
      *
      * @param observerId A unique id associated with the group of apps to be monitored. There can
-     *                  be multiple groups with common packages and different time limits.
+     *                   be multiple groups with common packages and different time limits.
      * @param observedEntities The list of packages and token to observe for usage time. Cannot be
      *                         null and must include at least one package or token.
      * @param timeLimit The total time the set of apps can be in the foreground before the
-     *                  callbackIntent is delivered. Must be at least one minute. Note: a limit of
-     *                  0 can be set to indicate that the user has already exhausted the limit for
-     *                  a group, in which case, the given {@code callbackIntent} will be ignored.
-     * @param timeUnit The unit for time specified in {@code timeLimit}. Cannot be null.
+     *                  {@code callbackIntent} is delivered. Must be at least one minute.
+     * @param timeRemaining The remaining time the set of apps can be in the foreground before the
+     *                      {@code callbackIntent} is delivered. Must be greater than
+     *                      {@code timeLimit}. Note: a limit of 0 can be set to indicate that the
+     *                      user has already exhausted the limit for a group, in which case,
+     *                      the given {@code callbackIntent} will be ignored.
      * @param callbackIntent The PendingIntent that will be dispatched when the usage limit is
      *                       exceeded by the group of apps. The delivered Intent will also contain
      *                       the extras {@link #EXTRA_OBSERVER_ID}, {@link #EXTRA_TIME_LIMIT} and
      *                       {@link #EXTRA_TIME_USED}. Cannot be {@code null} unless the observer is
-     *                       being registered with a {@code timeLimit} of 0.
+     *                       being registered with a {@code timeRemaining} of 0.
      * @throws SecurityException if the caller doesn't have both SUSPEND_APPS and OBSERVE_APP_USAGE
      *                           permissions.
      * @hide
@@ -781,10 +801,12 @@
             android.Manifest.permission.SUSPEND_APPS,
             android.Manifest.permission.OBSERVE_APP_USAGE})
     public void registerAppUsageLimitObserver(int observerId, @NonNull String[] observedEntities,
-            long timeLimit, @NonNull TimeUnit timeUnit, @Nullable PendingIntent callbackIntent) {
+            @NonNull Duration timeLimit, @NonNull Duration timeRemaining,
+            @Nullable PendingIntent callbackIntent) {
         try {
             mService.registerAppUsageLimitObserver(observerId, observedEntities,
-                    timeUnit.toMillis(timeLimit), callbackIntent, mContext.getOpPackageName());
+                    timeLimit.toMillis(), timeRemaining.toMillis(), callbackIntent,
+                    mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl
index cb2517e..76e911d 100644
--- a/core/java/android/permission/IPermissionController.aidl
+++ b/core/java/android/permission/IPermissionController.aidl
@@ -40,6 +40,7 @@
     void getPermissionUsages(boolean countSystem, long numMillis, in RemoteCallback callback);
     void isApplicationQualifiedForRole(String roleName, String packageName,
             in RemoteCallback callback);
+    void isRoleVisible(String roleName, in RemoteCallback callback);
     void setRuntimePermissionGrantStateByDeviceAdmin(String callerPackageName, String packageName,
             String permission, int grantState, in RemoteCallback callback);
 }
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index 9d58064..5695e42 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -476,6 +476,26 @@
     }
 
     /**
+     * Check whether a role should be visible to user.
+     *
+     * @param roleName name of the role to check for
+     * @param executor Executor on which to invoke the callback
+     * @param callback Callback to receive the result
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
+    public void isRoleVisible(@NonNull String roleName,
+            @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
+        checkStringNotEmpty(roleName);
+        checkNotNull(executor);
+        checkNotNull(callback);
+
+        mRemoteService.scheduleRequest(new PendingIsRoleVisibleRequest(mRemoteService, roleName,
+                executor, callback));
+    }
+
+    /**
      * A connection to the remote service
      */
     static final class RemoteService extends
@@ -1222,4 +1242,55 @@
             }
         }
     }
+
+    /**
+     * Request for {@link #isRoleVisible}.
+     */
+    private static final class PendingIsRoleVisibleRequest extends
+            AbstractRemoteService.PendingRequest<RemoteService, IPermissionController> {
+
+        private final @NonNull String mRoleName;
+        private final @NonNull Consumer<Boolean> mCallback;
+
+        private final @NonNull RemoteCallback mRemoteCallback;
+
+        private PendingIsRoleVisibleRequest(@NonNull RemoteService service,
+                @NonNull String roleName, @NonNull @CallbackExecutor Executor executor,
+                @NonNull Consumer<Boolean> callback) {
+            super(service);
+
+            mRoleName = roleName;
+            mCallback = callback;
+
+            mRemoteCallback = new RemoteCallback(result -> executor.execute(() -> {
+                long token = Binder.clearCallingIdentity();
+                try {
+                    boolean visible;
+                    if (result != null) {
+                        visible = result.getBoolean(KEY_RESULT);
+                    } else {
+                        visible = false;
+                    }
+                    callback.accept(visible);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                    finish();
+                }
+            }), null);
+        }
+
+        @Override
+        protected void onTimeout(RemoteService remoteService) {
+            mCallback.accept(false);
+        }
+
+        @Override
+        public void run() {
+            try {
+                getService().getServiceInterface().isRoleVisible(mRoleName, mRemoteCallback);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error checking whether role should be visible", e);
+            }
+        }
+    }
 }
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index ee03689..d375c10 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -179,12 +179,21 @@
      * @param roleName name of the role to check for
      * @param packageName package name of the application to check for
      *
-     * @return whether the application is qualified for the role.
+     * @return whether the application is qualified for the role
      */
     public abstract boolean onIsApplicationQualifiedForRole(@NonNull String roleName,
             @NonNull String packageName);
 
     /**
+     * Check whether a role should be visible to user.
+     *
+     * @param roleName name of the role to check for
+     *
+     * @return whether the role should be visible to user
+     */
+    public abstract boolean onIsRoleVisible(@NonNull String roleName);
+
+    /**
      * Set the runtime permission state from a device admin.
      *
      * @param callerPackageName The package name of the admin requesting the change
@@ -344,6 +353,18 @@
             }
 
             @Override
+            public void isRoleVisible(String roleName, RemoteCallback callback) {
+                checkStringNotEmpty(roleName);
+                checkNotNull(callback, "callback");
+
+                enforceCallingPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, null);
+
+                mHandler.sendMessage(obtainMessage(
+                        PermissionControllerService::isRoleVisible,
+                        PermissionControllerService.this, roleName, callback));
+            }
+
+            @Override
             public void setRuntimePermissionGrantStateByDeviceAdmin(String callerPackageName,
                     String packageName, String permission, int grantState,
                     RemoteCallback callback) {
@@ -445,6 +466,13 @@
         callback.sendResult(result);
     }
 
+    private void isRoleVisible(@NonNull String roleName, @NonNull RemoteCallback callback) {
+        boolean visible = onIsRoleVisible(roleName);
+        Bundle result = new Bundle();
+        result.putBoolean(PermissionControllerManager.KEY_RESULT, visible);
+        callback.sendResult(result);
+    }
+
     private void setRuntimePermissionGrantStateByDeviceAdmin(@NonNull String callerPackageName,
             @NonNull String packageName, @NonNull String permission,
             @PermissionGrantState int grantState, @NonNull RemoteCallback callback) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 368fc2a..45219d6 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3321,6 +3321,17 @@
                         ColorDisplayManager.COLOR_MODE_AUTOMATIC);
 
         /**
+         * The user selected peak refresh rate in frames per second.
+         *
+         * If this isn't set, the system falls back to a device specific default.
+         * @hide
+         */
+        public static final String PEAK_REFRESH_RATE = "peak_refresh_rate";
+
+        private static final Validator PEAK_REFRESH_RATE_VALIDATOR =
+                new SettingsValidators.InclusiveFloatRangeValidator(24f, Float.MAX_VALUE);
+
+        /**
          * The amount of time in milliseconds before the device goes to sleep or begins
          * to dream after a period of inactivity.  This value is also known as the
          * user activity timeout period since the screen isn't necessarily turned off
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e87295a..ae3e714 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4445,15 +4445,23 @@
          @hide -->
     <permission android:name="android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY"
         android:protectionLevel="signature|preinstalled" />
+
     <!-- @SystemApi Allows wallpaper to be rendered in ambient mode.
          @hide -->
     <permission android:name="android.permission.AMBIENT_WALLPAPER"
                 android:protectionLevel="signature|preinstalled" />
+
     <!-- @SystemApi Allows sensor privacy to be modified.
          @hide -->
     <permission android:name="android.permission.MANAGE_SENSOR_PRIVACY"
                 android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows an activity to replace the app name and icon displayed in share targets
+         in the sharesheet for the Q-release and later.
+         @hide  <p>Not for use by third-party applications.</p> -->
+    <permission android:name="android.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON"
+                android:protectionLevel="signature|privileged" />
+
     <application android:process="system"
                  android:persistent="true"
                  android:hasCode="false"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 47e5462..2f2fdca 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3945,4 +3945,7 @@
      and a second time clipped to the fill level to indicate charge -->
     <bool name="config_batterymeterDualTone">false</bool>
 
+    <!-- The default peak refresh rate for a given device. Change this value if you want to allow
+         for higher refresh rates to be automatically used out of the box -->
+    <integer name="config_defaultPeakRefreshRate">60</integer>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 426d813..d5444c0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3688,4 +3688,6 @@
   <java-symbol type="string" name="mime_type_spreadsheet_ext" />
   <java-symbol type="string" name="mime_type_presentation" />
   <java-symbol type="string" name="mime_type_presentation_ext" />
+
+  <java-symbol type="integer" name="config_defaultPeakRefreshRate" />
 </resources>
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 69632c1..ebc6be7 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -92,7 +92,8 @@
                     Settings.System.VOLUME_SYSTEM, // deprecated since API 2?
                     Settings.System.VOLUME_VOICE, // deprecated since API 2?
                     Settings.System.WHEN_TO_MAKE_WIFI_CALLS, // bug?
-                    Settings.System.WINDOW_ORIENTATION_LISTENER_LOG // used for debugging only
+                    Settings.System.WINDOW_ORIENTATION_LISTENER_LOG, // used for debugging only
+                    Settings.System.PEAK_REFRESH_RATE // depends on hardware capabilities
                     );
 
     private static final Set<String> BACKUP_BLACKLISTED_GLOBAL_SETTINGS =
diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml
index 2110a8f..3e53a38 100644
--- a/data/etc/com.android.settings.xml
+++ b/data/etc/com.android.settings.xml
@@ -33,6 +33,7 @@
         <permission name="android.permission.MANAGE_USERS"/>
         <permission name="android.permission.MANAGE_USER_OEM_UNLOCK_STATE" />
         <permission name="android.permission.MASTER_CLEAR"/>
+        <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
         <permission name="android.permission.MODIFY_PHONE_STATE"/>
         <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
         <permission name="android.permission.MOVE_PACKAGE"/>
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 117d05e..d74f27c 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -1052,6 +1052,8 @@
 }
 
 void AssetManager2::InvalidateCaches(uint32_t diff) {
+  cached_bag_resid_stacks_.clear();
+
   if (diff == 0xffffffffu) {
     // Everything must go.
     cached_bags_.clear();
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index ed74333..17e2509 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -113,10 +113,6 @@
      * Bit mask for mFieldsMask indicating the presence of mBearingAccuracy.
      */
     private static final int HAS_BEARING_ACCURACY_MASK = 128;
-    /**
-     * Bit mask for mFieldsMask indicating the presence of mElapsedRealtimeUncertaintyNanos.
-     */
-    private static final int HAS_ELAPSED_REALTIME_UNCERTAINTY_MASK = 256;
 
     // Cached data to make bearing/distance computations more efficient for the case
     // where distanceTo and bearingTo are called in sequence.  Assume this typically happens
@@ -134,9 +130,6 @@
     private long mTime = 0;
     @UnsupportedAppUsage
     private long mElapsedRealtimeNanos = 0;
-    // Estimate of the relative precision of the alignment of this SystemClock
-    // timestamp, with the reported measurements in nanoseconds (68% confidence).
-    private long mElapsedRealtimeUncertaintyNanos = 0;
     private double mLatitude = 0.0;
     private double mLongitude = 0.0;
     private double mAltitude = 0.0f;
@@ -178,7 +171,6 @@
         mProvider = l.mProvider;
         mTime = l.mTime;
         mElapsedRealtimeNanos = l.mElapsedRealtimeNanos;
-        mElapsedRealtimeUncertaintyNanos = l.mElapsedRealtimeUncertaintyNanos;
         mFieldsMask = l.mFieldsMask;
         mLatitude = l.mLatitude;
         mLongitude = l.mLongitude;
@@ -199,7 +191,6 @@
         mProvider = null;
         mTime = 0;
         mElapsedRealtimeNanos = 0;
-        mElapsedRealtimeUncertaintyNanos = 0;
         mFieldsMask = 0;
         mLatitude = 0;
         mLongitude = 0;
@@ -595,37 +586,6 @@
     }
 
     /**
-     * Get estimate of the relative precision of the alignment of the
-     * ElapsedRealtimeNanos timestamp, with the reported measurements in
-     * nanoseconds (68% confidence).
-     *
-     * @return uncertainty of elapsed real-time of fix, in nanoseconds.
-     */
-    public long getElapsedRealtimeUncertaintyNanos() {
-        return mElapsedRealtimeUncertaintyNanos;
-    }
-
-    /**
-     * Set estimate of the relative precision of the alignment of the
-     * ElapsedRealtimeNanos timestamp, with the reported measurements in
-     * nanoseconds (68% confidence).
-     *
-     * @param time uncertainty of the elapsed real-time of fix, in nanoseconds.
-     */
-    public void setElapsedRealtimeUncertaintyNanos(long time) {
-        mElapsedRealtimeUncertaintyNanos = time;
-        mFieldsMask |= HAS_ELAPSED_REALTIME_UNCERTAINTY_MASK;
-    }
-
-    /**
-     * True if this location has a elapsed realtime accuracy.
-     */
-    public boolean hasElapsedRealtimeUncertaintyNanos() {
-        return (mFieldsMask & HAS_ELAPSED_REALTIME_UNCERTAINTY_MASK) != 0;
-    }
-
-
-    /**
      * Get the latitude, in degrees.
      *
      * <p>All locations generated by the {@link LocationManager}
@@ -1102,10 +1062,6 @@
             s.append(" et=");
             TimeUtils.formatDuration(mElapsedRealtimeNanos / 1000000L, s);
         }
-        if (hasElapsedRealtimeUncertaintyNanos()) {
-            s.append(" etAcc=");
-            TimeUtils.formatDuration(mElapsedRealtimeUncertaintyNanos / 1000000L, s);
-        }
         if (hasAltitude()) s.append(" alt=").append(mAltitude);
         if (hasSpeed()) s.append(" vel=").append(mSpeed);
         if (hasBearing()) s.append(" bear=").append(mBearing);
@@ -1136,7 +1092,6 @@
             Location l = new Location(provider);
             l.mTime = in.readLong();
             l.mElapsedRealtimeNanos = in.readLong();
-            l.mElapsedRealtimeUncertaintyNanos = in.readLong();
             l.mFieldsMask = in.readByte();
             l.mLatitude = in.readDouble();
             l.mLongitude = in.readDouble();
@@ -1167,7 +1122,6 @@
         parcel.writeString(mProvider);
         parcel.writeLong(mTime);
         parcel.writeLong(mElapsedRealtimeNanos);
-        parcel.writeLong(mElapsedRealtimeUncertaintyNanos);
         parcel.writeByte(mFieldsMask);
         parcel.writeDouble(mLatitude);
         parcel.writeDouble(mLongitude);
diff --git a/media/java/android/media/HwAudioSource.java b/media/java/android/media/HwAudioSource.java
index 8bdb8a6..d9be0a5 100644
--- a/media/java/android/media/HwAudioSource.java
+++ b/media/java/android/media/HwAudioSource.java
@@ -165,7 +165,7 @@
      * If the audio attributes are not set with {@link #setAudioAttributes(AudioAttributes)},
      * attributes comprising {@link AudioAttributes#USAGE_MEDIA} will be used.
      */
-    public static class Builder {
+    public static final class Builder {
         private AudioAttributes mAudioAttributes;
         private AudioDeviceInfo mAudioDeviceInfo;
 
diff --git a/media/java/android/media/PlayerBase.java b/media/java/android/media/PlayerBase.java
index fbe5152..b302b06 100644
--- a/media/java/android/media/PlayerBase.java
+++ b/media/java/android/media/PlayerBase.java
@@ -571,13 +571,12 @@
     // Utilities
 
     /**
+     * @hide
      * Use to generate warning or exception in legacy code paths that allowed passing stream types
      * to qualify audio playback.
      * @param streamType the stream type to check
      * @throws IllegalArgumentException
-     * @deprecated This method is not intended to be used by applications.
      */
-    @java.lang.Deprecated
     public static void deprecateStreamTypeForPlayback(int streamType, @NonNull String className,
             @NonNull String opName) throws IllegalArgumentException {
         // STREAM_ACCESSIBILITY was introduced at the same time the use of stream types
diff --git a/media/java/android/media/audiopolicy/AudioProductStrategies.java b/media/java/android/media/audiopolicy/AudioProductStrategies.java
index 98b6d97..c305b68 100644
--- a/media/java/android/media/audiopolicy/AudioProductStrategies.java
+++ b/media/java/android/media/audiopolicy/AudioProductStrategies.java
@@ -85,7 +85,7 @@
      * Returns an {@link Iterator}
      */
     @Override
-    public Iterator<AudioProductStrategy> iterator() {
+    public @NonNull Iterator<AudioProductStrategy> iterator() {
         return mAudioProductStrategyList.iterator();
     }
 
diff --git a/media/java/android/media/audiopolicy/AudioVolumeGroup.java b/media/java/android/media/audiopolicy/AudioVolumeGroup.java
index 0b4ba93..b60947f 100644
--- a/media/java/android/media/audiopolicy/AudioVolumeGroup.java
+++ b/media/java/android/media/audiopolicy/AudioVolumeGroup.java
@@ -77,7 +77,7 @@
     /**
      * @return List of {@link AudioAttributes} involved in this {@link AudioVolumeGroup}.
      */
-    public List<AudioAttributes> getAudioAttributes() {
+    public @NonNull List<AudioAttributes> getAudioAttributes() {
         return Arrays.asList(mAudioAttributes);
     }
 
diff --git a/media/java/android/media/audiopolicy/AudioVolumeGroups.java b/media/java/android/media/audiopolicy/AudioVolumeGroups.java
index 301bec7..2e56f84 100644
--- a/media/java/android/media/audiopolicy/AudioVolumeGroups.java
+++ b/media/java/android/media/audiopolicy/AudioVolumeGroups.java
@@ -70,7 +70,7 @@
      * Returns an {@link Iterator}
      */
     @Override
-    public Iterator<AudioVolumeGroup> iterator() {
+    public @NonNull Iterator<AudioVolumeGroup> iterator() {
         return mAudioVolumeGroupList.iterator();
     }
 
diff --git a/packages/SystemUI/res/drawable/ic_volume_remote.xml b/packages/SettingsLib/res/drawable/ic_volume_remote.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/ic_volume_remote.xml
rename to packages/SettingsLib/res/drawable/ic_volume_remote.xml
diff --git a/packages/SystemUI/res/drawable/ic_volume_remote_mute.xml b/packages/SettingsLib/res/drawable/ic_volume_remote_mute.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/ic_volume_remote_mute.xml
rename to packages/SettingsLib/res/drawable/ic_volume_remote_mute.xml
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/D.java b/packages/SettingsLib/src/com/android/settingslib/volume/D.java
new file mode 100644
index 0000000..7e0654d
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/D.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume;
+
+import android.util.Log;
+
+class D {
+    public static boolean BUG = Log.isLoggable("volume", Log.DEBUG);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java b/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java
similarity index 84%
rename from packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java
rename to packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java
index 8b00eee..4ed1154 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume;
+package com.android.settingslib.volume;
 
 import android.app.PendingIntent;
 import android.content.Context;
@@ -27,7 +27,6 @@
 import android.media.MediaMetadata;
 import android.media.session.MediaController;
 import android.media.session.MediaController.PlaybackInfo;
-import android.media.session.MediaSession;
 import android.media.session.MediaSession.QueueItem;
 import android.media.session.MediaSession.Token;
 import android.media.session.MediaSessionManager;
@@ -41,7 +40,6 @@
 import android.util.Log;
 
 import java.io.PrintWriter;
-import java.io.StringWriter;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -73,16 +71,24 @@
         mCallbacks = callbacks;
     }
 
+    /**
+     * Dump to {@code writer}
+     */
     public void dump(PrintWriter writer) {
         writer.println(getClass().getSimpleName() + " state:");
-        writer.print("  mInit: "); writer.println(mInit);
-        writer.print("  mRecords.size: "); writer.println(mRecords.size());
+        writer.print("  mInit: ");
+        writer.println(mInit);
+        writer.print("  mRecords.size: ");
+        writer.println(mRecords.size());
         int i = 0;
         for (MediaControllerRecord r : mRecords.values()) {
             dump(++i, writer, r.controller);
         }
     }
 
+    /**
+     * init MediaSessions
+     */
     public void init() {
         if (D.BUG) Log.d(TAG, "init");
         // will throw if no permission
@@ -97,12 +103,18 @@
         mHandler.sendEmptyMessage(H.UPDATE_SESSIONS);
     }
 
+    /**
+     * Destroy MediaSessions
+     */
     public void destroy() {
         if (D.BUG) Log.d(TAG, "destroy");
         mInit = false;
         mMgr.removeOnActiveSessionsChangedListener(mSessionsListener);
     }
 
+    /**
+     * Set volume {@code level} to remote media {@code token}
+     */
     public void setVolume(Token token, int level) {
         final MediaControllerRecord r = mRecords.get(token);
         if (r == null) {
@@ -113,15 +125,17 @@
         r.controller.setVolumeTo(level, 0);
     }
 
-    private void onRemoteVolumeChangedH(MediaSession.Token sessionToken, int flags) {
+    private void onRemoteVolumeChangedH(Token sessionToken, int flags) {
         final MediaController controller = new MediaController(mContext, sessionToken);
-        if (D.BUG) Log.d(TAG, "remoteVolumeChangedH " + controller.getPackageName() + " "
-                + Util.audioManagerFlagsToString(flags));
+        if (D.BUG) {
+            Log.d(TAG, "remoteVolumeChangedH " + controller.getPackageName() + " "
+                    + Util.audioManagerFlagsToString(flags));
+        }
         final Token token = controller.getSessionToken();
         mCallbacks.onRemoteVolumeChanged(token, flags);
     }
 
-    private void onUpdateRemoteControllerH(MediaSession.Token sessionToken) {
+    private void onUpdateRemoteControllerH(Token sessionToken) {
         final MediaController controller =
                 sessionToken != null ? new MediaController(mContext, sessionToken) : null;
         final String pkg = controller != null ? controller.getPackageName() : null;
@@ -191,7 +205,8 @@
             if (appLabel.length() > 0) {
                 return appLabel;
             }
-        } catch (NameNotFoundException e) { }
+        } catch (NameNotFoundException e) {
+        }
         return pkg;
     }
 
@@ -240,29 +255,11 @@
         }
     }
 
-    public static void dumpMediaSessions(Context context) {
-        final MediaSessionManager mgr = (MediaSessionManager) context
-                .getSystemService(Context.MEDIA_SESSION_SERVICE);
-        try {
-            final List<MediaController> controllers = mgr.getActiveSessions(null);
-            final int N = controllers.size();
-            if (D.BUG) Log.d(TAG, N + " controllers");
-            for (int i = 0; i < N; i++) {
-                final StringWriter sw = new StringWriter();
-                final PrintWriter pw = new PrintWriter(sw, true);
-                dump(i + 1, pw, controllers.get(i));
-                if (D.BUG) Log.d(TAG, sw.toString());
-            }
-        } catch (SecurityException e) {
-            Log.w(TAG, "Not allowed to get sessions", e);
-        }
-    }
-
     private final class MediaControllerRecord extends MediaController.Callback {
-        private final MediaController controller;
+        public final MediaController controller;
 
-        private boolean sentRemote;
-        private String name;
+        public boolean sentRemote;
+        public String name;
 
         private MediaControllerRecord(MediaController controller) {
             this.controller = controller;
@@ -274,8 +271,10 @@
 
         @Override
         public void onAudioInfoChanged(PlaybackInfo info) {
-            if (D.BUG) Log.d(TAG, cb("onAudioInfoChanged") + Util.playbackInfoToString(info)
-                    + " sentRemote=" + sentRemote);
+            if (D.BUG) {
+                Log.d(TAG, cb("onAudioInfoChanged") + Util.playbackInfoToString(info)
+                        + " sentRemote=" + sentRemote);
+            }
             final boolean remote = isRemote(info);
             if (!remote && sentRemote) {
                 mCallbacks.onRemoteRemoved(controller.getSessionToken());
@@ -324,22 +323,22 @@
 
     private final OnActiveSessionsChangedListener mSessionsListener =
             new OnActiveSessionsChangedListener() {
-        @Override
-        public void onActiveSessionsChanged(List<MediaController> controllers) {
-            onActiveSessionsUpdatedH(controllers);
-        }
-    };
+                @Override
+                public void onActiveSessionsChanged(List<MediaController> controllers) {
+                    onActiveSessionsUpdatedH(controllers);
+                }
+            };
 
     private final IRemoteVolumeController mRvc = new IRemoteVolumeController.Stub() {
         @Override
-        public void remoteVolumeChanged(MediaSession.Token sessionToken, int flags)
+        public void remoteVolumeChanged(Token sessionToken, int flags)
                 throws RemoteException {
             mHandler.obtainMessage(H.REMOTE_VOLUME_CHANGED, flags, 0,
                     sessionToken).sendToTarget();
         }
 
         @Override
-        public void updateRemoteController(final MediaSession.Token sessionToken)
+        public void updateRemoteController(final Token sessionToken)
                 throws RemoteException {
             mHandler.obtainMessage(H.UPDATE_REMOTE_CONTROLLER, sessionToken).sendToTarget();
         }
@@ -361,18 +360,32 @@
                     onActiveSessionsUpdatedH(mMgr.getActiveSessions(null));
                     break;
                 case REMOTE_VOLUME_CHANGED:
-                    onRemoteVolumeChangedH((MediaSession.Token) msg.obj, msg.arg1);
+                    onRemoteVolumeChangedH((Token) msg.obj, msg.arg1);
                     break;
                 case UPDATE_REMOTE_CONTROLLER:
-                    onUpdateRemoteControllerH((MediaSession.Token) msg.obj);
+                    onUpdateRemoteControllerH((Token) msg.obj);
                     break;
             }
         }
     }
 
+    /**
+     * Callback for remote media sessions
+     */
     public interface Callbacks {
+        /**
+         * Invoked when remote media session is updated
+         */
         void onRemoteUpdate(Token token, String name, PlaybackInfo pi);
+
+        /**
+         * Invoked when remote media session is removed
+         */
         void onRemoteRemoved(Token t);
+
+        /**
+         * Invoked when remote volume is changed
+         */
         void onRemoteVolumeChanged(Token token, int flags);
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/Util.java b/packages/SettingsLib/src/com/android/settingslib/volume/Util.java
new file mode 100644
index 0000000..4e770ae
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/Util.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.MediaMetadata;
+import android.media.VolumeProvider;
+import android.media.session.MediaController.PlaybackInfo;
+import android.media.session.PlaybackState;
+import android.telephony.TelephonyManager;
+import android.widget.TextView;
+
+import java.util.Objects;
+
+/**
+ * Static helpers for the volume dialog.
+ */
+public class Util {
+
+    private static final int[] AUDIO_MANAGER_FLAGS = new int[]{
+            AudioManager.FLAG_SHOW_UI,
+            AudioManager.FLAG_VIBRATE,
+            AudioManager.FLAG_PLAY_SOUND,
+            AudioManager.FLAG_ALLOW_RINGER_MODES,
+            AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE,
+            AudioManager.FLAG_SHOW_VIBRATE_HINT,
+            AudioManager.FLAG_SHOW_SILENT_HINT,
+            AudioManager.FLAG_FROM_KEY,
+            AudioManager.FLAG_SHOW_UI_WARNINGS,
+    };
+
+    private static final String[] AUDIO_MANAGER_FLAG_NAMES = new String[]{
+            "SHOW_UI",
+            "VIBRATE",
+            "PLAY_SOUND",
+            "ALLOW_RINGER_MODES",
+            "REMOVE_SOUND_AND_VIBRATE",
+            "SHOW_VIBRATE_HINT",
+            "SHOW_SILENT_HINT",
+            "FROM_KEY",
+            "SHOW_UI_WARNINGS",
+    };
+
+    /**
+     * Extract log tag from {@code c}
+     */
+    public static String logTag(Class<?> c) {
+        final String tag = "vol." + c.getSimpleName();
+        return tag.length() < 23 ? tag : tag.substring(0, 23);
+    }
+
+    /**
+     * Convert media metadata to string
+     */
+    public static String mediaMetadataToString(MediaMetadata metadata) {
+        if (metadata == null) return null;
+        return metadata.getDescription().toString();
+    }
+
+    /**
+     * Convert playback info to string
+     */
+    public static String playbackInfoToString(PlaybackInfo info) {
+        if (info == null) return null;
+        final String type = playbackInfoTypeToString(info.getPlaybackType());
+        final String vc = volumeProviderControlToString(info.getVolumeControl());
+        return String.format("PlaybackInfo[vol=%s,max=%s,type=%s,vc=%s],atts=%s",
+                info.getCurrentVolume(), info.getMaxVolume(), type, vc, info.getAudioAttributes());
+    }
+
+    /**
+     * Convert type of playback info to string
+     */
+    public static String playbackInfoTypeToString(int type) {
+        switch (type) {
+            case PlaybackInfo.PLAYBACK_TYPE_LOCAL:
+                return "LOCAL";
+            case PlaybackInfo.PLAYBACK_TYPE_REMOTE:
+                return "REMOTE";
+            default:
+                return "UNKNOWN_" + type;
+        }
+    }
+
+    /**
+     * Convert state of playback info to string
+     */
+    public static String playbackStateStateToString(int state) {
+        switch (state) {
+            case PlaybackState.STATE_NONE:
+                return "STATE_NONE";
+            case PlaybackState.STATE_STOPPED:
+                return "STATE_STOPPED";
+            case PlaybackState.STATE_PAUSED:
+                return "STATE_PAUSED";
+            case PlaybackState.STATE_PLAYING:
+                return "STATE_PLAYING";
+            default:
+                return "UNKNOWN_" + state;
+        }
+    }
+
+    /**
+     * Convert volume provider control to string
+     */
+    public static String volumeProviderControlToString(int control) {
+        switch (control) {
+            case VolumeProvider.VOLUME_CONTROL_ABSOLUTE:
+                return "VOLUME_CONTROL_ABSOLUTE";
+            case VolumeProvider.VOLUME_CONTROL_FIXED:
+                return "VOLUME_CONTROL_FIXED";
+            case VolumeProvider.VOLUME_CONTROL_RELATIVE:
+                return "VOLUME_CONTROL_RELATIVE";
+            default:
+                return "VOLUME_CONTROL_UNKNOWN_" + control;
+        }
+    }
+
+    /**
+     * Convert {@link PlaybackState} to string
+     */
+    public static String playbackStateToString(PlaybackState playbackState) {
+        if (playbackState == null) return null;
+        return playbackStateStateToString(playbackState.getState()) + " " + playbackState;
+    }
+
+    /**
+     * Convert audio manager flags to string
+     */
+    public static String audioManagerFlagsToString(int value) {
+        return bitFieldToString(value, AUDIO_MANAGER_FLAGS, AUDIO_MANAGER_FLAG_NAMES);
+    }
+
+    protected static String bitFieldToString(int value, int[] values, String[] names) {
+        if (value == 0) return "";
+        final StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < values.length; i++) {
+            if ((value & values[i]) != 0) {
+                if (sb.length() > 0) sb.append(',');
+                sb.append(names[i]);
+            }
+            value &= ~values[i];
+        }
+        if (value != 0) {
+            if (sb.length() > 0) sb.append(',');
+            sb.append("UNKNOWN_").append(value);
+        }
+        return sb.toString();
+    }
+
+    private static CharSequence emptyToNull(CharSequence str) {
+        return str == null || str.length() == 0 ? null : str;
+    }
+
+    /**
+     * Set text for specific {@link TextView}
+     */
+    public static boolean setText(TextView tv, CharSequence text) {
+        if (Objects.equals(emptyToNull(tv.getText()), emptyToNull(text))) return false;
+        tv.setText(text);
+        return true;
+    }
+
+    /**
+     * Return {@code true} if it is voice capable
+     */
+    public static boolean isVoiceCapable(Context context) {
+        final TelephonyManager telephony =
+                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        return telephony != null && telephony.isVoiceCapable();
+    }
+}
diff --git a/packages/SystemUI/res-keyguard/drawable-xxxhdpi/default_preview.png b/packages/SystemUI/res-keyguard/drawable-xxxhdpi/default_preview.png
deleted file mode 100644
index 035a4df..0000000
--- a/packages/SystemUI/res-keyguard/drawable-xxxhdpi/default_preview.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Util.java b/packages/SystemUI/src/com/android/systemui/volume/Util.java
index c6d6218..7edb5a5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/Util.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/Util.java
@@ -16,52 +16,13 @@
 
 package com.android.systemui.volume;
 
-import android.content.Context;
 import android.media.AudioManager;
-import android.media.MediaMetadata;
-import android.media.VolumeProvider;
-import android.media.session.MediaController.PlaybackInfo;
-import android.media.session.PlaybackState;
-import android.telephony.TelephonyManager;
 import android.view.View;
-import android.widget.TextView;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-import java.util.Objects;
 
 /**
  * Static helpers for the volume dialog.
  */
-class Util {
-
-    // Note: currently not shown (only used in the text footer)
-    private static final SimpleDateFormat HMMAA = new SimpleDateFormat("h:mm aa", Locale.US);
-
-    private static int[] AUDIO_MANAGER_FLAGS = new int[] {
-        AudioManager.FLAG_SHOW_UI,
-        AudioManager.FLAG_VIBRATE,
-        AudioManager.FLAG_PLAY_SOUND,
-        AudioManager.FLAG_ALLOW_RINGER_MODES,
-        AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE,
-        AudioManager.FLAG_SHOW_VIBRATE_HINT,
-        AudioManager.FLAG_SHOW_SILENT_HINT,
-        AudioManager.FLAG_FROM_KEY,
-        AudioManager.FLAG_SHOW_UI_WARNINGS,
-    };
-
-    private static String[] AUDIO_MANAGER_FLAG_NAMES = new String[] {
-        "SHOW_UI",
-        "VIBRATE",
-        "PLAY_SOUND",
-        "ALLOW_RINGER_MODES",
-        "REMOVE_SOUND_AND_VIBRATE",
-        "SHOW_VIBRATE_HINT",
-        "SHOW_SILENT_HINT",
-        "FROM_KEY",
-        "SHOW_UI_WARNINGS",
-    };
+class Util extends com.android.settingslib.volume.Util {
 
     public static String logTag(Class<?> c) {
         final String tag = "vol." + c.getSimpleName();
@@ -70,106 +31,19 @@
 
     public static String ringerModeToString(int ringerMode) {
         switch (ringerMode) {
-            case AudioManager.RINGER_MODE_SILENT: return "RINGER_MODE_SILENT";
-            case AudioManager.RINGER_MODE_VIBRATE: return "RINGER_MODE_VIBRATE";
-            case AudioManager.RINGER_MODE_NORMAL: return "RINGER_MODE_NORMAL";
-            default: return "RINGER_MODE_UNKNOWN_" + ringerMode;
+            case AudioManager.RINGER_MODE_SILENT:
+                return "RINGER_MODE_SILENT";
+            case AudioManager.RINGER_MODE_VIBRATE:
+                return "RINGER_MODE_VIBRATE";
+            case AudioManager.RINGER_MODE_NORMAL:
+                return "RINGER_MODE_NORMAL";
+            default:
+                return "RINGER_MODE_UNKNOWN_" + ringerMode;
         }
     }
 
-    public static String mediaMetadataToString(MediaMetadata metadata) {
-        if (metadata == null) return null;
-        return metadata.getDescription().toString();
-    }
-
-    public static String playbackInfoToString(PlaybackInfo info) {
-        if (info == null) return null;
-        final String type = playbackInfoTypeToString(info.getPlaybackType());
-        final String vc = volumeProviderControlToString(info.getVolumeControl());
-        return String.format("PlaybackInfo[vol=%s,max=%s,type=%s,vc=%s],atts=%s",
-                info.getCurrentVolume(), info.getMaxVolume(), type, vc, info.getAudioAttributes());
-    }
-
-    public static String playbackInfoTypeToString(int type) {
-        switch (type) {
-            case PlaybackInfo.PLAYBACK_TYPE_LOCAL: return "LOCAL";
-            case PlaybackInfo.PLAYBACK_TYPE_REMOTE: return "REMOTE";
-            default: return "UNKNOWN_" + type;
-        }
-    }
-
-    public static String playbackStateStateToString(int state) {
-        switch (state) {
-            case PlaybackState.STATE_NONE: return "STATE_NONE";
-            case PlaybackState.STATE_STOPPED: return "STATE_STOPPED";
-            case PlaybackState.STATE_PAUSED: return "STATE_PAUSED";
-            case PlaybackState.STATE_PLAYING: return "STATE_PLAYING";
-            default: return "UNKNOWN_" + state;
-        }
-    }
-
-    public static String volumeProviderControlToString(int control) {
-        switch (control) {
-            case VolumeProvider.VOLUME_CONTROL_ABSOLUTE: return "VOLUME_CONTROL_ABSOLUTE";
-            case VolumeProvider.VOLUME_CONTROL_FIXED: return "VOLUME_CONTROL_FIXED";
-            case VolumeProvider.VOLUME_CONTROL_RELATIVE: return "VOLUME_CONTROL_RELATIVE";
-            default: return "VOLUME_CONTROL_UNKNOWN_" + control;
-        }
-    }
-
-    public static String playbackStateToString(PlaybackState playbackState) {
-        if (playbackState == null) return null;
-        return playbackStateStateToString(playbackState.getState()) + " " + playbackState;
-    }
-
-    public static String audioManagerFlagsToString(int value) {
-        return bitFieldToString(value, AUDIO_MANAGER_FLAGS, AUDIO_MANAGER_FLAG_NAMES);
-    }
-
-    private static String bitFieldToString(int value, int[] values, String[] names) {
-        if (value == 0) return "";
-        final StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < values.length; i++) {
-            if ((value & values[i]) != 0) {
-                if (sb.length() > 0) sb.append(',');
-                sb.append(names[i]);
-            }
-            value &= ~values[i];
-        }
-        if (value != 0) {
-            if (sb.length() > 0) sb.append(',');
-            sb.append("UNKNOWN_").append(value);
-        }
-        return sb.toString();
-    }
-
-    public static String getShortTime(long millis) {
-        return HMMAA.format(new Date(millis));
-    }
-
-    private static CharSequence emptyToNull(CharSequence str) {
-        return str == null || str.length() == 0 ? null : str;
-    }
-
-    public static boolean setText(TextView tv, CharSequence text) {
-        if (Objects.equals(emptyToNull(tv.getText()), emptyToNull(text))) return false;
-        tv.setText(text);
-        return true;
-    }
-
     public static final void setVisOrGone(View v, boolean vis) {
         if (v == null || (v.getVisibility() == View.VISIBLE) == vis) return;
         v.setVisibility(vis ? View.VISIBLE : View.GONE);
     }
-
-    public static final void setVisOrInvis(View v, boolean vis) {
-        if (v == null || (v.getVisibility() == View.VISIBLE) == vis) return;
-        v.setVisibility(vis ? View.VISIBLE : View.INVISIBLE);
-    }
-
-    public static boolean isVoiceCapable(Context context) {
-        final TelephonyManager telephony =
-                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
-        return telephony != null && telephony.isVoiceCapable();
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 32bc01c..4c16297 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -53,6 +53,7 @@
 import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.settingslib.volume.MediaSessions;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.SysUiServiceProvider;
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index e9ae516..9882f6c 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -138,9 +138,19 @@
     }
 
     /**
-     * Sets the mode, if supported.
+     * Sets the display modes the system is allowed to switch between, roughly ordered by
+     * preference.
+     *
+     * Not all display devices will automatically switch between modes, so it's important that the
+     * most-desired modes are at the beginning of the allowed array.
      */
-    public void requestDisplayModesLocked(int colorMode, int modeId) {
+    public void setAllowedDisplayModesLocked(int[] modes) {
+    }
+
+    /**
+     * Sets the requested color mode.
+     */
+    public void setRequestedColorModeLocked(int colorMode) {
     }
 
     public void onOverlayChangedLocked() {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 6c00da2..32f34b8 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -167,13 +167,13 @@
     private static final int MSG_DELIVER_DISPLAY_EVENT = 3;
     private static final int MSG_REQUEST_TRAVERSAL = 4;
     private static final int MSG_UPDATE_VIEWPORT = 5;
-    private static final int MSG_REGISTER_BRIGHTNESS_TRACKER = 6;
-    private static final int MSG_LOAD_BRIGHTNESS_CONFIGURATION = 7;
+    private static final int MSG_LOAD_BRIGHTNESS_CONFIGURATION = 6;
 
     private final Context mContext;
     private final DisplayManagerHandler mHandler;
     private final Handler mUiHandler;
     private final DisplayAdapterListener mDisplayAdapterListener;
+    private final DisplayModeDirector mDisplayModeDirector;
     private WindowManagerInternal mWindowManagerInternal;
     private InputManagerInternal mInputManagerInternal;
     private IMediaProjectionManager mProjectionService;
@@ -310,6 +310,7 @@
         mHandler = new DisplayManagerHandler(DisplayThread.get().getLooper());
         mUiHandler = UiThread.getHandler();
         mDisplayAdapterListener = new DisplayAdapterListener();
+        mDisplayModeDirector = new DisplayModeDirector(context, mHandler);
         mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false);
         Resources resources = mContext.getResources();
         mDefaultDisplayDefaultColorMode = mContext.getResources().getInteger(
@@ -322,7 +323,7 @@
         mMinimumBrightnessCurve = new Curve(lux, nits);
         mMinimumBrightnessSpline = Spline.createSpline(lux, nits);
 
-        PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        PowerManager pm = mContext.getSystemService(PowerManager.class);
         mGlobalDisplayBrightness = pm.getDefaultScreenBrightnessSetting();
         mCurrentUserId = UserHandle.USER_SYSTEM;
         ColorSpace[] colorSpaces = SurfaceControl.getCompositionColorSpaces();
@@ -347,9 +348,9 @@
         // adapter is up so that we have it's configuration. We could load it lazily, but since
         // we're going to have to read it in eventually we may as well do it here rather than after
         // we've waited for the display to register itself with us.
-		synchronized(mSyncRoot) {
-			mPersistentDataStore.loadIfNeeded();
-			loadStableDisplayValuesLocked();
+        synchronized (mSyncRoot) {
+            mPersistentDataStore.loadIfNeeded();
+            loadStableDisplayValuesLocked();
         }
         mHandler.sendEmptyMessage(MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS);
 
@@ -417,8 +418,10 @@
             mOnlyCore = onlyCore;
         }
 
+        mDisplayModeDirector.setListener(new AllowedDisplayModeObserver());
+        mDisplayModeDirector.start();
+
         mHandler.sendEmptyMessage(MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS);
-        mHandler.sendEmptyMessage(MSG_REGISTER_BRIGHTNESS_TRACKER);
     }
 
     @VisibleForTesting
@@ -1194,13 +1197,8 @@
                 requestedModeId = display.getDisplayInfoLocked().findDefaultModeByRefreshRate(
                         requestedRefreshRate);
             }
-            if (display.getRequestedModeIdLocked() != requestedModeId) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Display " + displayId + " switching to mode " + requestedModeId);
-                }
-                display.setRequestedModeIdLocked(requestedModeId);
-                scheduleTraversalLocked(inTraversal);
-            }
+            mDisplayModeDirector.getAppRequestObserver().setAppRequestedMode(
+                    displayId, requestedModeId);
         }
     }
 
@@ -1319,6 +1317,28 @@
         return SurfaceControl.getDisplayedContentSample(token, maxFrames, timestamp);
     }
 
+    private void onAllowedDisplayModesChangedInternal() {
+        boolean changed = false;
+        synchronized (mSyncRoot) {
+            final int count = mLogicalDisplays.size();
+            for (int i = 0; i < count; i++) {
+                LogicalDisplay display = mLogicalDisplays.valueAt(i);
+                int displayId = mLogicalDisplays.keyAt(i);
+                int[] allowedModes = mDisplayModeDirector.getAllowedModes(displayId);
+                // Note that order is important here since not all display devices are capable of
+                // automatically switching, so we do actually want to check for equality and not
+                // just equivalent contents (regardless of order).
+                if (!Arrays.equals(allowedModes, display.getAllowedDisplayModesLocked())) {
+                    display.setAllowedDisplayModesLocked(allowedModes);
+                    changed = true;
+                }
+            }
+            if (changed) {
+                scheduleTraversalLocked(false);
+            }
+        }
+    }
+
     private void clearViewportsLocked() {
         mViewports.clear();
     }
@@ -1518,6 +1538,9 @@
                 display.dumpLocked(ipw);
             }
 
+            pw.println();
+            mDisplayModeDirector.dump(pw);
+
             final int callbackCount = mCallbacks.size();
             pw.println();
             pw.println("Callbacks: size=" + callbackCount);
@@ -2431,4 +2454,10 @@
         }
 
     }
+
+    class AllowedDisplayModeObserver implements DisplayModeDirector.Listener {
+        public void onAllowedDisplayModesChanged() {
+            onAllowedDisplayModesChangedInternal();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
new file mode 100644
index 0000000..af4db07
--- /dev/null
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -0,0 +1,685 @@
+/*
+ * Copyright (C) 2019 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.display;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.hardware.display.DisplayManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import com.android.internal.R;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * The DisplayModeDirector is responsible for determining what modes are allowed to be
+ * automatically picked by the system based on system-wide and display-specific configuration.
+ */
+public class DisplayModeDirector {
+    private static final String TAG = "DisplayModeDirector";
+    private static final boolean DEBUG = false;
+
+    private static final int MSG_ALLOWED_MODES_CHANGED = 1;
+
+    // Special ID used to indicate that given vote is to be applied globally, rather than to a
+    // specific display.
+    private static final int GLOBAL_ID = -1;
+
+    // What we consider to be the system's "default" refresh rate.
+    private static final float DEFAULT_REFRESH_RATE = 60f;
+
+    // The tolerance within which we consider something approximately equals.
+    private static final float EPSILON = 0.001f;
+
+    private final Object mLock = new Object();
+    private final Context mContext;
+
+    private final DisplayModeDirectorHandler mHandler;
+
+    // A map from the display ID to the collection of votes and their priority. The latter takes
+    // the form of another map from the priority to the vote itself so that each priority is
+    // guaranteed to have exactly one vote, which is also easily and efficiently replaceable.
+    private final SparseArray<SparseArray<Vote>> mVotesByDisplay;
+    // A map from the display ID to the supported modes on that display.
+    private final SparseArray<Display.Mode[]> mSupportedModesByDisplay;
+    // A map from the display ID to the default mode of that display.
+    private final SparseArray<Display.Mode> mDefaultModeByDisplay;
+
+    private final AppRequestObserver mAppRequestObserver;
+    private final SettingsObserver mSettingsObserver;
+    private final DisplayObserver mDisplayObserver;
+
+
+    private Listener mListener;
+
+    public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler) {
+        mContext = context;
+        mHandler = new DisplayModeDirectorHandler(handler.getLooper());
+        mVotesByDisplay = new SparseArray<>();
+        mSupportedModesByDisplay = new SparseArray<>();
+        mDefaultModeByDisplay =  new SparseArray<>();
+        mAppRequestObserver = new AppRequestObserver();
+        mSettingsObserver = new SettingsObserver(context, handler);
+        mDisplayObserver = new DisplayObserver(context, handler);
+    }
+
+    /**
+     * Tells the DisplayModeDirector to update allowed votes and begin observing relevant system
+     * state.
+     *
+     * This has to be deferred because the object may be constructed before the rest of the system
+     * is ready.
+     */
+    public void start() {
+        mSettingsObserver.observe();
+        mDisplayObserver.observe();
+        mSettingsObserver.observe();
+        synchronized (mLock) {
+            // We may have a listener already registered before the call to start, so go ahead and
+            // notify them to pick up our newly initialized state.
+            notifyAllowedModesChangedLocked();
+        }
+    }
+
+    /**
+     * Calculates the modes the system is allowed to freely switch between based on global and
+     * display-specific constraints.
+     *
+     * @param displayId The display to query for.
+     * @return The IDs of the modes the system is allowed to freely switch between.
+     */
+    @NonNull
+    public int[] getAllowedModes(int displayId) {
+        synchronized (mLock) {
+            SparseArray<Vote> votes = getVotesLocked(displayId);
+            Display.Mode[] modes = mSupportedModesByDisplay.get(displayId);
+            Display.Mode defaultMode = mDefaultModeByDisplay.get(displayId);
+            if (modes == null || defaultMode == null) {
+                Slog.e(TAG, "Asked about unknown display, returning empty allowed set! (id="
+                        + displayId + ")");
+                return new int[0];
+            }
+            return getAllowedModesLocked(votes, modes, defaultMode);
+        }
+    }
+
+    @NonNull
+    private SparseArray<Vote> getVotesLocked(int displayId) {
+        SparseArray<Vote> displayVotes = mVotesByDisplay.get(displayId);
+        final SparseArray<Vote> votes;
+        if (displayVotes != null) {
+            votes = displayVotes.clone();
+        } else {
+            votes = new SparseArray<>();
+        }
+
+        SparseArray<Vote> globalVotes = mVotesByDisplay.get(GLOBAL_ID);
+        if (globalVotes != null) {
+            for (int i = 0; i < globalVotes.size(); i++) {
+                int priority = globalVotes.keyAt(i);
+                if (votes.indexOfKey(priority) < 0) {
+                    votes.put(priority, globalVotes.valueAt(i));
+                }
+            }
+        }
+        return votes;
+    }
+
+    @NonNull
+    private int[] getAllowedModesLocked(@NonNull SparseArray<Vote> votes,
+            @NonNull Display.Mode[] modes, @NonNull Display.Mode defaultMode) {
+        int lowestConsideredPriority = Vote.MIN_PRIORITY;
+        while (lowestConsideredPriority <= Vote.MAX_PRIORITY) {
+            float minRefreshRate = 0f;
+            float maxRefreshRate = Float.POSITIVE_INFINITY;
+            int height = Vote.INVALID_SIZE;
+            int width = Vote.INVALID_SIZE;
+
+            for (int priority = Vote.MAX_PRIORITY;
+                    priority >= lowestConsideredPriority;
+                    priority--) {
+                Vote vote = votes.get(priority);
+                if (vote == null) {
+                    continue;
+                }
+                // For refresh rates, just use the tightest bounds of all the votes
+                minRefreshRate = Math.max(minRefreshRate, vote.minRefreshRate);
+                maxRefreshRate = Math.min(maxRefreshRate, vote.maxRefreshRate);
+                // For display size, use only the first vote we come across (i.e. the highest
+                // priority vote that includes the width / height).
+                if (height == Vote.INVALID_SIZE && width == Vote.INVALID_SIZE
+                        && vote.height > 0 && vote.width > 0) {
+                    width = vote.width;
+                    height = vote.height;
+                }
+            }
+
+            // If we don't have anything specifying the width / height of the display, just use the
+            // default width and height. We don't want these switching out from underneath us since
+            // it's a pretty disruptive behavior.
+            if (height == Vote.INVALID_SIZE || width == Vote.INVALID_SIZE) {
+                width = defaultMode.getPhysicalWidth();
+                height = defaultMode.getPhysicalHeight();
+            }
+
+            int[] availableModes =
+                    filterModes(modes, width, height, minRefreshRate, maxRefreshRate);
+            if (availableModes.length > 0) {
+                if (DEBUG) {
+                    Slog.w(TAG, "Found available modes=" + Arrays.toString(availableModes)
+                            + " with lowest priority considered "
+                            + Vote.priorityToString(lowestConsideredPriority)
+                            + " and constraints: "
+                            + "width=" + width
+                            + ", height=" + height
+                            + ", minRefreshRate=" + minRefreshRate
+                            + ", maxRefreshRate=" + maxRefreshRate);
+                }
+                return availableModes;
+            }
+
+            if (DEBUG) {
+                Slog.w(TAG, "Couldn't find available modes with lowest priority set to "
+                        + Vote.priorityToString(lowestConsideredPriority)
+                        + " and with the following constraints: "
+                        + "width=" + width
+                        + ", height=" + height
+                        + ", minRefreshRate=" + minRefreshRate
+                        + ", maxRefreshRate=" + maxRefreshRate);
+            }
+            // If we haven't found anything with the current set of votes, drop the current lowest
+            // priority vote.
+            lowestConsideredPriority++;
+        }
+
+        // If we still haven't found anything that matches our current set of votes, just fall back
+        // to the default mode.
+        return new int[] { defaultMode.getModeId() };
+    }
+
+    private int[] filterModes(Display.Mode[] supportedModes,
+            int width, int height, float minRefreshRate, float maxRefreshRate) {
+        ArrayList<Display.Mode> availableModes = new ArrayList<>();
+        for (Display.Mode mode : supportedModes) {
+            if (mode.getPhysicalWidth() != width || mode.getPhysicalHeight() != height) {
+                if (DEBUG) {
+                    Slog.w(TAG, "Discarding mode " + mode.getModeId() + ", wrong size"
+                            + ": desiredWidth=" + width
+                            + ": desiredHeight=" + height
+                            + ": actualWidth=" + mode.getPhysicalWidth()
+                            + ": actualHeight=" + mode.getPhysicalHeight());
+                }
+                continue;
+            }
+            final float refreshRate = mode.getRefreshRate();
+            // Some refresh rates are calculated based on frame timings, so they aren't *exactly*
+            // equal to expected refresh rate. Given that, we apply a bit of tolerance to this
+            // comparison.
+            if (refreshRate < (minRefreshRate - EPSILON)
+                    || refreshRate > (maxRefreshRate + EPSILON)) {
+                if (DEBUG) {
+                    Slog.w(TAG, "Discarding mode " + mode.getModeId()
+                            + ", outside refresh rate bounds"
+                            + ": minRefreshRate=" + minRefreshRate
+                            + ", maxRefreshRate=" + maxRefreshRate
+                            + ", modeRefreshRate=" + refreshRate);
+                }
+                continue;
+            }
+            availableModes.add(mode);
+        }
+        final int size = availableModes.size();
+        int[] availableModeIds = new int[size];
+        for (int i = 0; i < size; i++) {
+            availableModeIds[i] = availableModes.get(i).getModeId();
+        }
+        return availableModeIds;
+    }
+
+    /**
+     * Gets the observer responsible for application display mode requests.
+     */
+    @NonNull
+    public AppRequestObserver getAppRequestObserver() {
+        // We don't need to lock here because mAppRequestObserver is a final field, which is
+        // guaranteed to be visible on all threads after construction.
+        return mAppRequestObserver;
+    }
+
+    /**
+     * Sets the listener for changes to allowed display modes.
+     */
+    public void setListener(@Nullable Listener listener) {
+        synchronized (mLock) {
+            mListener = listener;
+        }
+    }
+
+    /**
+     * Print the object's state and debug information into the given stream.
+     *
+     * @param pw The stream to dump information to.
+     */
+    public void dump(PrintWriter pw) {
+        pw.println("DisplayModeDirector");
+        synchronized (mLock) {
+            pw.println("  mSupportedModesByDisplay:");
+            for (int i = 0; i < mSupportedModesByDisplay.size(); i++) {
+                final int id = mSupportedModesByDisplay.keyAt(i);
+                final Display.Mode[] modes = mSupportedModesByDisplay.valueAt(i);
+                pw.println("    " + id + " -> " + Arrays.toString(modes));
+            }
+            pw.println("  mDefaultModeByDisplay:");
+            for (int i = 0; i < mDefaultModeByDisplay.size(); i++) {
+                final int id = mDefaultModeByDisplay.keyAt(i);
+                final Display.Mode mode = mDefaultModeByDisplay.valueAt(i);
+                pw.println("    " + id + " -> " + mode);
+            }
+            pw.println("  mVotesByDisplay:");
+            for (int i = 0; i < mVotesByDisplay.size(); i++) {
+                pw.println("    " + mVotesByDisplay.keyAt(i) + ":");
+                SparseArray<Vote> votes = mVotesByDisplay.valueAt(i);
+                for (int p = Vote.MAX_PRIORITY; p >= Vote.MIN_PRIORITY; p--) {
+                    Vote vote = votes.get(p);
+                    if (vote == null) {
+                        continue;
+                    }
+                    pw.println("      " + Vote.priorityToString(p) + " -> " + vote);
+                }
+            }
+            mSettingsObserver.dumpLocked(pw);
+            mAppRequestObserver.dumpLocked(pw);
+        }
+    }
+
+    private void updateVoteLocked(int priority, Vote vote) {
+        updateVoteLocked(GLOBAL_ID, priority, vote);
+    }
+
+    private void updateVoteLocked(int displayId, int priority, Vote vote) {
+        if (DEBUG) {
+            Slog.i(TAG, "updateVoteLocked(displayId=" + displayId
+                    + ", priority=" + Vote.priorityToString(priority)
+                    + ", vote=" + vote + ")");
+        }
+        if (priority < Vote.MIN_PRIORITY || priority > Vote.MAX_PRIORITY) {
+            Slog.w(TAG, "Received a vote with an invalid priority, ignoring:"
+                    + " priority=" + Vote.priorityToString(priority)
+                    + ", vote=" + vote, new Throwable());
+            return;
+        }
+        final SparseArray<Vote> votes = getOrCreateVotesByDisplay(displayId);
+
+        Vote currentVote = votes.get(priority);
+        if (vote != null) {
+            votes.put(priority, vote);
+        } else {
+            votes.remove(priority);
+        }
+
+        if (votes.size() == 0) {
+            if (DEBUG) {
+                Slog.i(TAG, "No votes left for display " + displayId + ", removing.");
+            }
+            mVotesByDisplay.remove(displayId);
+        }
+
+        notifyAllowedModesChangedLocked();
+    }
+
+    private void notifyAllowedModesChangedLocked() {
+        if (mListener != null && !mHandler.hasMessages(MSG_ALLOWED_MODES_CHANGED)) {
+            // We need to post this to a handler to avoid calling out while holding the lock
+            // since we know there are things that both listen for changes as well as provide
+            // information. If we did call out while holding the lock, then there's no guaranteed
+            // lock order and we run the real of risk deadlock.
+            Message msg = mHandler.obtainMessage(MSG_ALLOWED_MODES_CHANGED, mListener);
+            msg.sendToTarget();
+        }
+    }
+
+    private SparseArray<Vote> getOrCreateVotesByDisplay(int displayId) {
+        int index = mVotesByDisplay.indexOfKey(displayId);
+        if (mVotesByDisplay.indexOfKey(displayId) >= 0) {
+            return mVotesByDisplay.get(displayId);
+        } else {
+            SparseArray<Vote> votes = new SparseArray<>();
+            mVotesByDisplay.put(displayId, votes);
+            return votes;
+        }
+    }
+
+    /**
+     * Listens for changes to display mode coordination.
+     */
+    public interface Listener {
+        /**
+         * Called when the allowed display modes may have changed.
+         */
+        void onAllowedDisplayModesChanged();
+    }
+
+    private static final class DisplayModeDirectorHandler extends Handler {
+        DisplayModeDirectorHandler(Looper looper) {
+            super(looper, null, true /*async*/);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_ALLOWED_MODES_CHANGED:
+                    Listener listener = (Listener) msg.obj;
+                    listener.onAllowedDisplayModesChanged();
+                    break;
+            }
+        }
+    }
+
+    private static final class Vote {
+        public static final int PRIORITY_USER_SETTING = 0;
+        // We split the app request into two priorities in case we can satisfy one desire without
+        // the other.
+        public static final int PRIORITY_APP_REQUEST_REFRESH_RATE = 1;
+        public static final int PRIORITY_APP_REQUEST_SIZE = 2;
+        public static final int PRIORITY_LOW_POWER_MODE = 3;
+
+        // Whenever a new priority is added, remember to update MIN_PRIORITY and/or MAX_PRIORITY as
+        // appropriate, as well as priorityToString.
+
+        public static final int MIN_PRIORITY = PRIORITY_USER_SETTING;
+        public static final int MAX_PRIORITY = PRIORITY_LOW_POWER_MODE;
+
+        /**
+         * A value signifying an invalid width or height in a vote.
+         */
+        public static final int INVALID_SIZE = -1;
+
+        /**
+         * The requested width of the display in pixels, or INVALID_SIZE;
+         */
+        public final int width;
+        /**
+         * The requested height of the display in pixels, or INVALID_SIZE;
+         */
+        public final int height;
+
+        /**
+         * The lowest desired refresh rate.
+         */
+        public final float minRefreshRate;
+        /**
+         * The highest desired refresh rate.
+         */
+        public final float maxRefreshRate;
+
+        public static Vote forRefreshRates(float minRefreshRate, float maxRefreshRate) {
+            return new Vote(INVALID_SIZE, INVALID_SIZE, minRefreshRate, maxRefreshRate);
+        }
+
+        public static Vote forSize(int width, int height) {
+            return new Vote(width, height, 0f, Float.POSITIVE_INFINITY);
+        }
+
+        private Vote(int width, int height,
+                float minRefreshRate, float maxRefreshRate) {
+            this.width = width;
+            this.height = height;
+            this.minRefreshRate = minRefreshRate;
+            this.maxRefreshRate = maxRefreshRate;
+        }
+
+        public static String priorityToString(int priority) {
+            switch (priority) {
+                case PRIORITY_USER_SETTING:
+                    return "PRIORITY_USER_SETTING";
+                case PRIORITY_APP_REQUEST_REFRESH_RATE:
+                    return "PRIORITY_APP_REQUEST_REFRESH_RATE";
+                case PRIORITY_APP_REQUEST_SIZE:
+                    return "PRIORITY_APP_REQUEST_SIZE";
+                case PRIORITY_LOW_POWER_MODE:
+                    return "PRIORITY_LOW_POWER_MODE";
+                default:
+                    return Integer.toString(priority);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "Vote{"
+                + "width=" + width
+                + ", height=" + height
+                + ", minRefreshRate=" + minRefreshRate
+                + ", maxRefreshRate=" + maxRefreshRate
+                + "}";
+        }
+    }
+
+    private final class SettingsObserver extends ContentObserver {
+        private final Uri mRefreshRateSetting =
+                Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE);
+        private final Uri mLowPowerModeSetting =
+                Settings.Global.getUriFor(Settings.Global.LOW_POWER_MODE);
+
+        private final Context mContext;
+        private final float mDefaultPeakRefreshRate;
+
+        SettingsObserver(@NonNull Context context, @NonNull Handler handler) {
+            super(handler);
+            mContext = context;
+            mDefaultPeakRefreshRate = (float) context.getResources().getInteger(
+                    R.integer.config_defaultPeakRefreshRate);
+        }
+
+        public void observe() {
+            final ContentResolver cr = mContext.getContentResolver();
+            cr.registerContentObserver(mRefreshRateSetting, false /*notifyDescendants*/, this,
+                    UserHandle.USER_SYSTEM);
+            cr.registerContentObserver(mLowPowerModeSetting, false /*notifyDescendants*/, this,
+                    UserHandle.USER_SYSTEM);
+            synchronized (mLock) {
+                updateRefreshRateSettingLocked();
+                updateLowPowerModeSettingLocked();
+            }
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri, int userId) {
+            synchronized (mLock) {
+                if (mRefreshRateSetting.equals(uri)) {
+                    updateRefreshRateSettingLocked();
+                } else if (mLowPowerModeSetting.equals(uri)) {
+                    updateLowPowerModeSettingLocked();
+                }
+            }
+        }
+
+        private void updateLowPowerModeSettingLocked() {
+            boolean inLowPowerMode = Settings.Global.getInt(mContext.getContentResolver(),
+                    Settings.Global.LOW_POWER_MODE, 0 /*default*/) != 0;
+            final Vote vote;
+            if (inLowPowerMode) {
+                vote = Vote.forRefreshRates(0f, 60f);
+            } else {
+                vote = null;
+            }
+            updateVoteLocked(Vote.PRIORITY_LOW_POWER_MODE, vote);
+        }
+
+        private void updateRefreshRateSettingLocked() {
+            float peakRefreshRate = Settings.System.getFloat(mContext.getContentResolver(),
+                    Settings.System.PEAK_REFRESH_RATE, DEFAULT_REFRESH_RATE);
+            Vote vote = Vote.forRefreshRates(0f, peakRefreshRate);
+            updateVoteLocked(Vote.PRIORITY_USER_SETTING, vote);
+        }
+
+        public void dumpLocked(PrintWriter pw) {
+            pw.println("  SettingsObserver");
+            pw.println("    mDefaultPeakRefreshRate: " + mDefaultPeakRefreshRate);
+        }
+    }
+
+    final class AppRequestObserver {
+        private SparseArray<Display.Mode> mAppRequestedModeByDisplay;
+
+        AppRequestObserver() {
+            mAppRequestedModeByDisplay = new SparseArray<>();
+        }
+
+        public void setAppRequestedMode(int displayId, int modeId) {
+            synchronized (mLock) {
+                setAppRequestedModeLocked(displayId, modeId);
+            }
+        }
+
+        private void setAppRequestedModeLocked(int displayId, int modeId) {
+            final Display.Mode requestedMode = findModeByIdLocked(displayId, modeId);
+            if (Objects.equals(requestedMode, mAppRequestedModeByDisplay.get(displayId))) {
+                return;
+            }
+
+            final Vote refreshRateVote;
+            final Vote sizeVote;
+            if (requestedMode != null) {
+                mAppRequestedModeByDisplay.put(displayId, requestedMode);
+                float refreshRate = requestedMode.getRefreshRate();
+                refreshRateVote = Vote.forRefreshRates(refreshRate, refreshRate);
+                sizeVote = Vote.forSize(requestedMode.getPhysicalWidth(),
+                        requestedMode.getPhysicalHeight());
+            } else {
+                mAppRequestedModeByDisplay.remove(displayId);
+                refreshRateVote = null;
+                sizeVote = null;
+            }
+            updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, refreshRateVote);
+            updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_SIZE, sizeVote);
+            return;
+        }
+
+        private Display.Mode findModeByIdLocked(int displayId, int modeId) {
+            Display.Mode[] modes = mSupportedModesByDisplay.get(displayId);
+            if (modes == null) {
+                return null;
+            }
+            for (Display.Mode mode : modes) {
+                if (mode.getModeId() == modeId) {
+                    return mode;
+                }
+            }
+            return null;
+        }
+
+        public void dumpLocked(PrintWriter pw) {
+            pw.println("  AppRequestObserver");
+            pw.println("    mAppRequestedModeByDisplay:");
+            for (int i = 0; i < mAppRequestedModeByDisplay.size(); i++) {
+                final int id = mAppRequestedModeByDisplay.keyAt(i);
+                final Display.Mode mode = mAppRequestedModeByDisplay.valueAt(i);
+                pw.println("    " + id + " -> " + mode);
+            }
+        }
+    }
+
+    private final class DisplayObserver implements DisplayManager.DisplayListener {
+        // Note that we can never call into DisplayManager or any of the non-POD classes it
+        // returns, while holding mLock since it may call into DMS, which might be simultaneously
+        // calling into us already holding its own lock.
+        private final Context mContext;
+        private final Handler mHandler;
+
+        DisplayObserver(Context context, Handler handler) {
+            mContext = context;
+            mHandler = handler;
+        }
+
+        public void observe() {
+            DisplayManager dm = mContext.getSystemService(DisplayManager.class);
+            dm.registerDisplayListener(this, mHandler);
+
+            // Populate existing displays
+            SparseArray<Display.Mode[]> modes = new SparseArray<>();
+            SparseArray<Display.Mode> defaultModes = new SparseArray<>();
+            DisplayInfo info = new DisplayInfo();
+            Display[] displays = dm.getDisplays();
+            for (Display d : displays) {
+                final int displayId = d.getDisplayId();
+                d.getDisplayInfo(info);
+                modes.put(displayId, info.supportedModes);
+                defaultModes.put(displayId, info.getDefaultMode());
+            }
+            synchronized (mLock) {
+                final int size = modes.size();
+                for (int i = 0; i < size; i++) {
+                    mSupportedModesByDisplay.put(modes.keyAt(i), modes.valueAt(i));
+                    mDefaultModeByDisplay.put(defaultModes.keyAt(i), defaultModes.valueAt(i));
+                }
+            }
+        }
+
+        @Override
+        public void onDisplayAdded(int displayId) {
+            updateDisplayModes(displayId);
+        }
+
+        @Override
+        public void onDisplayRemoved(int displayId) {
+            synchronized (mLock) {
+                mSupportedModesByDisplay.remove(displayId);
+                mDefaultModeByDisplay.remove(displayId);
+            }
+        }
+
+        @Override
+        public void onDisplayChanged(int displayId) {
+            updateDisplayModes(displayId);
+        }
+
+        private void updateDisplayModes(int displayId) {
+            Display d = mContext.getSystemService(DisplayManager.class).getDisplay(displayId);
+            DisplayInfo info = new DisplayInfo();
+            d.getDisplayInfo(info);
+            boolean changed = false;
+            synchronized (mLock) {
+                if (!Arrays.equals(mSupportedModesByDisplay.get(displayId), info.supportedModes)) {
+                    mSupportedModesByDisplay.put(displayId, info.supportedModes);
+                    changed = true;
+                }
+                if (!Objects.equals(mDefaultModeByDisplay.get(displayId), info.getDefaultMode())) {
+                    changed = true;
+                    mDefaultModeByDisplay.put(displayId, info.getDefaultMode());
+                }
+                if (changed) {
+                    notifyAllowedModesChangedLocked();
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 77df10b..5e5ef26 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -112,16 +112,18 @@
                 activeColorMode = Display.COLOR_MODE_INVALID;
             }
             int[] colorModes = SurfaceControl.getDisplayColorModes(displayToken);
+            int[] allowedConfigs = SurfaceControl.getAllowedDisplayConfigs(displayToken);
             LocalDisplayDevice device = mDevices.get(physicalDisplayId);
             if (device == null) {
                 // Display was added.
                 final boolean isInternal = mDevices.size() == 0;
                 device = new LocalDisplayDevice(displayToken, physicalDisplayId,
-                        configs, activeConfig, colorModes, activeColorMode, isInternal);
+                        configs, activeConfig, allowedConfigs, colorModes, activeColorMode,
+                        isInternal);
                 mDevices.put(physicalDisplayId, device);
                 sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED);
             } else if (device.updatePhysicalDisplayInfoLocked(configs, activeConfig,
-                        colorModes, activeColorMode)) {
+                        allowedConfigs, colorModes, activeColorMode)) {
                 // Display properties changed.
                 sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED);
             }
@@ -167,26 +169,30 @@
         private boolean mHavePendingChanges;
         private int mState = Display.STATE_UNKNOWN;
         private int mBrightness = PowerManager.BRIGHTNESS_DEFAULT;
-        private int mActivePhysIndex;
         private int mDefaultModeId;
         private int mActiveModeId;
         private boolean mActiveModeInvalid;
+        private int[] mAllowedModeIds;
+        private boolean mAllowedModeIdsInvalid;
+        private int mActivePhysIndex;
+        private int[] mAllowedPhysIndexes;
         private int mActiveColorMode;
         private boolean mActiveColorModeInvalid;
         private Display.HdrCapabilities mHdrCapabilities;
         private boolean mSidekickActive;
         private SidekickInternal mSidekickInternal;
 
-        private  SurfaceControl.PhysicalDisplayInfo mDisplayInfos[];
+        private SurfaceControl.PhysicalDisplayInfo[] mDisplayInfos;
 
         LocalDisplayDevice(IBinder displayToken, long physicalDisplayId,
                 SurfaceControl.PhysicalDisplayInfo[] physicalDisplayInfos, int activeDisplayInfo,
-                int[] colorModes, int activeColorMode, boolean isInternal) {
+                int[] allowedDisplayInfos, int[] colorModes, int activeColorMode,
+                boolean isInternal) {
             super(LocalDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + physicalDisplayId);
             mPhysicalDisplayId = physicalDisplayId;
             mIsInternal = isInternal;
             updatePhysicalDisplayInfoLocked(physicalDisplayInfos, activeDisplayInfo,
-                    colorModes, activeColorMode);
+                    allowedDisplayInfos, colorModes, activeColorMode);
             updateColorModesLocked(colorModes, activeColorMode);
             mSidekickInternal = LocalServices.getService(SidekickInternal.class);
             if (mIsInternal) {
@@ -205,9 +211,10 @@
 
         public boolean updatePhysicalDisplayInfoLocked(
                 SurfaceControl.PhysicalDisplayInfo[] physicalDisplayInfos, int activeDisplayInfo,
-                int[] colorModes, int activeColorMode) {
+                int[] allowedDisplayInfos, int[] colorModes, int activeColorMode) {
             mDisplayInfos = Arrays.copyOf(physicalDisplayInfos, physicalDisplayInfos.length);
             mActivePhysIndex = activeDisplayInfo;
+            mAllowedPhysIndexes = Arrays.copyOf(allowedDisplayInfos, allowedDisplayInfos.length);
             // Build an updated list of all existing modes.
             ArrayList<DisplayModeRecord> records = new ArrayList<DisplayModeRecord>();
             boolean modesAdded = false;
@@ -246,8 +253,9 @@
                     break;
                 }
             }
-            // Check whether surface flinger spontaneously changed modes out from under us. Schedule
-            // traversals to ensure that the correct state is reapplied if necessary.
+
+            // Check whether surface flinger spontaneously changed modes out from under us.
+            // Schedule traversals to ensure that the correct state is reapplied if necessary.
             if (mActiveModeId != 0
                     && mActiveModeId != activeRecord.mMode.getModeId()) {
                 mActiveModeInvalid = true;
@@ -266,6 +274,7 @@
             for (DisplayModeRecord record : records) {
                 mSupportedModes.put(record.mMode.getModeId(), record);
             }
+
             // Update the default mode, if needed.
             if (findDisplayInfoIndexLocked(mDefaultModeId) < 0) {
                 if (mDefaultModeId != 0) {
@@ -274,6 +283,7 @@
                 }
                 mDefaultModeId = activeRecord.mMode.getModeId();
             }
+
             // Determine whether the active mode is still there.
             if (mSupportedModes.indexOfKey(mActiveModeId) < 0) {
                 if (mActiveModeId != 0) {
@@ -284,6 +294,21 @@
                 mActiveModeInvalid = true;
             }
 
+            // Determine what the currently allowed modes are
+            mAllowedModeIds = new int[] { mActiveModeId };
+            int[] allowedModeIds = new int[mAllowedPhysIndexes.length];
+            int size = 0;
+            for (int physIndex : mAllowedPhysIndexes) {
+                int modeId = findMatchingModeIdLocked(physIndex);
+                if (modeId > 0) {
+                    allowedModeIds[size++] = modeId;
+                }
+            }
+
+            // If this is different from our desired allowed modes, then mark our current set as
+            // invalid so we correct this on the next traversal.
+            mAllowedModeIdsInvalid = !Arrays.equals(allowedModeIds, mAllowedModeIds);
+
             // Schedule traversals so that we apply pending changes.
             sendTraversalRequestLocked();
             return true;
@@ -368,11 +393,7 @@
                 mInfo.height = phys.height;
                 mInfo.modeId = mActiveModeId;
                 mInfo.defaultModeId = mDefaultModeId;
-                mInfo.supportedModes = new Display.Mode[mSupportedModes.size()];
-                for (int i = 0; i < mSupportedModes.size(); i++) {
-                    DisplayModeRecord record = mSupportedModes.valueAt(i);
-                    mInfo.supportedModes[i] = record.mMode;
-                }
+                mInfo.supportedModes = getDisplayModes(mSupportedModes);
                 mInfo.colorMode = mActiveColorMode;
                 mInfo.supportedColorModes =
                         new int[mSupportedColorModes.size()];
@@ -593,44 +614,104 @@
         }
 
         @Override
-        public void requestDisplayModesLocked(int colorMode, int modeId) {
-            if (requestModeLocked(modeId) ||
-                    requestColorModeLocked(colorMode)) {
+        public void setRequestedColorModeLocked(int colorMode) {
+            if (requestColorModeLocked(colorMode)) {
                 updateDeviceInfoLocked();
             }
         }
 
         @Override
+        public void setAllowedDisplayModesLocked(int[] modes) {
+            updateAllowedModesLocked(modes);
+        }
+
+        @Override
         public void onOverlayChangedLocked() {
             updateDeviceInfoLocked();
         }
 
-        public boolean requestModeLocked(int modeId) {
-            if (modeId == 0) {
-                modeId = mDefaultModeId;
-            } else if (mSupportedModes.indexOfKey(modeId) < 0) {
-                Slog.w(TAG, "Requested mode " + modeId + " is not supported by this display,"
-                        + " reverting to default display mode.");
-                modeId = mDefaultModeId;
+        public void onActivePhysicalDisplayModeChangedLocked(int physIndex) {
+            if (updateActiveModeLocked(physIndex)) {
+                updateDeviceInfoLocked();
             }
+        }
 
-            int physIndex = findDisplayInfoIndexLocked(modeId);
-            if (physIndex < 0) {
-                Slog.w(TAG, "Requested mode ID " + modeId + " not available,"
-                        + " trying with default mode ID");
-                modeId = mDefaultModeId;
-                physIndex = findDisplayInfoIndexLocked(modeId);
-            }
-            if (mActivePhysIndex == physIndex) {
+        public boolean updateActiveModeLocked(int activePhysIndex) {
+            if (mActivePhysIndex == activePhysIndex) {
                 return false;
             }
-            SurfaceControl.setActiveConfig(getDisplayTokenLocked(), physIndex);
-            mActivePhysIndex = physIndex;
-            mActiveModeId = modeId;
-            mActiveModeInvalid = false;
+            mActivePhysIndex = activePhysIndex;
+            mActiveModeId = findMatchingModeIdLocked(activePhysIndex);
+            mActiveModeInvalid = mActiveModeId == 0;
+            if (mActiveModeInvalid) {
+                Slog.w(TAG, "In unknown mode after setting allowed configs"
+                        + ": allowedPhysIndexes=" + mAllowedPhysIndexes
+                        + ", activePhysIndex=" + mActivePhysIndex);
+            }
             return true;
         }
 
+        public void updateAllowedModesLocked(int[] allowedModes) {
+            if (Arrays.equals(allowedModes, mAllowedModeIds) && !mAllowedModeIdsInvalid) {
+                return;
+            }
+            if (updateAllowedModesInternalLocked(allowedModes)) {
+                updateDeviceInfoLocked();
+            }
+        }
+
+        public boolean updateAllowedModesInternalLocked(int[] allowedModes) {
+            if (DEBUG) {
+                Slog.w(TAG, "updateAllowedModesInternalLocked(allowedModes="
+                        + Arrays.toString(allowedModes) + ")");
+            }
+            int[] allowedPhysIndexes = new int[allowedModes.length];
+            int size = 0;
+            for (int modeId : allowedModes) {
+                int physIndex = findDisplayInfoIndexLocked(modeId);
+                if (physIndex < 0) {
+                    Slog.w(TAG, "Requested mode ID " + modeId + " not available,"
+                            + " dropping from allowed set.");
+                } else {
+                    allowedPhysIndexes[size++] = physIndex;
+                }
+            }
+
+            // If we couldn't find one or more of the suggested allowed modes then we need to
+            // shrink the array to its actual size.
+            if (size != allowedModes.length) {
+                allowedPhysIndexes = Arrays.copyOf(allowedPhysIndexes, size);
+            }
+
+            // If we found no suitable modes, then we try again with the default mode which we
+            // assume has a suitable physical config.
+            if (size == 0) {
+                if (DEBUG) {
+                    Slog.w(TAG, "No valid modes allowed, falling back to default mode (id="
+                            + mDefaultModeId + ")");
+                }
+                allowedModes = new int[] { mDefaultModeId };
+                allowedPhysIndexes = new int[] { findDisplayInfoIndexLocked(mDefaultModeId) };
+            }
+
+            mAllowedModeIds = allowedModes;
+            mAllowedModeIdsInvalid = false;
+
+            if (Arrays.equals(mAllowedPhysIndexes, allowedPhysIndexes)) {
+                return false;
+            }
+            mAllowedPhysIndexes = allowedPhysIndexes;
+
+            if (DEBUG) {
+                Slog.w(TAG, "Setting allowed physical configs: allowedPhysIndexes="
+                        + Arrays.toString(allowedPhysIndexes));
+            }
+
+            SurfaceControl.setAllowedDisplayConfigs(getDisplayTokenLocked(), allowedPhysIndexes);
+            int activePhysIndex = SurfaceControl.getActiveConfig(getDisplayTokenLocked());
+            return updateActiveModeLocked(activePhysIndex);
+        }
+
         public boolean requestColorModeLocked(int colorMode) {
             if (mActiveColorMode == colorMode) {
                 return false;
@@ -650,9 +731,13 @@
         public void dumpLocked(PrintWriter pw) {
             super.dumpLocked(pw);
             pw.println("mPhysicalDisplayId=" + mPhysicalDisplayId);
+            pw.println("mAllowedPhysIndexes=" + Arrays.toString(mAllowedPhysIndexes));
+            pw.println("mAllowedModeIds=" + Arrays.toString(mAllowedModeIds));
+            pw.println("mAllowedModeIdsInvalid=" + mAllowedModeIdsInvalid);
             pw.println("mActivePhysIndex=" + mActivePhysIndex);
             pw.println("mActiveModeId=" + mActiveModeId);
             pw.println("mActiveColorMode=" + mActiveColorMode);
+            pw.println("mDefaultModeId=" + mDefaultModeId);
             pw.println("mState=" + Display.stateToString(mState));
             pw.println("mBrightness=" + mBrightness);
             pw.println("mBacklight=" + mBacklight);
@@ -687,10 +772,31 @@
             return -1;
         }
 
+        private int findMatchingModeIdLocked(int physIndex) {
+            SurfaceControl.PhysicalDisplayInfo info = mDisplayInfos[physIndex];
+            for (int i = 0; i < mSupportedModes.size(); i++) {
+                DisplayModeRecord record = mSupportedModes.valueAt(i);
+                if (record.hasMatchingMode(info)) {
+                    return record.mMode.getModeId();
+                }
+            }
+            return 0;
+        }
+
         private void updateDeviceInfoLocked() {
             mInfo = null;
             sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
         }
+
+        private Display.Mode[] getDisplayModes(SparseArray<DisplayModeRecord> records) {
+            final int size = records.size();
+            Display.Mode[] modes = new Display.Mode[size];
+            for (int i = 0; i < size; i++) {
+                DisplayModeRecord record = records.valueAt(i);
+                modes[i] = record.mMode;
+            }
+            return modes;
+        }
     }
 
     /** Supplies a context whose Resources apply runtime-overlays */
@@ -745,12 +851,23 @@
         }
 
         @Override
-        public void onConfigChanged(long timestampNanos, long physicalDisplayId, int configId) {
+        public void onConfigChanged(long timestampNanos, long physicalDisplayId, int physIndex) {
             if (DEBUG) {
                 Slog.d(TAG, "onConfigChanged("
                         + "timestampNanos=" + timestampNanos
-                        + ", builtInDisplayId=" + physicalDisplayId
-                        + ", configId=" + configId + ")");
+                        + ", physicalDisplayId=" + physicalDisplayId
+                        + ", physIndex=" + physIndex + ")");
+            }
+            synchronized (getSyncRoot()) {
+                LocalDisplayDevice device = mDevices.get(physicalDisplayId);
+                if (device == null) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Received config change for unhandled physical display: "
+                                + "physicalDisplayId=" + physicalDisplayId);
+                    }
+                    return;
+                }
+                device.onActivePhysicalDisplayModeChangedLocked(physIndex);
             }
         }
     }
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index b21f0a7..a7b9051 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -87,7 +87,7 @@
     // True if the logical display has unique content.
     private boolean mHasContent;
 
-    private int mRequestedModeId;
+    private int[] mAllowedDisplayModes = new int[0];
     private int mRequestedColorMode;
 
     // The display offsets to apply to the display projection.
@@ -354,12 +354,14 @@
         // Set the layer stack.
         device.setLayerStackLocked(t, isBlanked ? BLANK_LAYER_STACK : mLayerStack);
 
-        // Set the color mode and mode.
+        // Set the color mode and allowed display mode.
         if (device == mPrimaryDisplayDevice) {
-            device.requestDisplayModesLocked(
-                    mRequestedColorMode, mRequestedModeId);
+            device.setAllowedDisplayModesLocked(mAllowedDisplayModes);
+            device.setRequestedColorModeLocked(mRequestedColorMode);
         } else {
-            device.requestDisplayModesLocked(0, 0);  // Revert to default.
+            // Reset to default for non primary displays
+            device.setAllowedDisplayModesLocked(new int[] {0});
+            device.setRequestedColorModeLocked(0);
         }
 
         // Only grab the display info now as it may have been changed based on the requests above.
@@ -463,17 +465,17 @@
     }
 
     /**
-     * Requests the given mode.
+     * Sets the display modes the system is free to switch between.
      */
-    public void setRequestedModeIdLocked(int modeId) {
-        mRequestedModeId = modeId;
+    public void setAllowedDisplayModesLocked(int[] modes) {
+        mAllowedDisplayModes = modes;
     }
 
     /**
-     * Returns the pending requested mode.
+     * Returns the display modes the system is free to switch between.
      */
-    public int getRequestedModeIdLocked() {
-        return mRequestedModeId;
+    public int[] getAllowedDisplayModesLocked() {
+        return mAllowedDisplayModes;
     }
 
     /**
@@ -532,7 +534,7 @@
         pw.println("mDisplayId=" + mDisplayId);
         pw.println("mLayerStack=" + mLayerStack);
         pw.println("mHasContent=" + mHasContent);
-        pw.println("mRequestedMode=" + mRequestedModeId);
+        pw.println("mAllowedDisplayModes=" + Arrays.toString(mAllowedDisplayModes));
         pw.println("mRequestedColorMode=" + mRequestedColorMode);
         pw.println("mDisplayOffset=(" + mDisplayOffsetX + ", " + mDisplayOffsetY + ")");
         pw.println("mDisplayScalingDisabled=" + mDisplayScalingDisabled);
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index 2f507d1..60cfbd0 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -16,9 +16,6 @@
 
 package com.android.server.display;
 
-import com.android.internal.util.DumpUtils;
-import com.android.internal.util.IndentingPrintWriter;
-
 import android.content.Context;
 import android.database.ContentObserver;
 import android.graphics.SurfaceTexture;
@@ -32,6 +29,9 @@
 import android.view.Surface;
 import android.view.SurfaceControl;
 
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.IndentingPrintWriter;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -315,7 +315,16 @@
         }
 
         @Override
-        public void requestDisplayModesLocked(int color, int id) {
+        public void setAllowedDisplayModesLocked(int[] modes) {
+            final int id;
+            if (modes.length > 0) {
+                // The allowed modes should be ordered by preference, so just use the first mode
+                // here.
+                id = modes[0];
+            } else {
+                // If we don't have any allowed modes, just use the default mode.
+                id = 0;
+            }
             int index = -1;
             if (id == 0) {
                 // Use the default.
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 9ab9975..71ec5b0 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -142,7 +142,6 @@
 
     // these need to match ElapsedRealtimeFlags enum in types.hal
     private static final int ELAPSED_REALTIME_HAS_TIMESTAMP_NS = 1;
-    private static final int ELAPSED_REALTIME_HAS_TIME_UNCERTAINTY_NS = 2;
 
     // IMPORTANT - the GPS_DELETE_* symbols here must match GnssAidingData enum in IGnss.hal
     private static final int GPS_DELETE_EPHEMERIS = 0x0001;
@@ -769,18 +768,15 @@
         float bearingAccuracyDegrees = location.getBearingAccuracyDegrees();
         long timestamp = location.getTime();
 
-        int elapsedRealtimeFlags = ELAPSED_REALTIME_HAS_TIMESTAMP_NS
-                | (location.hasElapsedRealtimeUncertaintyNanos()
-                        ? ELAPSED_REALTIME_HAS_TIME_UNCERTAINTY_NS : 0);
+        int elapsedRealtimeFlags = ELAPSED_REALTIME_HAS_TIMESTAMP_NS;
         long elapsedRealtimeNanos = location.getElapsedRealtimeNanos();
-        long elapsedRealtimeUncertaintyNanos = location.getElapsedRealtimeUncertaintyNanos();
 
         native_inject_best_location(
                 gnssLocationFlags, latitudeDegrees, longitudeDegrees,
                 altitudeMeters, speedMetersPerSec, bearingDegrees,
                 horizontalAccuracyMeters, verticalAccuracyMeters,
                 speedAccuracyMetersPerSecond, bearingAccuracyDegrees, timestamp,
-                elapsedRealtimeFlags, elapsedRealtimeNanos, elapsedRealtimeUncertaintyNanos);
+                elapsedRealtimeFlags, elapsedRealtimeNanos);
     }
 
     /** Returns true if the location request is too frequent. */
@@ -2174,8 +2170,7 @@
             double altitudeMeters, float speedMetersPerSec, float bearingDegrees,
             float horizontalAccuracyMeters, float verticalAccuracyMeters,
             float speedAccuracyMetersPerSecond, float bearingAccuracyDegrees,
-            long timestamp, int elapsedRealtimeFlags, long elapsedRealtimeNanos,
-            long elapsedRealtimeUncertaintyNanos);
+            long timestamp, int elapsedRealtimeFlags, long elapsedRealtimeNanos);
 
     private native void native_inject_location(double latitude, double longitude, float accuracy);
 
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index e8882ec..d39f20c 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -491,10 +491,6 @@
         SET(ElapsedRealtimeNanos, location.elapsedRealtime.timestampNs);
     }
 
-    if (flags & ElapsedRealtimeFlags::HAS_TIME_UNCERTAINTY_NS) {
-        SET(ElapsedUncertaintyRealtimeNanos, location.elapsedRealtime.timeUncertaintyNs);
-    }
-
     return object.get();
 }
 
@@ -525,8 +521,7 @@
         jdouble altitudeMeters, jfloat speedMetersPerSec, jfloat bearingDegrees,
         jfloat horizontalAccuracyMeters, jfloat verticalAccuracyMeters,
         jfloat speedAccuracyMetersPerSecond, jfloat bearingAccuracyDegrees,
-        jlong timestamp, jint elapsedRealtimeFlags, jlong elapsedRealtimeNanos,
-        jlong elapsedRealtimeUncertaintyNanos) {
+        jlong timestamp, jint elapsedRealtimeFlags, jlong elapsedRealtimeNanos) {
     GnssLocation_V2_0 location;
     location.v1_0 = createGnssLocation_V1_0(
             gnssLocationFlags, latitudeDegrees, longitudeDegrees, altitudeMeters,
@@ -536,7 +531,6 @@
 
     location.elapsedRealtime.flags = static_cast<uint16_t>(elapsedRealtimeFlags);
     location.elapsedRealtime.timestampNs = static_cast<uint64_t>(elapsedRealtimeNanos);
-    location.elapsedRealtime.timeUncertaintyNs = static_cast<uint64_t>(elapsedRealtimeUncertaintyNanos);
 
     return location;
 }
@@ -1893,8 +1887,7 @@
         jfloat bearingAccuracyDegrees,
         jlong timestamp,
         jint elapsedRealtimeFlags,
-        jlong elapsedRealtimeNanos,
-        jlong elapsedRealtimeUncertaintyNanos) {
+        jlong elapsedRealtimeNanos) {
     if (gnssHal_V2_0 != nullptr) {
         GnssLocation_V2_0 location = createGnssLocation_V2_0(
                 gnssLocationFlags,
@@ -1909,8 +1902,7 @@
                 bearingAccuracyDegrees,
                 timestamp,
                 elapsedRealtimeFlags,
-                elapsedRealtimeNanos,
-                elapsedRealtimeUncertaintyNanos);
+                elapsedRealtimeNanos);
         auto result = gnssHal_V2_0->injectBestLocation_2_0(location);
 
         if (!result.isOk() || !result) {
@@ -2821,7 +2813,7 @@
             android_location_GnssLocationProvider_read_nmea)},
     {"native_inject_time", "(JJI)V", reinterpret_cast<void *>(
             android_location_GnssLocationProvider_inject_time)},
-    {"native_inject_best_location", "(IDDDFFFFFFJIJJ)V", reinterpret_cast<void *>(
+    {"native_inject_best_location", "(IDDDFFFFFFJIJ)V", reinterpret_cast<void *>(
             android_location_GnssLocationProvider_inject_best_location)},
     {"native_inject_location", "(DDF)V", reinterpret_cast<void *>(
             android_location_GnssLocationProvider_inject_location)},
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
index 8caa39d..1f86171 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
@@ -175,9 +175,9 @@
     /** Verify app usage limit observer is added */
     @Test
     public void testAppUsageLimitObserver_AddObserver() {
-        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_30_MIN);
         assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
-        addAppUsageLimitObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN, TIME_30_MIN);
         assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID2));
         assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
     }
@@ -203,7 +203,7 @@
     /** Verify app usage limit observer is removed */
     @Test
     public void testAppUsageLimitObserver_RemoveObserver() {
-        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_30_MIN);
         assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
         mController.removeAppUsageLimitObserver(UID, OBS_ID1, USER_ID);
         assertFalse("Observer wasn't removed", hasAppUsageLimitObserver(UID, OBS_ID1));
@@ -290,9 +290,9 @@
     /** Re-adding an observer should result in only one copy */
     @Test
     public void testAppUsageLimitObserver_ObserverReAdd() {
-        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_30_MIN);
         assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
-        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_10_MIN);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_10_MIN, TIME_10_MIN);
         assertTrue("Observer wasn't added",
                 getAppUsageLimitObserver(UID, OBS_ID1).getTimeLimitMs() == TIME_10_MIN);
         mController.removeAppUsageLimitObserver(UID, OBS_ID1, USER_ID);
@@ -304,7 +304,7 @@
     public void testAllObservers_ExclusiveObserverIds() {
         addAppUsageObserver(OBS_ID1, GROUP1, TIME_10_MIN);
         addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
-        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_10_MIN);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_10_MIN, TIME_10_MIN);
         assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID1));
         assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID1));
         assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
@@ -396,7 +396,7 @@
     @Test
     public void testAppUsageLimitObserver_Accumulation() throws Exception {
         setTime(0L);
-        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_30_MIN);
         startUsage(PKG_SOC1);
         // Add 10 mins
         setTime(TIME_10_MIN);
@@ -456,7 +456,7 @@
     @Test
     public void testAppUsageLimitObserver_TimeoutOtherApp() throws Exception {
         setTime(0L);
-        addAppUsageLimitObserver(OBS_ID1, GROUP1, 4_000L);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, 4_000L, 4_000L);
         startUsage(PKG_SOC2);
         assertFalse(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
         setTime(6_000L);
@@ -498,7 +498,7 @@
     @Test
     public void testAppUsageLimitObserver_Timeout() throws Exception {
         setTime(0L);
-        addAppUsageLimitObserver(OBS_ID1, GROUP1, 4_000L);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, 4_000L, 4_000L);
         startUsage(PKG_SOC1);
         setTime(6_000L);
         assertTrue(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
@@ -551,7 +551,7 @@
         setTime(TIME_10_MIN);
         startUsage(PKG_GAME1);
         setTime(TIME_30_MIN);
-        addAppUsageLimitObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN, TIME_30_MIN);
         setTime(TIME_30_MIN + TIME_10_MIN);
         stopUsage(PKG_GAME1);
         assertFalse(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS));
@@ -612,7 +612,7 @@
         startUsage(PKG_SOC1);
         setTime(TIME_10_MIN);
         // 10 second time limit
-        addAppUsageLimitObserver(OBS_ID1, GROUP_SOC, 10_000L);
+        addAppUsageLimitObserver(OBS_ID1, GROUP_SOC, 10_000L, 10_000L);
         setTime(TIME_10_MIN + 5_000L);
         // Shouldn't call back in 6 seconds
         assertFalse(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
@@ -692,23 +692,23 @@
     public void testAppUsageLimitObserver_MaxObserverLimit() throws Exception {
         boolean receivedException = false;
         int ANOTHER_UID = UID + 1;
-        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
-        addAppUsageLimitObserver(OBS_ID2, GROUP1, TIME_30_MIN);
-        addAppUsageLimitObserver(OBS_ID3, GROUP1, TIME_30_MIN);
-        addAppUsageLimitObserver(OBS_ID4, GROUP1, TIME_30_MIN);
-        addAppUsageLimitObserver(OBS_ID5, GROUP1, TIME_30_MIN);
-        addAppUsageLimitObserver(OBS_ID6, GROUP1, TIME_30_MIN);
-        addAppUsageLimitObserver(OBS_ID7, GROUP1, TIME_30_MIN);
-        addAppUsageLimitObserver(OBS_ID8, GROUP1, TIME_30_MIN);
-        addAppUsageLimitObserver(OBS_ID9, GROUP1, TIME_30_MIN);
-        addAppUsageLimitObserver(OBS_ID10, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID2, GROUP1, TIME_30_MIN, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID3, GROUP1, TIME_30_MIN, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID4, GROUP1, TIME_30_MIN, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID5, GROUP1, TIME_30_MIN, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID6, GROUP1, TIME_30_MIN, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID7, GROUP1, TIME_30_MIN, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID8, GROUP1, TIME_30_MIN, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID9, GROUP1, TIME_30_MIN, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID10, GROUP1, TIME_30_MIN, TIME_30_MIN);
         // Readding an observer should not cause an IllegalStateException
-        addAppUsageLimitObserver(OBS_ID5, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID5, GROUP1, TIME_30_MIN, TIME_30_MIN);
         // Adding an observer for a different uid shouldn't cause an IllegalStateException
         mController.addAppUsageLimitObserver(
-                ANOTHER_UID, OBS_ID11, GROUP1, TIME_30_MIN, null, USER_ID);
+                ANOTHER_UID, OBS_ID11, GROUP1, TIME_30_MIN, TIME_30_MIN, null, USER_ID);
         try {
-            addAppUsageLimitObserver(OBS_ID11, GROUP1, TIME_30_MIN);
+            addAppUsageLimitObserver(OBS_ID11, GROUP1, TIME_30_MIN, TIME_30_MIN);
         } catch (IllegalStateException ise) {
             receivedException = true;
         }
@@ -748,9 +748,9 @@
     public void testAppUsageLimitObserver_MinimumTimeLimit() throws Exception {
         boolean receivedException = false;
         // adding an observer with a one minute time limit should not cause an exception
-        addAppUsageLimitObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT, MIN_TIME_LIMIT);
         try {
-            addAppUsageLimitObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT - 1);
+            addAppUsageLimitObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT - 1, MIN_TIME_LIMIT - 1);
         } catch (IllegalArgumentException iae) {
             receivedException = true;
         }
@@ -807,7 +807,7 @@
     @Test
     public void testAppUsageLimitObserver_ConcurrentUsage() throws Exception {
         setTime(0L);
-        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_30_MIN);
         AppTimeLimitController.UsageGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
         startUsage(PKG_SOC1);
         // Add 10 mins
@@ -967,7 +967,7 @@
     /** Verify app usage limit observer added correctly reports its total usage limit */
     @Test
     public void testAppUsageLimitObserver_GetTotalUsageLimit() {
-        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_30_MIN);
         AppTimeLimitController.AppUsageLimitGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
         assertNotNull("Observer wasn't added", group);
         assertEquals("Observer didn't correctly report total usage limit",
@@ -978,7 +978,7 @@
     @Test
     public void testAppUsageLimitObserver_GetUsageRemaining() {
         setTime(0L);
-        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_30_MIN);
         startUsage(PKG_SOC1);
         setTime(TIME_10_MIN);
         stopUsage(PKG_SOC1);
@@ -993,8 +993,8 @@
      */
     @Test
     public void testAppUsageLimitObserver_GetAppUsageLimit() {
-        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
-        addAppUsageLimitObserver(OBS_ID2, GROUP_SOC, TIME_10_MIN);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID2, GROUP_SOC, TIME_10_MIN, TIME_10_MIN);
         UsageStatsManagerInternal.AppUsageLimitData group = getAppUsageLimit(PKG_SOC1);
         assertEquals("Observer with the smallest usage limit remaining wasn't returned",
                 TIME_10_MIN, group.getTotalUsageLimit());
@@ -1006,8 +1006,8 @@
     @Test
     public void testAppUsageLimitObserver_GetAppUsageLimitUsed() {
         setTime(0L);
-        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
-        addAppUsageLimitObserver(OBS_ID2, GROUP_SOC, TIME_10_MIN);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID2, GROUP_SOC, TIME_10_MIN, TIME_10_MIN);
         startUsage(PKG_GAME1);
         setTime(TIME_10_MIN * 2 + TIME_1_MIN);
         stopUsage(PKG_GAME1);
@@ -1024,8 +1024,8 @@
     @Test
     public void testAppUsageLimitObserver_GetAppUsageLimitAllUsed() {
         setTime(0L);
-        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
-        addAppUsageLimitObserver(OBS_ID2, GROUP_SOC, TIME_10_MIN);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID2, GROUP_SOC, TIME_10_MIN, TIME_10_MIN);
         startUsage(PKG_SOC1);
         setTime(TIME_10_MIN);
         stopUsage(PKG_SOC1);
@@ -1035,10 +1035,21 @@
                 0L, group.getUsageRemaining());
     }
 
+    /** Verify that a limit of 0 is not allowed. */
+    @Test
+    public void testAppUsageLimitObserver_ZeroTimeLimitIsNotAllowed() {
+        try {
+            addAppUsageLimitObserver(OBS_ID1, GROUP1, 0, 0);
+            fail("timeLimit of 0 should not be allowed.");
+        } catch (IllegalArgumentException expected) {
+            // Exception expected.
+        }
+    }
+
     /** Verify that a limit of 0 is allowed for the special case of re-registering an observer. */
     @Test
-    public void testAppUsageLimitObserver_ZeroTimeLimitIsAllowed() {
-        addAppUsageLimitObserver(OBS_ID1, GROUP1, 0);
+    public void testAppUsageLimitObserver_ZeroTimeRemainingIsAllowed() {
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_1_MIN, 0);
         AppTimeLimitController.AppUsageLimitGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
         assertNotNull("Observer wasn't added", group);
         assertEquals("Usage remaining was not 0.", 0, group.getUsageRemaining());
@@ -1066,8 +1077,10 @@
                 null, null, USER_ID);
     }
 
-    private void addAppUsageLimitObserver(int observerId, String[] packages, long timeLimit) {
-        mController.addAppUsageLimitObserver(UID, observerId, packages, timeLimit, null, USER_ID);
+    private void addAppUsageLimitObserver(int observerId, String[] packages, long timeLimit,
+            long timeRemaining) {
+        mController.addAppUsageLimitObserver(UID, observerId, packages, timeLimit, timeRemaining,
+                null, USER_ID);
     }
 
     /** Is there still an app usage observer by that id */
diff --git a/services/usage/java/com/android/server/usage/AppTimeLimitController.java b/services/usage/java/com/android/server/usage/AppTimeLimitController.java
index 731cbf4..f3d6387 100644
--- a/services/usage/java/com/android/server/usage/AppTimeLimitController.java
+++ b/services/usage/java/com/android/server/usage/AppTimeLimitController.java
@@ -511,8 +511,10 @@
 
     class AppUsageLimitGroup extends UsageGroup {
         public AppUsageLimitGroup(UserData user, ObserverAppData observerApp, int observerId,
-                String[] observed, long timeLimitMs, PendingIntent limitReachedCallback) {
+                String[] observed, long timeLimitMs, long timeRemainingMs,
+                PendingIntent limitReachedCallback) {
             super(user, observerApp, observerId, observed, timeLimitMs, limitReachedCallback);
+            mUsageTimeMs = timeLimitMs - timeRemainingMs;
         }
 
         @Override
@@ -839,9 +841,9 @@
      * Existing app usage limit observer with the same observerId will be removed.
      */
     public void addAppUsageLimitObserver(int requestingUid, int observerId, String[] observed,
-            long timeLimit, PendingIntent callbackIntent, @UserIdInt int userId) {
-        // Allow the special case of the limit being 0, but with no callback.
-        if (timeLimit != 0L && timeLimit < getMinTimeLimit()) {
+            long timeLimit, long timeRemaining, PendingIntent callbackIntent,
+            @UserIdInt int userId) {
+        if (timeLimit < getMinTimeLimit()) {
             throw new IllegalArgumentException("Time limit must be >= " + getMinTimeLimit());
         }
         synchronized (mLock) {
@@ -859,7 +861,7 @@
                         "Too many app usage observers added by uid " + requestingUid);
             }
             group = new AppUsageLimitGroup(user, observerApp, observerId, observed, timeLimit,
-                    timeLimit == 0L ? null : callbackIntent);
+                    timeRemaining, timeRemaining == 0L ? null : callbackIntent);
             observerApp.appUsageLimitGroups.append(observerId, group);
 
             if (DEBUG) {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index b14d722..27fdbcb 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -1399,7 +1399,8 @@
 
         @Override
         public void registerAppUsageLimitObserver(int observerId, String[] packages,
-                long timeLimitMs, PendingIntent callbackIntent, String callingPackage) {
+                long timeLimitMs, long timeRemainingMs, PendingIntent callbackIntent,
+                String callingPackage) {
             if (!hasPermissions(callingPackage,
                     Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE)) {
                 throw new SecurityException("Caller doesn't have both SUSPEND_APPS and "
@@ -1409,7 +1410,11 @@
             if (packages == null || packages.length == 0) {
                 throw new IllegalArgumentException("Must specify at least one package");
             }
-            if (callbackIntent == null && timeLimitMs != 0L) {
+            if (timeRemainingMs > timeLimitMs) {
+                throw new IllegalArgumentException(
+                        "Remaining time can't be greater than total time.");
+            }
+            if (callbackIntent == null && timeRemainingMs != 0L) {
                 throw new NullPointerException("callbackIntent can't be null");
             }
             final int callingUid = Binder.getCallingUid();
@@ -1417,7 +1422,7 @@
             final long token = Binder.clearCallingIdentity();
             try {
                 UsageStatsService.this.registerAppUsageLimitObserver(callingUid, observerId,
-                        packages, timeLimitMs, callbackIntent, userId);
+                        packages, timeLimitMs, timeRemainingMs, callbackIntent, userId);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -1545,9 +1550,9 @@
     }
 
     void registerAppUsageLimitObserver(int callingUid, int observerId, String[] packages,
-            long timeLimitMs, PendingIntent callbackIntent, int userId) {
-        mAppTimeLimit.addAppUsageLimitObserver(callingUid, observerId, packages, timeLimitMs,
-                callbackIntent, userId);
+            long timeLimitMs, long timeRemainingMs, PendingIntent callbackIntent, int userId) {
+        mAppTimeLimit.addAppUsageLimitObserver(callingUid, observerId, packages,
+                timeLimitMs, timeRemainingMs, callbackIntent, userId);
     }
 
     void unregisterAppUsageLimitObserver(int callingUid, int observerId, int userId) {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index bce6f17..aaf5f33 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -10372,12 +10372,6 @@
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public void switchMultiSimConfig(int numOfSims) {
-        //only proceed if multi-sim is not restricted
-        if (!isMultisimSupported()) {
-            Rlog.e(TAG, "switchMultiSimConfig not possible. It is restricted or not supported.");
-            return;
-        }
-
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
diff --git a/telephony/java/android/telephony/ims/ImsCallSessionListener.java b/telephony/java/android/telephony/ims/ImsCallSessionListener.java
index 4519a57..a09844d 100644
--- a/telephony/java/android/telephony/ims/ImsCallSessionListener.java
+++ b/telephony/java/android/telephony/ims/ImsCallSessionListener.java
@@ -607,7 +607,7 @@
      *
      * @param profile updated ImsStreamMediaProfile
      */
-    public void callSessionRttAudioIndicatorChanged(ImsStreamMediaProfile profile) {
+    public void callSessionRttAudioIndicatorChanged(@NonNull ImsStreamMediaProfile profile) {
         try {
             mListener.callSessionRttAudioIndicatorChanged(profile);
         } catch (RemoteException e) {
diff --git a/telephony/java/android/telephony/ims/ImsSsData.java b/telephony/java/android/telephony/ims/ImsSsData.java
index 10001bc..464db34 100644
--- a/telephony/java/android/telephony/ims/ImsSsData.java
+++ b/telephony/java/android/telephony/ims/ImsSsData.java
@@ -17,6 +17,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -24,6 +25,9 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 
 /**
  * Provides STK Call Control Supplementary Service information.
@@ -260,13 +264,13 @@
     public final int result;
 
     private int[] mSsInfo;
-    private ImsCallForwardInfo[] mCfInfo;
-    private ImsSsInfo[] mImsSsInfo;
+    private List<ImsCallForwardInfo> mCfInfo;
+    private List<ImsSsInfo> mImsSsInfo;
 
     /**
      * Builder for optional ImsSsData parameters.
      */
-    public static class Builder {
+    public static final class Builder {
         private ImsSsData mImsSsData;
 
         /**
@@ -301,7 +305,7 @@
          * Set the array of {@link ImsSsInfo}s that are associated with this supplementary service
          * data.
          */
-        public @NonNull Builder setSuppServiceInfo(@NonNull ImsSsInfo[] imsSsInfos) {
+        public @NonNull Builder setSuppServiceInfo(@NonNull List<ImsSsInfo> imsSsInfos) {
             mImsSsData.mImsSsInfo = imsSsInfos;
             return this;
         }
@@ -311,7 +315,7 @@
          * service data.
          */
         public @NonNull Builder setCallForwardingInfo(
-                @NonNull ImsCallForwardInfo[] imsCallForwardInfos) {
+                @NonNull List<ImsCallForwardInfo> imsCallForwardInfos) {
             mImsSsData.mCfInfo = imsCallForwardInfos;
             return this;
         }
@@ -360,8 +364,8 @@
         serviceClass = in.readInt();
         result = in.readInt();
         mSsInfo = in.createIntArray();
-        mCfInfo = (ImsCallForwardInfo[])in.readParcelableArray(this.getClass().getClassLoader());
-        mImsSsInfo = (ImsSsInfo[])in.readParcelableArray(this.getClass().getClassLoader());
+        mCfInfo = in.readParcelableList(new ArrayList<>(), this.getClass().getClassLoader());
+        mImsSsInfo = in.readParcelableList(new ArrayList<>(), this.getClass().getClassLoader());
     }
 
     public static final @android.annotation.NonNull Creator<ImsSsData> CREATOR = new Creator<ImsSsData>() {
@@ -384,8 +388,8 @@
         out.writeInt(getServiceClass());
         out.writeInt(getResult());
         out.writeIntArray(mSsInfo);
-        out.writeParcelableArray(mCfInfo, 0);
-        out.writeParcelableArray(mImsSsInfo, 0);
+        out.writeParcelableList(mCfInfo, 0);
+        out.writeParcelableList(mImsSsInfo, 0);
     }
 
     @Override
@@ -500,12 +504,12 @@
 
     /** @hide */
     public void setImsSpecificSuppServiceInfo(ImsSsInfo[] imsSsInfo) {
-        mImsSsInfo = imsSsInfo;
+        mImsSsInfo = Arrays.asList(imsSsInfo);
     }
 
     /** @hide */
     public void setCallForwardingInfo(ImsCallForwardInfo[] cfInfo) {
-        mCfInfo = cfInfo;
+        mCfInfo = Arrays.asList(cfInfo);
     }
 
     /**
@@ -524,7 +528,7 @@
 
 
         int[] result = new int[2];
-        if (mImsSsInfo == null || mImsSsInfo.length == 0) {
+        if (mImsSsInfo == null || mImsSsInfo.size() == 0) {
             Rlog.e(TAG, "getSuppServiceInfoCompat: Could not parse mImsSsInfo, returning empty "
                     + "int[]");
             return result;
@@ -535,26 +539,26 @@
         if (isTypeClir()) {
             // Assume there will only be one ImsSsInfo.
             // contains {"n","m"} parameters
-            result[0] = mImsSsInfo[0].getClirOutgoingState();
-            result[1] = mImsSsInfo[0].getClirInterrogationStatus();
+            result[0] = mImsSsInfo.get(0).getClirOutgoingState();
+            result[1] = mImsSsInfo.get(0).getClirInterrogationStatus();
             return result;
         }
         // COLR 7.31
         if (isTypeColr()) {
-            result[0] = mImsSsInfo[0].getProvisionStatus();
+            result[0] = mImsSsInfo.get(0).getProvisionStatus();
         }
         // Facility Lock CLCK 7.4 (for call barring), CLIP 7.6, COLP 7.8, as well as any
         // other result, just return the status for the "n" parameter and provisioning status for
         // "m" as the default.
-        result[0] = mImsSsInfo[0].getStatus();
-        result[1] = mImsSsInfo[0].getProvisionStatus();
+        result[0] = mImsSsInfo.get(0).getStatus();
+        result[1] = mImsSsInfo.get(0).getProvisionStatus();
         return result;
     }
 
     /**
      * @return an array of {@link ImsSsInfo}s associated with this supplementary service data.
      */
-    public @NonNull ImsSsInfo[] getSuppServiceInfo() {
+    public @NonNull List<ImsSsInfo> getSuppServiceInfo() {
         return mImsSsInfo;
     }
 
@@ -562,7 +566,7 @@
      * @return an array of {@link ImsCallForwardInfo}s associated with this supplementary service
      * data.
      **/
-    public ImsCallForwardInfo[] getCallForwardInfo() {
+    public @Nullable List<ImsCallForwardInfo> getCallForwardInfo() {
         return mCfInfo;
     }
 
diff --git a/telephony/java/android/telephony/ims/ImsSsInfo.java b/telephony/java/android/telephony/ims/ImsSsInfo.java
index 303a9fe..18e7530 100644
--- a/telephony/java/android/telephony/ims/ImsSsInfo.java
+++ b/telephony/java/android/telephony/ims/ImsSsInfo.java
@@ -173,7 +173,7 @@
     /**
      * Builds {@link ImsSsInfo} instances, which may include optional parameters.
      */
-    public static class Builder {
+    public static final class Builder {
 
         private final ImsSsInfo mImsSsInfo;
 
@@ -304,7 +304,7 @@
     /**
      * @return The Incoming Communication Barring (ICB) number.
      */
-    public String getIncomingCommunicationBarringNumber() {
+    public @Nullable String getIncomingCommunicationBarringNumber() {
         return mIcbNum;
     }
 
diff --git a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
index 0105893..adcd11a 100644
--- a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
+++ b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
@@ -41,6 +41,7 @@
 import android.widget.TextView;
 import android.widget.Toast;
 
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -196,8 +197,8 @@
                     intent.setPackage(getPackageName());
                     intent.putExtra(EXTRA_KEY_TIMEOUT, true);
                     mUsageStatsManager.registerAppUsageLimitObserver(1, packages,
-                            60, TimeUnit.SECONDS, PendingIntent.getActivity(UsageStatsActivity.this,
-                                    1, intent, 0));
+                            Duration.ofSeconds(60), Duration.ofSeconds(60),
+                            PendingIntent.getActivity(UsageStatsActivity.this, 1, intent, 0));
                 }
             }
         });