Merge "Update methods of PacProcessor."
diff --git a/api/current.txt b/api/current.txt
index 8776d2d..09a65c3 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -21,6 +21,7 @@
     field public static final String ACTIVITY_RECOGNITION = "android.permission.ACTIVITY_RECOGNITION";
     field public static final String ADD_VOICEMAIL = "com.android.voicemail.permission.ADD_VOICEMAIL";
     field public static final String ANSWER_PHONE_CALLS = "android.permission.ANSWER_PHONE_CALLS";
+    field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA";
     field public static final String BATTERY_STATS = "android.permission.BATTERY_STATS";
     field public static final String BIND_ACCESSIBILITY_SERVICE = "android.permission.BIND_ACCESSIBILITY_SERVICE";
     field public static final String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET";
@@ -129,6 +130,7 @@
     field public static final String RECEIVE_SMS = "android.permission.RECEIVE_SMS";
     field public static final String RECEIVE_WAP_PUSH = "android.permission.RECEIVE_WAP_PUSH";
     field public static final String RECORD_AUDIO = "android.permission.RECORD_AUDIO";
+    field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO";
     field public static final String REORDER_TASKS = "android.permission.REORDER_TASKS";
     field public static final String REQUEST_COMPANION_RUN_IN_BACKGROUND = "android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND";
     field public static final String REQUEST_COMPANION_USE_DATA_IN_BACKGROUND = "android.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND";
@@ -12235,6 +12237,7 @@
     field public static final String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct";
     field public static final String FEATURE_WIFI_PASSPOINT = "android.hardware.wifi.passpoint";
     field public static final String FEATURE_WIFI_RTT = "android.hardware.wifi.rtt";
+    field public static final int FLAG_PERMISSION_ALLOWLIST_ROLE = 8; // 0x8
     field public static final int FLAG_PERMISSION_WHITELIST_INSTALLER = 2; // 0x2
     field public static final int FLAG_PERMISSION_WHITELIST_SYSTEM = 1; // 0x1
     field public static final int FLAG_PERMISSION_WHITELIST_UPGRADE = 4; // 0x4
@@ -12344,6 +12347,7 @@
     field public static final int FLAG_HARD_RESTRICTED = 4; // 0x4
     field public static final int FLAG_IMMUTABLY_RESTRICTED = 16; // 0x10
     field public static final int FLAG_INSTALLED = 1073741824; // 0x40000000
+    field public static final int FLAG_INSTALLER_EXEMPT_IGNORED = 32; // 0x20
     field public static final int FLAG_SOFT_RESTRICTED = 8; // 0x8
     field public static final int PROTECTION_DANGEROUS = 1; // 0x1
     field public static final int PROTECTION_FLAG_APPOP = 64; // 0x40
@@ -31607,6 +31611,7 @@
     method public boolean isEasyConnectSupported();
     method public boolean isEnhancedOpenSupported();
     method public boolean isEnhancedPowerReportingSupported();
+    method public boolean isMultiStaConcurrencySupported();
     method public boolean isP2pSupported();
     method public boolean isPreferredNetworkOffloadSupported();
     method @Deprecated public boolean isScanAlwaysAvailable();
diff --git a/api/system-current.txt b/api/system-current.txt
index b820ae0..aa9536e 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -2157,6 +2157,7 @@
     field public static final int FLAG_PERMISSION_ONE_TIME = 65536; // 0x10000
     field public static final int FLAG_PERMISSION_POLICY_FIXED = 4; // 0x4
     field public static final int FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT = 2048; // 0x800
+    field public static final int FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT = 262144; // 0x40000
     field public static final int FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT = 4096; // 0x1000
     field public static final int FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT = 8192; // 0x2000
     field public static final int FLAG_PERMISSION_REVIEW_REQUIRED = 64; // 0x40
@@ -7565,10 +7566,12 @@
 
   public final class WifiNetworkSuggestion implements android.os.Parcelable {
     method @NonNull public android.net.wifi.WifiConfiguration getWifiConfiguration();
+    method public boolean isOemPaid();
   }
 
   public static final class WifiNetworkSuggestion.Builder {
     method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING) public android.net.wifi.WifiNetworkSuggestion.Builder setCarrierId(int);
+    method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setOemPaid(boolean);
   }
 
   public class WifiScanner {
@@ -10718,16 +10721,12 @@
     method public int getTimeoutSeconds();
     method public boolean isEnabled();
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallForwardingInfo> CREATOR;
-    field public static final int ERROR_FDN_CHECK_FAILURE = 2; // 0x2
-    field public static final int ERROR_NOT_SUPPORTED = 3; // 0x3
-    field public static final int ERROR_UNKNOWN = 1; // 0x1
     field public static final int REASON_ALL = 4; // 0x4
     field public static final int REASON_ALL_CONDITIONAL = 5; // 0x5
     field public static final int REASON_BUSY = 1; // 0x1
     field public static final int REASON_NOT_REACHABLE = 3; // 0x3
     field public static final int REASON_NO_REPLY = 2; // 0x2
     field public static final int REASON_UNCONDITIONAL = 0; // 0x0
-    field public static final int SUCCESS = 0; // 0x0
   }
 
   public final class CallQuality implements android.os.Parcelable {
@@ -11470,7 +11469,7 @@
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setAllowedNetworkTypes(long);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallForwarding(@NonNull android.telephony.CallForwardingInfo, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallWaitingStatus(boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallWaitingEnabled(boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCarrierDataEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setCarrierRestrictionRules(@NonNull android.telephony.CarrierRestrictionRules);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataActivationState(int);
@@ -11569,6 +11568,10 @@
   public static interface TelephonyManager.CallForwardingInfoCallback {
     method public void onCallForwardingInfoAvailable(@NonNull android.telephony.CallForwardingInfo);
     method public void onError(int);
+    field public static final int RESULT_ERROR_FDN_CHECK_FAILURE = 2; // 0x2
+    field public static final int RESULT_ERROR_NOT_SUPPORTED = 3; // 0x3
+    field public static final int RESULT_ERROR_UNKNOWN = 1; // 0x1
+    field public static final int RESULT_SUCCESS = 0; // 0x0
   }
 
   public final class UiccAccessRule implements android.os.Parcelable {
diff --git a/api/test-current.txt b/api/test-current.txt
index 4c2aa5a..0540054 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1037,6 +1037,7 @@
     field public static final int FLAG_PERMISSION_ONE_TIME = 65536; // 0x10000
     field public static final int FLAG_PERMISSION_POLICY_FIXED = 4; // 0x4
     field public static final int FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT = 2048; // 0x800
+    field public static final int FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT = 262144; // 0x40000
     field public static final int FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT = 4096; // 0x1000
     field public static final int FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT = 8192; // 0x2000
     field public static final int FLAG_PERMISSION_REVIEW_REQUIRED = 64; // 0x40
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index d50cdee..9100d57 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -33,6 +33,7 @@
 import android.app.role.RoleControllerManager;
 import android.app.role.RoleManager;
 import android.app.slice.SliceManager;
+import android.app.time.TimeManager;
 import android.app.timedetector.TimeDetector;
 import android.app.timedetector.TimeDetectorImpl;
 import android.app.timezone.RulesManager;
@@ -1218,6 +1219,14 @@
                         return new TimeZoneDetectorImpl();
                     }});
 
+        registerService(Context.TIME_MANAGER, TimeManager.class,
+                new CachedServiceFetcher<TimeManager>() {
+                    @Override
+                    public TimeManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        return new TimeManager();
+                    }});
+
         registerService(Context.PERMISSION_SERVICE, PermissionManager.class,
                 new CachedServiceFetcher<PermissionManager>() {
                     @Override
diff --git a/core/java/android/app/timezonedetector/TimeZoneConfiguration.aidl b/core/java/android/app/time/ITimeZoneDetectorListener.aidl
similarity index 84%
copy from core/java/android/app/timezonedetector/TimeZoneConfiguration.aidl
copy to core/java/android/app/time/ITimeZoneDetectorListener.aidl
index 62240ba..723ad59 100644
--- a/core/java/android/app/timezonedetector/TimeZoneConfiguration.aidl
+++ b/core/java/android/app/time/ITimeZoneDetectorListener.aidl
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
-package android.app.timezonedetector;
+package android.app.time;
 
-parcelable TimeZoneConfiguration;
+/** {@hide} */
+oneway interface ITimeZoneDetectorListener {
+    void onChange();
+}
\ No newline at end of file
diff --git a/core/java/android/app/time/TEST_MAPPING b/core/java/android/app/time/TEST_MAPPING
new file mode 100644
index 0000000..951905b
--- /dev/null
+++ b/core/java/android/app/time/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+          "include-filter": "android.app.time."
+        }
+      ]
+    }
+  ]
+}
diff --git a/core/java/android/app/time/TimeManager.java b/core/java/android/app/time/TimeManager.java
new file mode 100644
index 0000000..9864afb
--- /dev/null
+++ b/core/java/android/app/time/TimeManager.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.time;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.app.timezonedetector.ITimeZoneDetectorService;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.concurrent.Executor;
+
+/**
+ * The interface through which system components can interact with time and time zone services.
+ *
+ * @hide
+ */
+// @SystemApi
+@SystemService(Context.TIME_MANAGER)
+public final class TimeManager {
+    private static final String TAG = "time.TimeManager";
+    private static final boolean DEBUG = false;
+
+    private final Object mLock = new Object();
+    private final ITimeZoneDetectorService mITimeZoneDetectorService;
+
+    @GuardedBy("mLock")
+    private ITimeZoneDetectorListener mTimeZoneDetectorReceiver;
+
+    /**
+     * The registered listeners. The key is the actual listener that was registered, the value is a
+     * wrapper that ensures the listener is executed on the correct Executor.
+     */
+    @GuardedBy("mLock")
+    private ArrayMap<TimeZoneDetectorListener, TimeZoneDetectorListener> mTimeZoneDetectorListeners;
+
+    /** @hide */
+    public TimeManager() throws ServiceNotFoundException {
+        // TimeManager is an API over one or possibly more services. At least until there's an
+        // internal refactoring.
+        mITimeZoneDetectorService = ITimeZoneDetectorService.Stub.asInterface(
+                ServiceManager.getServiceOrThrow(Context.TIME_ZONE_DETECTOR_SERVICE));
+    }
+
+    /**
+     * Returns the calling user's time zone capabilities and configuration.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
+    @NonNull
+    public TimeZoneCapabilitiesAndConfig getTimeZoneCapabilitiesAndConfig() {
+        if (DEBUG) {
+            Log.d(TAG, "getTimeZoneCapabilities called");
+        }
+        try {
+            return mITimeZoneDetectorService.getCapabilitiesAndConfig();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Modifies the time zone detection configuration.
+     *
+     * <p>Configuration settings vary in scope: some may be global (affect all users), others may be
+     * specific to the current user.
+     *
+     * <p>The ability to modify configuration settings can be subject to restrictions. For
+     * example, they may be determined by device hardware, general policy (i.e. only the primary
+     * user can set them), or by a managed device policy. Use {@link
+     * #getTimeZoneCapabilitiesAndConfig()} to obtain information at runtime about the user's
+     * capabilities.
+     *
+     * <p>Attempts to modify configuration settings with capabilities that are {@link
+     * TimeZoneCapabilities#CAPABILITY_NOT_SUPPORTED} or {@link
+     * TimeZoneCapabilities#CAPABILITY_NOT_ALLOWED} will have no effect and a {@code false}
+     * will be returned. Modifying configuration settings with capabilities that are {@link
+     * TimeZoneCapabilities#CAPABILITY_NOT_APPLICABLE} or {@link
+     * TimeZoneCapabilities#CAPABILITY_POSSESSED} will succeed. See {@link
+     * TimeZoneCapabilities} for further details.
+     *
+     * <p>If the supplied configuration only has some values set, then only the specified settings
+     * will be updated (where the user's capabilities allow) and other settings will be left
+     * unchanged.
+     *
+     * @return {@code true} if all the configuration settings specified have been set to the
+     *   new values, {@code false} if none have
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
+    public boolean updateTimeZoneConfiguration(@NonNull TimeZoneConfiguration configuration) {
+        if (DEBUG) {
+            Log.d(TAG, "updateConfiguration called: " + configuration);
+        }
+        try {
+            return mITimeZoneDetectorService.updateConfiguration(configuration);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * An interface that can be used to listen for changes to the time zone detector behavior.
+     */
+    @FunctionalInterface
+    public interface TimeZoneDetectorListener {
+        /**
+         * Called when something about the time zone detector behavior on the device has changed.
+         * For example, this could be because the current user has switched, one of the global or
+         * user's settings been changed, or something that could affect a user's capabilities with
+         * respect to the time zone detector has changed. Because different users can have different
+         * configuration and capabilities, this method may be called when nothing has changed for
+         * the receiving user.
+         */
+        void onChange();
+    }
+
+    /**
+     * Registers a listener that will be informed when something about the time zone detector
+     * behavior changes.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
+    public void addTimeZoneDetectorListener(@NonNull Executor executor,
+            @NonNull TimeZoneDetectorListener listener) {
+
+        if (DEBUG) {
+            Log.d(TAG, "addTimeZoneDetectorListener called: " + listener);
+        }
+        synchronized (mLock) {
+            if (mTimeZoneDetectorListeners == null) {
+                mTimeZoneDetectorListeners = new ArrayMap<>();
+            } else if (mTimeZoneDetectorListeners.containsKey(listener)) {
+                return;
+            }
+
+            if (mTimeZoneDetectorReceiver == null) {
+                ITimeZoneDetectorListener iListener = new ITimeZoneDetectorListener.Stub() {
+                    @Override
+                    public void onChange() {
+                        notifyTimeZoneDetectorListeners();
+                    }
+                };
+                mTimeZoneDetectorReceiver = iListener;
+                try {
+                    mITimeZoneDetectorService.addListener(mTimeZoneDetectorReceiver);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+            mTimeZoneDetectorListeners.put(listener, () -> executor.execute(listener::onChange));
+        }
+    }
+
+    private void notifyTimeZoneDetectorListeners() {
+        ArrayMap<TimeZoneDetectorListener, TimeZoneDetectorListener> timeZoneDetectorListeners;
+        synchronized (mLock) {
+            if (mTimeZoneDetectorListeners == null || mTimeZoneDetectorListeners.isEmpty()) {
+                return;
+            }
+            timeZoneDetectorListeners = new ArrayMap<>(mTimeZoneDetectorListeners);
+        }
+        int size = timeZoneDetectorListeners.size();
+        for (int i = 0; i < size; i++) {
+            timeZoneDetectorListeners.valueAt(i).onChange();
+        }
+    }
+
+    /**
+     * Removes a listener previously passed to
+     * {@link #addTimeZoneDetectorListener(Executor, TimeZoneDetectorListener)}
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
+    public void removeTimeZoneDetectorListener(@NonNull TimeZoneDetectorListener listener) {
+        if (DEBUG) {
+            Log.d(TAG, "removeConfigurationListener called: " + listener);
+        }
+
+        synchronized (mLock) {
+            if (mTimeZoneDetectorListeners == null || mTimeZoneDetectorListeners.isEmpty()) {
+                return;
+            }
+            mTimeZoneDetectorListeners.remove(listener);
+
+            // If the last local listener has been removed, remove and discard the
+            // mTimeZoneDetectorReceiver.
+            if (mTimeZoneDetectorListeners.isEmpty()) {
+                try {
+                    mITimeZoneDetectorService.removeListener(mTimeZoneDetectorReceiver);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                } finally {
+                    mTimeZoneDetectorReceiver = null;
+                }
+            }
+        }
+    }
+}
diff --git a/core/java/android/app/timezonedetector/TimeZoneCapabilities.aidl b/core/java/android/app/time/TimeZoneCapabilities.aidl
similarity index 94%
rename from core/java/android/app/timezonedetector/TimeZoneCapabilities.aidl
rename to core/java/android/app/time/TimeZoneCapabilities.aidl
index fede645..f744bf1 100644
--- a/core/java/android/app/timezonedetector/TimeZoneCapabilities.aidl
+++ b/core/java/android/app/time/TimeZoneCapabilities.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.app.timezonedetector;
+package android.app.time;
 
 parcelable TimeZoneCapabilities;
diff --git a/core/java/android/app/time/TimeZoneCapabilities.java b/core/java/android/app/time/TimeZoneCapabilities.java
new file mode 100644
index 0000000..c62c2b3
--- /dev/null
+++ b/core/java/android/app/time/TimeZoneCapabilities.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.time;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.timezonedetector.ManualTimeZoneSuggestion;
+import android.app.timezonedetector.TimeZoneDetector;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Time zone-related capabilities for a user. A capability is the ability for the user to configure
+ * something or perform an action. This information is exposed so that system apps like SettingsUI
+ * can be dynamic, rather than hard-coding knowledge of when configuration or actions are applicable
+ * / available to the user.
+ *
+ * <p>Capabilities have states that users cannot change directly. They may influence some
+ * capabilities indirectly by agreeing to certain device-wide behaviors such as location sharing, or
+ * by changing the configuration. See the {@code CAPABILITY_} constants for details.
+ *
+ * <p>Actions have associated methods, see the documentation for each action for details.
+ *
+ * <p>For configuration settings capabilities, the associated settings value can be found via
+ * {@link TimeManager#getTimeZoneCapabilitiesAndConfig()} and may be changed using {@link
+ * TimeManager#updateTimeZoneConfiguration(TimeZoneConfiguration)} (if the user's capabilities
+ * allow).
+ *
+ * <p>Note: Capabilities are independent of app permissions required to call the associated APIs.
+ *
+ * @hide
+ */
+// @SystemApi
+public final class TimeZoneCapabilities implements Parcelable {
+
+    /** @hide */
+    @IntDef({ CAPABILITY_NOT_SUPPORTED, CAPABILITY_NOT_ALLOWED, CAPABILITY_NOT_APPLICABLE,
+            CAPABILITY_POSSESSED })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CapabilityState {}
+
+    /**
+     * Indicates that a capability is not supported on this device, e.g. because of form factor or
+     * hardware. The associated UI should usually not be shown to the user.
+     */
+    public static final int CAPABILITY_NOT_SUPPORTED = 10;
+
+    /**
+     * Indicates that a capability is supported on this device, but not allowed for the user, e.g.
+     * if the capability relates to the ability to modify settings the user is not able to.
+     * This could be because of the user's type (e.g. maybe it applies to the primary user only) or
+     * device policy. Depending on the capability, this could mean the associated UI
+     * should be hidden, or displayed but disabled.
+     */
+    public static final int CAPABILITY_NOT_ALLOWED = 20;
+
+    /**
+     * Indicates that a capability is possessed but not currently applicable, e.g. if the
+     * capability relates to the ability to modify settings, the user has the ability to modify
+     * it, but it is currently rendered irrelevant by other settings or other device state (flags,
+     * resource config, etc.). The associated UI may be hidden, disabled, or left visible (but
+     * ineffective) depending on requirements.
+     */
+    public static final int CAPABILITY_NOT_APPLICABLE = 30;
+
+    /** Indicates that a capability is possessed by the user. */
+    public static final int CAPABILITY_POSSESSED = 40;
+
+    public static final @NonNull Creator<TimeZoneCapabilities> CREATOR =
+            new Creator<TimeZoneCapabilities>() {
+                public TimeZoneCapabilities createFromParcel(Parcel in) {
+                    return TimeZoneCapabilities.createFromParcel(in);
+                }
+
+                public TimeZoneCapabilities[] newArray(int size) {
+                    return new TimeZoneCapabilities[size];
+                }
+            };
+
+    /**
+     * The user the capabilities are for. This is used for object equality and debugging but there
+     * is no accessor.
+     */
+    @NonNull private final UserHandle mUserHandle;
+    private final @CapabilityState int mConfigureAutoDetectionEnabledCapability;
+    private final @CapabilityState int mConfigureGeoDetectionEnabledCapability;
+    private final @CapabilityState int mSuggestManualTimeZoneCapability;
+
+    private TimeZoneCapabilities(@NonNull Builder builder) {
+        this.mUserHandle = Objects.requireNonNull(builder.mUserHandle);
+        this.mConfigureAutoDetectionEnabledCapability =
+                builder.mConfigureAutoDetectionEnabledCapability;
+        this.mConfigureGeoDetectionEnabledCapability =
+                builder.mConfigureGeoDetectionEnabledCapability;
+        this.mSuggestManualTimeZoneCapability = builder.mSuggestManualTimeZoneCapability;
+    }
+
+    @NonNull
+    private static TimeZoneCapabilities createFromParcel(Parcel in) {
+        UserHandle userHandle = UserHandle.readFromParcel(in);
+        return new TimeZoneCapabilities.Builder(userHandle)
+                .setConfigureAutoDetectionEnabledCapability(in.readInt())
+                .setConfigureGeoDetectionEnabledCapability(in.readInt())
+                .setSuggestManualTimeZoneCapability(in.readInt())
+                .build();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        UserHandle.writeToParcel(mUserHandle, dest);
+        dest.writeInt(mConfigureAutoDetectionEnabledCapability);
+        dest.writeInt(mConfigureGeoDetectionEnabledCapability);
+        dest.writeInt(mSuggestManualTimeZoneCapability);
+    }
+
+    /**
+     * Returns the capability state associated with the user's ability to modify the automatic time
+     * zone detection setting. The setting can be updated via {@link
+     * TimeManager#updateTimeZoneConfiguration(TimeZoneConfiguration)}.
+     */
+    @CapabilityState
+    public int getConfigureAutoDetectionEnabledCapability() {
+        return mConfigureAutoDetectionEnabledCapability;
+    }
+
+    /**
+     * Returns the capability state associated with the user's ability to modify the geolocation
+     * detection setting. The setting can be updated via {@link
+     * TimeManager#updateTimeZoneConfiguration(TimeZoneConfiguration)}.
+     */
+    @CapabilityState
+    public int getConfigureGeoDetectionEnabledCapability() {
+        return mConfigureGeoDetectionEnabledCapability;
+    }
+
+    /**
+     * Returns the capability state associated with the user's ability to manually set the time zone
+     * on a device via {@link TimeZoneDetector#suggestManualTimeZone(ManualTimeZoneSuggestion)}.
+     *
+     * <p>The suggestion will be ignored in all cases unless the value is {@link
+     * #CAPABILITY_POSSESSED}. See also {@link TimeZoneConfiguration#isAutoDetectionEnabled()}.
+     *
+     * @hide
+     */
+    @CapabilityState
+    public int getSuggestManualTimeZoneCapability() {
+        return mSuggestManualTimeZoneCapability;
+    }
+
+    /**
+     * Tries to create a new {@link TimeZoneConfiguration} from the {@code config} and the set of
+     * {@code requestedChanges}, if {@code this} capabilities allow. The new configuration is
+     * returned. If the capabilities do not permit one or more of the requested changes then {@code
+     * null} is returned.
+     *
+     * @hide
+     */
+    @Nullable
+    public TimeZoneConfiguration tryApplyConfigChanges(
+            @NonNull TimeZoneConfiguration config,
+            @NonNull TimeZoneConfiguration requestedChanges) {
+        TimeZoneConfiguration.Builder newConfigBuilder =
+                new TimeZoneConfiguration.Builder(config);
+        if (requestedChanges.hasIsAutoDetectionEnabled()) {
+            if (this.getConfigureAutoDetectionEnabledCapability() < CAPABILITY_NOT_APPLICABLE) {
+                return null;
+            }
+            newConfigBuilder.setAutoDetectionEnabled(requestedChanges.isAutoDetectionEnabled());
+        }
+
+        if (requestedChanges.hasIsGeoDetectionEnabled()) {
+            if (this.getConfigureGeoDetectionEnabledCapability() < CAPABILITY_NOT_APPLICABLE) {
+                return null;
+            }
+            newConfigBuilder.setGeoDetectionEnabled(requestedChanges.isGeoDetectionEnabled());
+        }
+
+        return newConfigBuilder.build();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        TimeZoneCapabilities that = (TimeZoneCapabilities) o;
+        return mUserHandle.equals(that.mUserHandle)
+                && mConfigureAutoDetectionEnabledCapability
+                == that.mConfigureAutoDetectionEnabledCapability
+                && mConfigureGeoDetectionEnabledCapability
+                == that.mConfigureGeoDetectionEnabledCapability
+                && mSuggestManualTimeZoneCapability == that.mSuggestManualTimeZoneCapability;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mUserHandle, mConfigureAutoDetectionEnabledCapability,
+                mConfigureGeoDetectionEnabledCapability, mSuggestManualTimeZoneCapability);
+    }
+
+    @Override
+    public String toString() {
+        return "TimeZoneDetectorCapabilities{"
+                + "mUserHandle=" + mUserHandle
+                + ", mConfigureAutoDetectionEnabledCapability="
+                + mConfigureAutoDetectionEnabledCapability
+                + ", mConfigureGeoDetectionEnabledCapability="
+                + mConfigureGeoDetectionEnabledCapability
+                + ", mSuggestManualTimeZoneCapability=" + mSuggestManualTimeZoneCapability
+                + '}';
+    }
+
+    /** @hide */
+    public static class Builder {
+
+        @NonNull private UserHandle mUserHandle;
+        private @CapabilityState int mConfigureAutoDetectionEnabledCapability;
+        private @CapabilityState int mConfigureGeoDetectionEnabledCapability;
+        private @CapabilityState int mSuggestManualTimeZoneCapability;
+
+        public Builder(@NonNull UserHandle userHandle) {
+            mUserHandle = Objects.requireNonNull(userHandle);
+        }
+
+        /** Sets the state for the automatic time zone detection enabled config. */
+        public Builder setConfigureAutoDetectionEnabledCapability(@CapabilityState int value) {
+            this.mConfigureAutoDetectionEnabledCapability = value;
+            return this;
+        }
+
+        /** Sets the state for the geolocation time zone detection enabled config. */
+        public Builder setConfigureGeoDetectionEnabledCapability(@CapabilityState int value) {
+            this.mConfigureGeoDetectionEnabledCapability = value;
+            return this;
+        }
+
+        /** Sets the state for the suggestManualTimeZone action. */
+        public Builder setSuggestManualTimeZoneCapability(@CapabilityState int value) {
+            this.mSuggestManualTimeZoneCapability = value;
+            return this;
+        }
+
+        /** Returns the {@link TimeZoneCapabilities}. */
+        @NonNull
+        public TimeZoneCapabilities build() {
+            verifyCapabilitySet(mConfigureAutoDetectionEnabledCapability,
+                    "configureAutoDetectionEnabledCapability");
+            verifyCapabilitySet(mConfigureGeoDetectionEnabledCapability,
+                    "configureGeoDetectionEnabledCapability");
+            verifyCapabilitySet(mSuggestManualTimeZoneCapability,
+                    "suggestManualTimeZoneCapability");
+            return new TimeZoneCapabilities(this);
+        }
+
+        private void verifyCapabilitySet(int value, String name) {
+            if (value == 0) {
+                throw new IllegalStateException(name + " not set");
+            }
+        }
+    }
+}
diff --git a/core/java/android/app/timezonedetector/TimeZoneCapabilities.aidl b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.aidl
similarity index 89%
copy from core/java/android/app/timezonedetector/TimeZoneCapabilities.aidl
copy to core/java/android/app/time/TimeZoneCapabilitiesAndConfig.aidl
index fede645..d7b6b58 100644
--- a/core/java/android/app/timezonedetector/TimeZoneCapabilities.aidl
+++ b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.app.timezonedetector;
+package android.app.time;
 
-parcelable TimeZoneCapabilities;
+parcelable TimeZoneCapabilitiesAndConfig;
diff --git a/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
new file mode 100644
index 0000000..6a04f3f
--- /dev/null
+++ b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.time;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * A pair containing a user's {@link TimeZoneCapabilities} and {@link TimeZoneConfiguration}.
+ *
+ * @hide
+ */
+// @SystemApi
+public final class TimeZoneCapabilitiesAndConfig implements Parcelable {
+
+    public static final @NonNull Creator<TimeZoneCapabilitiesAndConfig> CREATOR =
+            new Creator<TimeZoneCapabilitiesAndConfig>() {
+                public TimeZoneCapabilitiesAndConfig createFromParcel(Parcel in) {
+                    return TimeZoneCapabilitiesAndConfig.createFromParcel(in);
+                }
+
+                public TimeZoneCapabilitiesAndConfig[] newArray(int size) {
+                    return new TimeZoneCapabilitiesAndConfig[size];
+                }
+            };
+
+
+    @NonNull private final TimeZoneCapabilities mCapabilities;
+    @NonNull private final TimeZoneConfiguration mConfiguration;
+
+    /**
+     * Creates a new instance.
+     *
+     * @hide
+     */
+    public TimeZoneCapabilitiesAndConfig(
+            @NonNull TimeZoneCapabilities capabilities,
+            @NonNull TimeZoneConfiguration configuration) {
+        this.mCapabilities = Objects.requireNonNull(capabilities);
+        this.mConfiguration = Objects.requireNonNull(configuration);
+    }
+
+    @NonNull
+    private static TimeZoneCapabilitiesAndConfig createFromParcel(Parcel in) {
+        TimeZoneCapabilities capabilities = in.readParcelable(null);
+        TimeZoneConfiguration configuration = in.readParcelable(null);
+        return new TimeZoneCapabilitiesAndConfig(capabilities, configuration);
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeParcelable(mCapabilities, flags);
+        dest.writeParcelable(mConfiguration, flags);
+    }
+
+    /**
+     * Returns the user's time zone behavior capabilities.
+     */
+    @NonNull
+    public TimeZoneCapabilities getCapabilities() {
+        return mCapabilities;
+    }
+
+    /**
+     * Returns the user's time zone behavior configuration.
+     */
+    @NonNull
+    public TimeZoneConfiguration getConfiguration() {
+        return mConfiguration;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        TimeZoneCapabilitiesAndConfig that = (TimeZoneCapabilitiesAndConfig) o;
+        return mCapabilities.equals(that.mCapabilities)
+                && mConfiguration.equals(that.mConfiguration);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mCapabilities, mConfiguration);
+    }
+
+    @Override
+    public String toString() {
+        return "TimeZoneDetectorCapabilitiesAndConfig{"
+                + "mCapabilities=" + mCapabilities
+                + ", mConfiguration=" + mConfiguration
+                + '}';
+    }
+}
diff --git a/core/java/android/app/timezonedetector/TimeZoneConfiguration.aidl b/core/java/android/app/time/TimeZoneConfiguration.aidl
similarity index 94%
rename from core/java/android/app/timezonedetector/TimeZoneConfiguration.aidl
rename to core/java/android/app/time/TimeZoneConfiguration.aidl
index 62240ba..8e85929 100644
--- a/core/java/android/app/timezonedetector/TimeZoneConfiguration.aidl
+++ b/core/java/android/app/time/TimeZoneConfiguration.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.app.timezonedetector;
+package android.app.time;
 
 parcelable TimeZoneConfiguration;
diff --git a/core/java/android/app/timezonedetector/TimeZoneConfiguration.java b/core/java/android/app/time/TimeZoneConfiguration.java
similarity index 65%
rename from core/java/android/app/timezonedetector/TimeZoneConfiguration.java
rename to core/java/android/app/time/TimeZoneConfiguration.java
index e879091..488818a 100644
--- a/core/java/android/app/timezonedetector/TimeZoneConfiguration.java
+++ b/core/java/android/app/time/TimeZoneConfiguration.java
@@ -14,11 +14,10 @@
  * limitations under the License.
  */
 
-package android.app.timezonedetector;
+package android.app.time;
 
 import android.annotation.NonNull;
 import android.annotation.StringDef;
-import android.annotation.UserIdInt;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -36,15 +35,13 @@
  * several settings, the device behavior may not be directly affected by the setting value.
  *
  * <p>Settings can be left absent when updating configuration via {@link
- * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and those settings will not be
+ * TimeManager#updateTimeZoneConfiguration(TimeZoneConfiguration)} and those settings will not be
  * changed. Not all configuration settings can be modified by all users: see {@link
- * TimeZoneDetector#getCapabilities()} and {@link TimeZoneCapabilities} for details.
- *
- * <p>See {@link #hasSetting(String)} with {@code PROPERTY_} constants for testing for the presence
- * of individual settings.
+ * TimeManager#getTimeZoneCapabilitiesAndConfig()} and {@link TimeZoneCapabilities} for details.
  *
  * @hide
  */
+// @SystemApi
 public final class TimeZoneConfiguration implements Parcelable {
 
     public static final @NonNull Creator<TimeZoneConfiguration> CREATOR =
@@ -58,53 +55,48 @@
                 }
             };
 
-    /** All configuration properties */
+    /**
+     * All configuration properties
+     *
+     * @hide
+     */
     @StringDef({ SETTING_AUTO_DETECTION_ENABLED, SETTING_GEO_DETECTION_ENABLED })
     @Retention(RetentionPolicy.SOURCE)
     @interface Setting {}
 
     /** See {@link TimeZoneConfiguration#isAutoDetectionEnabled()} for details. */
     @Setting
-    public static final String SETTING_AUTO_DETECTION_ENABLED = "autoDetectionEnabled";
+    private static final String SETTING_AUTO_DETECTION_ENABLED = "autoDetectionEnabled";
 
     /** See {@link TimeZoneConfiguration#isGeoDetectionEnabled()} for details. */
     @Setting
-    public static final String SETTING_GEO_DETECTION_ENABLED = "geoDetectionEnabled";
+    private static final String SETTING_GEO_DETECTION_ENABLED = "geoDetectionEnabled";
 
-    private final @UserIdInt int mUserId;
     @NonNull private final Bundle mBundle;
 
     private TimeZoneConfiguration(Builder builder) {
-        this.mUserId = builder.mUserId;
         this.mBundle = Objects.requireNonNull(builder.mBundle);
     }
 
     private static TimeZoneConfiguration createFromParcel(Parcel in) {
-        return new TimeZoneConfiguration.Builder(in.readInt())
+        return new TimeZoneConfiguration.Builder()
                 .setPropertyBundleInternal(in.readBundle())
                 .build();
     }
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(mUserId);
         dest.writeBundle(mBundle);
     }
 
-    /** Returns the ID of the user this configuration is associated with. */
-    public @UserIdInt int getUserId() {
-        return mUserId;
-    }
-
-    /** Returns {@code true} if all known settings are present. */
+    /**
+     * Returns {@code true} if all known settings are present.
+     *
+     * @hide
+     */
     public boolean isComplete() {
-        return hasSetting(SETTING_AUTO_DETECTION_ENABLED)
-                && hasSetting(SETTING_GEO_DETECTION_ENABLED);
-    }
-
-    /** Returns true if the specified setting is set. */
-    public boolean hasSetting(@Setting String setting) {
-        return mBundle.containsKey(setting);
+        return hasIsAutoDetectionEnabled()
+                && hasIsGeoDetectionEnabled();
     }
 
     /**
@@ -112,9 +104,10 @@
      * controls whether a device will attempt to determine the time zone automatically using
      * contextual information if the device supports auto detection.
      *
-     * <p>This setting is global and can be updated by some users.
+     * <p>See {@link TimeZoneCapabilities#getConfigureAutoDetectionEnabledCapability()} for how to
+     * tell if the setting is meaningful for the current user at this time.
      *
-     * @throws IllegalStateException if the setting has not been set
+     * @throws IllegalStateException if the setting is not present
      */
     public boolean isAutoDetectionEnabled() {
         enforceSettingPresent(SETTING_AUTO_DETECTION_ENABLED);
@@ -122,21 +115,39 @@
     }
 
     /**
+     * Returns {@code true} if the {@link #isAutoDetectionEnabled()} setting is present.
+     *
+     * @hide
+     */
+    public boolean hasIsAutoDetectionEnabled() {
+        return mBundle.containsKey(SETTING_AUTO_DETECTION_ENABLED);
+    }
+
+    /**
      * Returns the value of the {@link #SETTING_GEO_DETECTION_ENABLED} setting. This
-     * controls whether a device can use geolocation to determine time zone. Only used when
-     * {@link #isAutoDetectionEnabled()} is {@code true} and when the user has allowed their
-     * location to be used.
+     * controls whether the device can use geolocation to determine time zone. This value may only
+     * be used by Android under some circumstances. For example, it is not used when
+     * {@link #isGeoDetectionEnabled()} is {@code false}.
      *
-     * <p>This setting is user-scoped and can be updated by some users.
-     * See {@link TimeZoneCapabilities#getConfigureGeoDetectionEnabled()}.
+     * <p>See {@link TimeZoneCapabilities#getConfigureGeoDetectionEnabledCapability()} for how to
+     * tell if the setting is meaningful for the current user at this time.
      *
-     * @throws IllegalStateException if the setting has not been set
+     * @throws IllegalStateException if the setting is not present
      */
     public boolean isGeoDetectionEnabled() {
         enforceSettingPresent(SETTING_GEO_DETECTION_ENABLED);
         return mBundle.getBoolean(SETTING_GEO_DETECTION_ENABLED);
     }
 
+    /**
+     * Returns {@code true} if the {@link #isGeoDetectionEnabled()} setting is present.
+     *
+     * @hide
+     */
+    public boolean hasIsGeoDetectionEnabled() {
+        return mBundle.containsKey(SETTING_GEO_DETECTION_ENABLED);
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -151,20 +162,18 @@
             return false;
         }
         TimeZoneConfiguration that = (TimeZoneConfiguration) o;
-        return mUserId == that.mUserId
-                && mBundle.kindofEquals(that.mBundle);
+        return mBundle.kindofEquals(that.mBundle);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mUserId, mBundle);
+        return Objects.hash(mBundle);
     }
 
     @Override
     public String toString() {
         return "TimeZoneConfiguration{"
-                + "mUserId=" + mUserId
-                + ", mBundle=" + mBundle
+                + "mBundle=" + mBundle
                 + '}';
     }
 
@@ -174,43 +183,43 @@
         }
     }
 
-    /** @hide */
-    public static class Builder {
+    /**
+     * A builder for {@link TimeZoneConfiguration} objects.
+     *
+     * @hide
+     */
+    // @SystemApi
+    public static final class Builder {
 
-        private final @UserIdInt int mUserId;
         private final Bundle mBundle = new Bundle();
 
         /**
-         * Creates a new Builder for a userId with no settings held.
+         * Creates a new Builder with no settings held.
          */
-        public Builder(@UserIdInt int userId) {
-            mUserId = userId;
+        public Builder() {
         }
 
         /**
-         * Creates a new Builder by copying the user ID and settings from an existing instance.
+         * Creates a new Builder by copying the settings from an existing instance.
          */
-        public Builder(TimeZoneConfiguration toCopy) {
-            this.mUserId = toCopy.mUserId;
+        public Builder(@NonNull TimeZoneConfiguration toCopy) {
             mergeProperties(toCopy);
         }
 
         /**
          * Merges {@code other} settings into this instances, replacing existing values in this
          * where the settings appear in both.
+         *
+         * @hide
          */
-        public Builder mergeProperties(TimeZoneConfiguration other) {
-            if (mUserId != other.mUserId) {
-                throw new IllegalArgumentException(
-                        "Cannot merge configurations for different user IDs."
-                                + " this.mUserId=" + this.mUserId
-                                + ", other.mUserId=" + other.mUserId);
-            }
+        @NonNull
+        public Builder mergeProperties(@NonNull TimeZoneConfiguration other) {
             this.mBundle.putAll(other.mBundle);
             return this;
         }
 
-        Builder setPropertyBundleInternal(Bundle bundle) {
+        @NonNull
+        Builder setPropertyBundleInternal(@NonNull Bundle bundle) {
             this.mBundle.putAll(bundle);
             return this;
         }
@@ -218,6 +227,7 @@
         /**
          * Sets the state of the {@link #SETTING_AUTO_DETECTION_ENABLED} setting.
          */
+        @NonNull
         public Builder setAutoDetectionEnabled(boolean enabled) {
             this.mBundle.putBoolean(SETTING_AUTO_DETECTION_ENABLED, enabled);
             return this;
@@ -226,6 +236,7 @@
         /**
          * Sets the state of the {@link #SETTING_GEO_DETECTION_ENABLED} setting.
          */
+        @NonNull
         public Builder setGeoDetectionEnabled(boolean enabled) {
             this.mBundle.putBoolean(SETTING_GEO_DETECTION_ENABLED, enabled);
             return this;
diff --git a/core/java/android/app/timezonedetector/ITimeZoneConfigurationListener.aidl b/core/java/android/app/timezonedetector/ITimeZoneConfigurationListener.aidl
deleted file mode 100644
index 6d0fe72..0000000
--- a/core/java/android/app/timezonedetector/ITimeZoneConfigurationListener.aidl
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.timezonedetector;
-
-import android.app.timezonedetector.TimeZoneConfiguration;
-
-/** {@hide} */
-oneway interface ITimeZoneConfigurationListener {
-    void onChange();
-}
\ No newline at end of file
diff --git a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
index 4f7e1f6..af0389a 100644
--- a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
+++ b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
@@ -16,11 +16,11 @@
 
 package android.app.timezonedetector;
 
-import android.app.timezonedetector.ITimeZoneConfigurationListener;
+import android.app.time.ITimeZoneDetectorListener;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.app.timezonedetector.TimeZoneCapabilities;
-import android.app.timezonedetector.TimeZoneConfiguration;
 
 /**
  * System private API to communicate with time zone detector service.
@@ -35,9 +35,9 @@
  * {@hide}
  */
 interface ITimeZoneDetectorService {
-  TimeZoneCapabilities getCapabilities();
-  void addConfigurationListener(ITimeZoneConfigurationListener listener);
-  void removeConfigurationListener(ITimeZoneConfigurationListener listener);
+  TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig();
+  void addListener(ITimeZoneDetectorListener listener);
+  void removeListener(ITimeZoneDetectorListener listener);
 
   boolean updateConfiguration(in TimeZoneConfiguration configuration);
 
diff --git a/core/java/android/app/timezonedetector/TimeZoneCapabilities.java b/core/java/android/app/timezonedetector/TimeZoneCapabilities.java
deleted file mode 100644
index 09fffe9..0000000
--- a/core/java/android/app/timezonedetector/TimeZoneCapabilities.java
+++ /dev/null
@@ -1,289 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.timezonedetector;
-
-import static android.app.timezonedetector.TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED;
-import static android.app.timezonedetector.TimeZoneConfiguration.SETTING_GEO_DETECTION_ENABLED;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Objects;
-
-/**
- * Time zone-related capabilities for a user. A capability is the ability for the user to configure
- * something or perform an action. This information is exposed so that system apps like SettingsUI
- * can be dynamic, rather than hard-coding knowledge of when configuration or actions are applicable
- * / available to the user.
- *
- * <p>Capabilities have states that users cannot change directly. They may influence some
- * capabilities indirectly by agreeing to certain device-wide behaviors such as location sharing, or
- * by changing the configuration. See the {@code CAPABILITY_} constants for details.
- *
- * <p>Actions have associated methods, see the documentation for each action for details.
- *
- * <p>For configuration settings capabilities, the associated settings value can be found via
- * {@link #getConfiguration()} and may be changed using {@link
- * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} (if the user's capabilities allow).
- *
- * <p>Note: Capabilities are independent of app permissions required to call the associated APIs.
- *
- * @hide
- */
-public final class TimeZoneCapabilities implements Parcelable {
-
-    @IntDef({ CAPABILITY_NOT_SUPPORTED, CAPABILITY_NOT_ALLOWED, CAPABILITY_NOT_APPLICABLE,
-            CAPABILITY_POSSESSED })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface CapabilityState {}
-
-    /**
-     * Indicates that a capability is not supported on this device, e.g. because of form factor or
-     * hardware. The associated UI should usually not be shown to the user.
-     */
-    public static final int CAPABILITY_NOT_SUPPORTED = 10;
-
-    /**
-     * Indicates that a capability is supported on this device, but not allowed for the user, e.g.
-     * if the capability relates to the ability to modify settings the user is not able to.
-     * This could be because of the user's type (e.g. maybe it applies to the primary user only) or
-     * device policy. Depending on the capability, this could mean the associated UI
-     * should be hidden, or displayed but disabled.
-     */
-    public static final int CAPABILITY_NOT_ALLOWED = 20;
-
-    /**
-     * Indicates that a capability is possessed but not currently applicable, e.g. if the
-     * capability relates to the ability to modify settings, the user has the ability to modify
-     * it, but it is currently rendered irrelevant by other settings or other device state (flags,
-     * resource config, etc.). The associated UI may be hidden, disabled, or left visible (but
-     * ineffective) depending on requirements.
-     */
-    public static final int CAPABILITY_NOT_APPLICABLE = 30;
-
-    /** Indicates that a capability is possessed by the user. */
-    public static final int CAPABILITY_POSSESSED = 40;
-
-    public static final @NonNull Creator<TimeZoneCapabilities> CREATOR =
-            new Creator<TimeZoneCapabilities>() {
-                public TimeZoneCapabilities createFromParcel(Parcel in) {
-                    return TimeZoneCapabilities.createFromParcel(in);
-                }
-
-                public TimeZoneCapabilities[] newArray(int size) {
-                    return new TimeZoneCapabilities[size];
-                }
-            };
-
-
-    @NonNull private final TimeZoneConfiguration mConfiguration;
-    private final @CapabilityState int mConfigureAutoDetectionEnabled;
-    private final @CapabilityState int mConfigureGeoDetectionEnabled;
-    private final @CapabilityState int mSuggestManualTimeZone;
-
-    private TimeZoneCapabilities(@NonNull Builder builder) {
-        this.mConfiguration = Objects.requireNonNull(builder.mConfiguration);
-        this.mConfigureAutoDetectionEnabled = builder.mConfigureAutoDetectionEnabled;
-        this.mConfigureGeoDetectionEnabled = builder.mConfigureGeoDetectionEnabled;
-        this.mSuggestManualTimeZone = builder.mSuggestManualTimeZone;
-    }
-
-    @NonNull
-    private static TimeZoneCapabilities createFromParcel(Parcel in) {
-        return new TimeZoneCapabilities.Builder()
-                .setConfiguration(in.readParcelable(null))
-                .setConfigureAutoDetectionEnabled(in.readInt())
-                .setConfigureGeoDetectionEnabled(in.readInt())
-                .setSuggestManualTimeZone(in.readInt())
-                .build();
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeParcelable(mConfiguration, flags);
-        dest.writeInt(mConfigureAutoDetectionEnabled);
-        dest.writeInt(mConfigureGeoDetectionEnabled);
-        dest.writeInt(mSuggestManualTimeZone);
-    }
-
-    /**
-     * Returns the user's time zone behavior configuration.
-     */
-    public @NonNull TimeZoneConfiguration getConfiguration() {
-        return mConfiguration;
-    }
-
-    /**
-     * Returns the capability state associated with the user's ability to modify the automatic time
-     * zone detection setting. The setting can be updated via {@link
-     * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and accessed via {@link
-     * #getConfiguration()}.
-     */
-    @CapabilityState
-    public int getConfigureAutoDetectionEnabled() {
-        return mConfigureAutoDetectionEnabled;
-    }
-
-    /**
-     * Returns the capability state associated with the user's ability to modify the geolocation
-     * detection setting. The setting can be updated via {@link
-     * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and accessed via {@link
-     * #getConfiguration()}.
-     */
-    @CapabilityState
-    public int getConfigureGeoDetectionEnabled() {
-        return mConfigureGeoDetectionEnabled;
-    }
-
-    /**
-     * Returns the capability state associated with the user's ability to manually set the time zone
-     * on a device via {@link TimeZoneDetector#suggestManualTimeZone(ManualTimeZoneSuggestion)}.
-     *
-     * <p>The suggestion will be ignored in all cases unless the value is {@link
-     * #CAPABILITY_POSSESSED}. See also {@link TimeZoneConfiguration#isAutoDetectionEnabled()}.
-     */
-    @CapabilityState
-    public int getSuggestManualTimeZone() {
-        return mSuggestManualTimeZone;
-    }
-
-    /**
-     * Constructs a new {@link TimeZoneConfiguration} from an {@code oldConfiguration} and a set of
-     * {@code requestedChanges}, if the current capabilities allow. The new configuration is
-     * returned and the capabilities are left unchanged. If the capabilities do not permit one or
-     * more of the changes then {@code null} is returned.
-     */
-    @Nullable
-    public TimeZoneConfiguration applyUpdate(TimeZoneConfiguration requestedChanges) {
-        if (requestedChanges.getUserId() != mConfiguration.getUserId()) {
-            throw new IllegalArgumentException("User does not match:"
-                    + " this=" + mConfiguration + ", other=" + requestedChanges);
-        }
-
-        TimeZoneConfiguration.Builder newConfigBuilder =
-                new TimeZoneConfiguration.Builder(mConfiguration);
-        if (requestedChanges.hasSetting(SETTING_AUTO_DETECTION_ENABLED)) {
-            if (getConfigureAutoDetectionEnabled() < CAPABILITY_NOT_APPLICABLE) {
-                return null;
-            }
-            newConfigBuilder.setAutoDetectionEnabled(requestedChanges.isAutoDetectionEnabled());
-        }
-
-        if (requestedChanges.hasSetting(SETTING_GEO_DETECTION_ENABLED)) {
-            if (getConfigureGeoDetectionEnabled() < CAPABILITY_NOT_APPLICABLE) {
-                return null;
-            }
-            newConfigBuilder.setGeoDetectionEnabled(requestedChanges.isGeoDetectionEnabled());
-        }
-
-        return newConfigBuilder.build();
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-        TimeZoneCapabilities that = (TimeZoneCapabilities) o;
-        return Objects.equals(mConfiguration, that.mConfiguration)
-                && mConfigureAutoDetectionEnabled == that.mConfigureAutoDetectionEnabled
-                && mConfigureGeoDetectionEnabled == that.mConfigureGeoDetectionEnabled
-                && mSuggestManualTimeZone == that.mSuggestManualTimeZone;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mConfiguration,
-                mConfigureAutoDetectionEnabled,
-                mConfigureGeoDetectionEnabled,
-                mSuggestManualTimeZone);
-    }
-
-    @Override
-    public String toString() {
-        return "TimeZoneDetectorCapabilities{"
-                + "mConfiguration=" + mConfiguration
-                + ", mConfigureAutomaticDetectionEnabled=" + mConfigureAutoDetectionEnabled
-                + ", mConfigureGeoDetectionEnabled=" + mConfigureGeoDetectionEnabled
-                + ", mSuggestManualTimeZone=" + mSuggestManualTimeZone
-                + '}';
-    }
-
-    /** @hide */
-    public static class Builder {
-
-        private TimeZoneConfiguration mConfiguration;
-        private @CapabilityState int mConfigureAutoDetectionEnabled;
-        private @CapabilityState int mConfigureGeoDetectionEnabled;
-        private @CapabilityState int mSuggestManualTimeZone;
-
-        /** Sets the user-visible configuration settings. */
-        public Builder setConfiguration(@NonNull TimeZoneConfiguration configuration) {
-            if (!configuration.isComplete()) {
-                throw new IllegalArgumentException(configuration + " is not complete");
-            }
-            this.mConfiguration = configuration;
-            return this;
-        }
-
-        /** Sets the state for the automatic time zone detection enabled config. */
-        public Builder setConfigureAutoDetectionEnabled(@CapabilityState int value) {
-            this.mConfigureAutoDetectionEnabled = value;
-            return this;
-        }
-
-        /** Sets the state for the geolocation time zone detection enabled config. */
-        public Builder setConfigureGeoDetectionEnabled(@CapabilityState int value) {
-            this.mConfigureGeoDetectionEnabled = value;
-            return this;
-        }
-
-        /** Sets the state for the suggestManualTimeZone action. */
-        public Builder setSuggestManualTimeZone(@CapabilityState int value) {
-            this.mSuggestManualTimeZone = value;
-            return this;
-        }
-
-        /** Returns the {@link TimeZoneCapabilities}. */
-        @NonNull
-        public TimeZoneCapabilities build() {
-            verifyCapabilitySet(mConfigureAutoDetectionEnabled, "configureAutoDetectionEnabled");
-            verifyCapabilitySet(mConfigureGeoDetectionEnabled, "configureGeoDetectionEnabled");
-            verifyCapabilitySet(mSuggestManualTimeZone, "suggestManualTimeZone");
-            return new TimeZoneCapabilities(this);
-        }
-
-        private void verifyCapabilitySet(int value, String name) {
-            if (value == 0) {
-                throw new IllegalStateException(name + " not set");
-            }
-        }
-    }
-}
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java
index 2b1cbf2..486232d 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetector.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java
@@ -30,70 +30,6 @@
 public interface TimeZoneDetector {
 
     /**
-     * Returns the current user's time zone capabilities. See {@link TimeZoneCapabilities}.
-     */
-    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-    @NonNull
-    TimeZoneCapabilities getCapabilities();
-
-    /**
-     * Modifies the time zone detection configuration.
-     *
-     * <p>Configuration settings vary in scope: some may be global (affect all users), others may be
-     * specific to the current user.
-     *
-     * <p>The ability to modify configuration settings can be subject to restrictions. For
-     * example, they may be determined by device hardware, general policy (i.e. only the primary
-     * user can set them), or by a managed device policy. Use {@link #getCapabilities()} to obtain
-     * information at runtime about the user's capabilities.
-     *
-     * <p>Attempts to modify configuration settings with capabilities that are {@link
-     * TimeZoneCapabilities#CAPABILITY_NOT_SUPPORTED} or {@link
-     * TimeZoneCapabilities#CAPABILITY_NOT_ALLOWED} will have no effect and a {@code false}
-     * will be returned. Modifying configuration settings with capabilities that are {@link
-     * TimeZoneCapabilities#CAPABILITY_NOT_APPLICABLE} or {@link
-     * TimeZoneCapabilities#CAPABILITY_POSSESSED} will succeed. See {@link
-     * TimeZoneCapabilities} for further details.
-     *
-     * <p>If the supplied configuration only has some values set, then only the specified settings
-     * will be updated (where the user's capabilities allow) and other settings will be left
-     * unchanged.
-     *
-     * @return {@code true} if all the configuration settings specified have been set to the
-     *   new values, {@code false} if none have
-     */
-    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-    boolean updateConfiguration(@NonNull TimeZoneConfiguration configuration);
-
-    /**
-     * An interface that can be used to listen for changes to the time zone detector configuration.
-     */
-    @FunctionalInterface
-    interface TimeZoneConfigurationListener {
-        /**
-         * Called when something about the time zone configuration on the device has changed.
-         * This could be because the current user has changed, one of the device's relevant settings
-         * has changed, or something that could affect a user's capabilities has changed.
-         * There are no guarantees about the thread used.
-         */
-        void onChange();
-    }
-
-    /**
-     * Registers a listener that will be informed when something about the time zone configuration
-     * changes.
-     */
-    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-    void addConfigurationListener(@NonNull TimeZoneConfigurationListener listener);
-
-    /**
-     * Removes a listener previously passed to
-     * {@link #addConfigurationListener(ITimeZoneConfigurationListener)}
-     */
-    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-    void removeConfigurationListener(@NonNull TimeZoneConfigurationListener listener);
-
-    /**
      * A shared utility method to create a {@link ManualTimeZoneSuggestion}.
      *
      * @hide
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java b/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java
index 4c69732..3bd6b4b 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java
@@ -21,7 +21,6 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
-import android.util.ArraySet;
 import android.util.Log;
 
 /**
@@ -35,108 +34,12 @@
 
     private final ITimeZoneDetectorService mITimeZoneDetectorService;
 
-    private ITimeZoneConfigurationListener mConfigurationReceiver;
-    private ArraySet<TimeZoneConfigurationListener> mConfigurationListeners;
-
     public TimeZoneDetectorImpl() throws ServiceNotFoundException {
         mITimeZoneDetectorService = ITimeZoneDetectorService.Stub.asInterface(
                 ServiceManager.getServiceOrThrow(Context.TIME_ZONE_DETECTOR_SERVICE));
     }
 
     @Override
-    @NonNull
-    public TimeZoneCapabilities getCapabilities() {
-        if (DEBUG) {
-            Log.d(TAG, "getCapabilities called");
-        }
-        try {
-            return mITimeZoneDetectorService.getCapabilities();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @Override
-    public boolean updateConfiguration(@NonNull TimeZoneConfiguration configuration) {
-        if (DEBUG) {
-            Log.d(TAG, "updateConfiguration called: " + configuration);
-        }
-        try {
-            return mITimeZoneDetectorService.updateConfiguration(configuration);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @Override
-    public void addConfigurationListener(@NonNull TimeZoneConfigurationListener listener) {
-        if (DEBUG) {
-            Log.d(TAG, "addConfigurationListener called: " + listener);
-        }
-        synchronized (this) {
-            if (mConfigurationListeners.contains(listener)) {
-                return;
-            }
-            if (mConfigurationReceiver == null) {
-                ITimeZoneConfigurationListener iListener =
-                        new ITimeZoneConfigurationListener.Stub() {
-                    @Override
-                    public void onChange() {
-                        notifyConfigurationListeners();
-                    }
-                };
-                mConfigurationReceiver = iListener;
-            }
-            if (mConfigurationListeners == null) {
-                mConfigurationListeners = new ArraySet<>();
-            }
-
-            boolean wasEmpty = mConfigurationListeners.isEmpty();
-            mConfigurationListeners.add(listener);
-            if (wasEmpty) {
-                try {
-                    mITimeZoneDetectorService.addConfigurationListener(mConfigurationReceiver);
-                } catch (RemoteException e) {
-                    throw e.rethrowFromSystemServer();
-                }
-            }
-        }
-    }
-
-    private void notifyConfigurationListeners() {
-        final ArraySet<TimeZoneConfigurationListener> configurationListeners;
-        synchronized (this) {
-            configurationListeners = new ArraySet<>(mConfigurationListeners);
-        }
-        int size = configurationListeners.size();
-        for (int i = 0; i < size; i++) {
-            configurationListeners.valueAt(i).onChange();
-        }
-    }
-
-    @Override
-    public void removeConfigurationListener(@NonNull TimeZoneConfigurationListener listener) {
-        if (DEBUG) {
-            Log.d(TAG, "removeConfigurationListener called: " + listener);
-        }
-
-        synchronized (this) {
-            if (mConfigurationListeners == null) {
-                return;
-            }
-            boolean wasEmpty = mConfigurationListeners.isEmpty();
-            mConfigurationListeners.remove(listener);
-            if (mConfigurationListeners.isEmpty() && !wasEmpty) {
-                try {
-                    mITimeZoneDetectorService.removeConfigurationListener(mConfigurationReceiver);
-                } catch (RemoteException e) {
-                    throw e.rethrowFromSystemServer();
-                }
-            }
-        }
-    }
-
-    @Override
     public boolean suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion timeZoneSuggestion) {
         if (DEBUG) {
             Log.d(TAG, "suggestManualTimeZone called: " + timeZoneSuggestion);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 005648f..666ba32 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -40,6 +40,7 @@
 import android.app.IApplicationThread;
 import android.app.IServiceConnection;
 import android.app.VrManager;
+import android.app.time.TimeManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -5090,6 +5091,14 @@
     public static final String TIME_ZONE_DETECTOR_SERVICE = "time_zone_detector";
 
     /**
+     * Use with {@link #getSystemService(String)} to retrieve an {@link TimeManager}.
+     * @hide
+     *
+     * @see #getSystemService(String)
+     */
+    public static final String TIME_MANAGER = "time_manager";
+
+    /**
      * Binder service name for {@link AppBindingService}.
      * @hide
      */
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 79e23b3..a600d6c 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -866,6 +866,12 @@
      * is set the restricted permissions will be whitelisted for all users, otherwise
      * only to the owner.
      *
+     * <p>
+     * <strong>Note: </strong>In retrospect it would have been preferred to use
+     * more inclusive terminology when naming this API. Similar APIs added will
+     * refrain from using the term "whitelist".
+     * </p>
+     *
      * @hide
      */
     public static final int INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS = 0x00400000;
@@ -3505,6 +3511,17 @@
     public static final int FLAG_PERMISSION_AUTO_REVOKED = 1 << 17;
 
     /**
+     * Permission flag: The permission is restricted but the app is exempt
+     * from the restriction and is allowed to hold this permission in its
+     * full form and the exemption is provided by the held roles.
+     *
+     * @hide
+     */
+    @TestApi
+    @SystemApi
+    public static final int FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT =  1 << 18;
+
+    /**
      * Permission flags: Reserved for use by the permission controller. The platform and any
      * packages besides the permission controller should not assume any definition about these
      * flags.
@@ -3522,7 +3539,8 @@
     public static final int FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT =
             FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT
                     | FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT
-                    | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
+                    | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT
+                    | FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT;
 
     /**
      * Mask for all permission flags.
@@ -3568,13 +3586,27 @@
 
     /**
      * Permission whitelist flag: permissions whitelisted by the system.
-     * Permissions can also be whitelisted by the installer or on upgrade.
+     * Permissions can also be whitelisted by the installer, on upgrade, or on
+     * role grant.
+     *
+     * <p>
+     * <strong>Note: </strong>In retrospect it would have been preferred to use
+     * more inclusive terminology when naming this API. Similar APIs added will
+     * refrain from using the term "whitelist".
+     * </p>
      */
     public static final int FLAG_PERMISSION_WHITELIST_SYSTEM = 1 << 0;
 
     /**
      * Permission whitelist flag: permissions whitelisted by the installer.
-     * Permissions can also be whitelisted by the system or on upgrade.
+     * Permissions can also be whitelisted by the system, on upgrade, or on role
+     * grant.
+     *
+     * <p>
+     * <strong>Note: </strong>In retrospect it would have been preferred to use
+     * more inclusive terminology when naming this API. Similar APIs added will
+     * refrain from using the term "whitelist".
+     * </p>
      */
     public static final int FLAG_PERMISSION_WHITELIST_INSTALLER = 1 << 1;
 
@@ -3582,15 +3614,31 @@
      * Permission whitelist flag: permissions whitelisted by the system
      * when upgrading from an OS version where the permission was not
      * restricted to an OS version where the permission is restricted.
-     * Permissions can also be whitelisted by the installer or the system.
+     * Permissions can also be whitelisted by the installer, the system, or on
+     * role grant.
+     *
+     * <p>
+     * <strong>Note: </strong>In retrospect it would have been preferred to use
+     * more inclusive terminology when naming this API. Similar APIs added will
+     * refrain from using the term "whitelist".
+     * </p>
      */
     public static final int FLAG_PERMISSION_WHITELIST_UPGRADE = 1 << 2;
 
+    /**
+     * Permission allowlist flag: permissions exempted by the system
+     * when being granted a role.
+     * Permissions can also be exempted by the installer, the system, or on
+     * upgrade.
+     */
+    public static final int FLAG_PERMISSION_ALLOWLIST_ROLE = 1 << 3;
+
     /** @hide */
     @IntDef(flag = true, prefix = {"FLAG_PERMISSION_WHITELIST_"}, value = {
             FLAG_PERMISSION_WHITELIST_SYSTEM,
             FLAG_PERMISSION_WHITELIST_INSTALLER,
-            FLAG_PERMISSION_WHITELIST_UPGRADE
+            FLAG_PERMISSION_WHITELIST_UPGRADE,
+            FLAG_PERMISSION_ALLOWLIST_ROLE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface PermissionWhitelistFlags {}
@@ -4536,7 +4584,7 @@
      * allows for the to hold that permission and whitelisting a soft restricted
      * permission allows the app to hold the permission in its full, unrestricted form.
      *
-     * <p><ol>There are three whitelists:
+     * <p><ol>There are four allowlists:
      *
      * <li>one for cases where the system permission policy whitelists a permission
      * This list corresponds to the{@link #FLAG_PERMISSION_WHITELIST_SYSTEM} flag.
@@ -4553,6 +4601,17 @@
      * Can be accessed by pre-installed holders of a dedicated permission or the
      * installer on record.
      *
+     * <li>one for cases where the system exempts the permission when granting a role.
+     * This list corresponds to the {@link #FLAG_PERMISSION_ALLOWLIST_ROLE} flag. Can
+     * be accessed by pre-installed holders of a dedicated permission.
+     * </ol>
+     *
+     * <p>
+     * <strong>Note: </strong>In retrospect it would have been preferred to use
+     * more inclusive terminology when naming this API. Similar APIs added will
+     * refrain from using the term "whitelist".
+     * </p>
+     *
      * @param packageName The app for which to get whitelisted permissions.
      * @param whitelistFlag The flag to determine which whitelist to query. Only one flag
      * can be passed.s
@@ -4563,6 +4622,7 @@
      * @see #FLAG_PERMISSION_WHITELIST_SYSTEM
      * @see #FLAG_PERMISSION_WHITELIST_UPGRADE
      * @see #FLAG_PERMISSION_WHITELIST_INSTALLER
+     * @see #FLAG_PERMISSION_ALLOWLIST_ROLE
      *
      * @throws SecurityException if you try to access a whitelist that you have no access to.
      */
@@ -4584,7 +4644,7 @@
      * allows for the to hold that permission and whitelisting a soft restricted
      * permission allows the app to hold the permission in its full, unrestricted form.
      *
-     * <p><ol>There are three whitelists:
+     * <p><ol>There are four whitelists:
      *
      * <li>one for cases where the system permission policy whitelists a permission
      * This list corresponds to the {@link #FLAG_PERMISSION_WHITELIST_SYSTEM} flag.
@@ -4602,10 +4662,21 @@
      * Can be modified by pre-installed holders of a dedicated permission or the installer
      * on record.
      *
+     * <li>one for cases where the system exempts the permission when permission when
+     * granting a role. This list corresponds to the {@link #FLAG_PERMISSION_ALLOWLIST_ROLE}
+     * flag. Can be modified by pre-installed holders of a dedicated permission.
+     * </ol>
+     *
      * <p>You need to specify the whitelists for which to set the whitelisted permissions
      * which will clear the previous whitelisted permissions and replace them with the
      * provided ones.
      *
+     * <p>
+     * <strong>Note: </strong>In retrospect it would have been preferred to use
+     * more inclusive terminology when naming this API. Similar APIs added will
+     * refrain from using the term "whitelist".
+     * </p>
+     *
      * @param packageName The app for which to get whitelisted permissions.
      * @param permName The whitelisted permission to add.
      * @param whitelistFlags The whitelists to which to add. Passing multiple flags
@@ -4617,6 +4688,7 @@
      * @see #FLAG_PERMISSION_WHITELIST_SYSTEM
      * @see #FLAG_PERMISSION_WHITELIST_UPGRADE
      * @see #FLAG_PERMISSION_WHITELIST_INSTALLER
+     * @see #FLAG_PERMISSION_ALLOWLIST_ROLE
      *
      * @throws SecurityException if you try to modify a whitelist that you have no access to.
      */
@@ -4638,7 +4710,7 @@
      * allows for the to hold that permission and whitelisting a soft restricted
      * permission allows the app to hold the permission in its full, unrestricted form.
      *
-     * <p><ol>There are three whitelists:
+     * <p><ol>There are four whitelists:
      *
      * <li>one for cases where the system permission policy whitelists a permission
      * This list corresponds to the {@link #FLAG_PERMISSION_WHITELIST_SYSTEM} flag.
@@ -4656,10 +4728,24 @@
      * Can be modified by pre-installed holders of a dedicated permission or the installer
      * on record.
      *
+     * <li>one for cases where the system exempts the permission when upgrading
+     * from an OS version in which the permission was not restricted to an OS version
+     * in which the permission is restricted. This list corresponds to the {@link
+     * #FLAG_PERMISSION_WHITELIST_UPGRADE} flag. Can be modified by pre-installed
+     * holders of a dedicated permission. The installer on record can only remove
+     * permissions from this allowlist.
+     * </ol>
+     *
      * <p>You need to specify the whitelists for which to set the whitelisted permissions
      * which will clear the previous whitelisted permissions and replace them with the
      * provided ones.
      *
+     * <p>
+     * <strong>Note: </strong>In retrospect it would have been preferred to use
+     * more inclusive terminology when naming this API. Similar APIs added will
+     * refrain from using the term "whitelist".
+     * </p>
+     *
      * @param packageName The app for which to get whitelisted permissions.
      * @param permName The whitelisted permission to remove.
      * @param whitelistFlags The whitelists from which to remove. Passing multiple flags
@@ -4671,6 +4757,7 @@
      * @see #FLAG_PERMISSION_WHITELIST_SYSTEM
      * @see #FLAG_PERMISSION_WHITELIST_UPGRADE
      * @see #FLAG_PERMISSION_WHITELIST_INSTALLER
+     * @see #FLAG_PERMISSION_ALLOWLIST_ROLE
      *
      * @throws SecurityException if you try to modify a whitelist that you have no access to.
      */
@@ -4691,6 +4778,12 @@
      * un-whitelist the packages it installs, unless auto-revoking permissions from that package
      * would cause breakages beyond having to re-request the permission(s).
      *
+     * <p>
+     * <strong>Note: </strong>In retrospect it would have been preferred to use
+     * more inclusive terminology when naming this API. Similar APIs added will
+     * refrain from using the term "whitelist".
+     * </p>
+     *
      * @param packageName The app for which to set exemption.
      * @param whitelisted Whether the app should be whitelisted.
      *
@@ -4712,6 +4805,13 @@
      *
      * Only the installer on record that installed the given package, or a holder of
      * {@code WHITELIST_AUTO_REVOKE_PERMISSIONS} is allowed to call this.
+     *
+     * <p>
+     * <strong>Note: </strong>In retrospect it would have been preferred to use
+     * more inclusive terminology when naming this API. Similar APIs added will
+     * refrain from using the term "whitelist".
+     * </p>
+     *
      * @param packageName The app for which to set exemption.
      *
      * @return Whether the app is whitelisted.
@@ -8026,6 +8126,12 @@
     }
 
     /**
+     * <p>
+     * <strong>Note: </strong>In retrospect it would have been preferred to use
+     * more inclusive terminology when naming this API. Similar APIs added will
+     * refrain from using the term "whitelist".
+     * </p>
+     *
      * @return whether this package is whitelisted from having its runtime permission be
      *         auto-revoked if unused for an extended period of time.
      */
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index 04e15c2..5d4c843 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -377,6 +377,14 @@
     public static final int FLAG_IMMUTABLY_RESTRICTED = 1<<4;
 
     /**
+     * Flag for {@link #flags}, corresponding to <code>installerExemptIgnored</code>
+     * value of {@link android.R.attr#permissionFlags}.
+     *
+     * <p> Modifier for permission restriction. This permission cannot be exempted by the installer.
+     */
+    public static final int FLAG_INSTALLER_EXEMPT_IGNORED = 1 << 5;
+
+    /**
      * Flag for {@link #flags}, indicating that this permission has been
      * installed into the system's globally defined permissions.
      */
@@ -656,6 +664,11 @@
     }
 
     /** @hide */
+    public boolean isInstallerExemptIgnored() {
+        return (flags & PermissionInfo.FLAG_INSTALLER_EXEMPT_IGNORED) != 0;
+    }
+
+    /** @hide */
     public boolean isAppOp() {
         return (protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0;
     }
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 986e6ea..9d4ab0b 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -72,6 +72,7 @@
 import android.util.Size;
 
 import dalvik.annotation.optimization.FastNative;
+import dalvik.system.VMRuntime;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
@@ -351,6 +352,7 @@
         if (mMetadataPtr == 0) {
             throw new OutOfMemoryError("Failed to allocate native CameraMetadata");
         }
+        updateNativeAllocation();
     }
 
     /**
@@ -362,6 +364,7 @@
         if (mMetadataPtr == 0) {
             throw new OutOfMemoryError("Failed to allocate native CameraMetadata");
         }
+        updateNativeAllocation();
     }
 
     /**
@@ -443,6 +446,7 @@
 
     public void readFromParcel(Parcel in) {
         nativeReadFromParcel(in, mMetadataPtr);
+        updateNativeAllocation();
     }
 
     /**
@@ -533,6 +537,11 @@
         // Delete native pointer, but does not clear it
         nativeClose(mMetadataPtr);
         mMetadataPtr = 0;
+
+        if (mBufferSize > 0) {
+            VMRuntime.getRuntime().registerNativeFree(mBufferSize);
+        }
+        mBufferSize = 0;
     }
 
     private <T> T getBase(CameraCharacteristics.Key<T> key) {
@@ -1645,9 +1654,26 @@
         return true;
     }
 
+    private void updateNativeAllocation() {
+        long currentBufferSize = nativeGetBufferSize(mMetadataPtr);
+
+        if (currentBufferSize != mBufferSize) {
+            if (mBufferSize > 0) {
+                VMRuntime.getRuntime().registerNativeFree(mBufferSize);
+            }
+
+            mBufferSize = currentBufferSize;
+
+            if (mBufferSize > 0) {
+                VMRuntime.getRuntime().registerNativeAllocation(mBufferSize);
+            }
+        }
+    }
+
     private int mCameraId = -1;
     private boolean mHasMandatoryConcurrentStreams = false;
     private Size mDisplaySize = new Size(0, 0);
+    private long mBufferSize = 0;
 
     /**
      * Set the current camera Id.
@@ -1705,6 +1731,8 @@
     private static synchronized native boolean nativeIsEmpty(long ptr);
     @FastNative
     private static synchronized native int nativeGetEntryCount(long ptr);
+    @FastNative
+    private static synchronized native long nativeGetBufferSize(long ptr);
 
     @UnsupportedAppUsage
     @FastNative
@@ -1744,6 +1772,8 @@
         mCameraId = other.mCameraId;
         mHasMandatoryConcurrentStreams = other.mHasMandatoryConcurrentStreams;
         mDisplaySize = other.mDisplaySize;
+        updateNativeAllocation();
+        other.updateNativeAllocation();
     }
 
     /**
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 042808a4..be52667 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -660,6 +660,8 @@
         ThreadedRenderer.setFPSDivisor(divisor);
     }
 
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "Use {@link "
+            + "#postFrameCallback} instead")
     void doFrame(long frameTimeNanos, int frame, long frameTimelineVsyncId) {
         final long startNanos;
         synchronized (mLock) {
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index 51474d3..430e2cf 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
 import android.os.Looper;
 import android.os.MessageQueue;
 import android.util.Log;
@@ -157,6 +158,8 @@
      * @param frameTimelineVsyncId The frame timeline vsync id, used to correlate a frame
      * produced by HWUI with the timeline data stored in Surface Flinger.
      */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "Use {@link "
+            + "Choreographer#postFrameCallback} instead")
     public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
             long frameTimelineVsyncId) {
     }
@@ -200,6 +203,8 @@
 
     // Called from native code.
     @SuppressWarnings("unused")
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "Use {@link "
+            + "Choreographer#postFrameCallback} instead")
     private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame,
             long frameTimelineVsyncId) {
         onVsync(timestampNanos, physicalDisplayId, frame, frameTimelineVsyncId);
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 44c754c..9ba886a 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -2465,8 +2465,7 @@
      *  {@link #STATE_UNKNOWN_COMPAT_MODE} (beucase the session was finished when the URL bar
      *  changed on compat mode), {@link #STATE_UNKNOWN_FAILED} (because the session was finished
      *  when the service failed to fullfil the request, or {@link #STATE_DISABLED_BY_SERVICE}
-     *  (because the autofill service or {@link #STATE_DISABLED_BY_SERVICE} (because the autofill
-     *  service disabled further autofill requests for the activity).
+     *  (because the autofill service disabled further autofill requests for the activity).
      * @param autofillableIds list of ids that could trigger autofill, use to not handle a new
      *  session when they're entered.
      */
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 0535365..16fd383 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -643,54 +643,58 @@
     // change should also get get rid of the "internalNotifyXXXX" methods above
     void notifyChildSessionStarted(int parentSessionId, int childSessionId,
             @NonNull ContentCaptureContext clientContext) {
-        sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED)
+        mHandler.post(() -> sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED)
                 .setParentSessionId(parentSessionId).setClientContext(clientContext),
-                FORCE_FLUSH);
+                FORCE_FLUSH));
     }
 
     void notifyChildSessionFinished(int parentSessionId, int childSessionId) {
-        sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED)
-                .setParentSessionId(parentSessionId), FORCE_FLUSH);
+        mHandler.post(() -> sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED)
+                .setParentSessionId(parentSessionId), FORCE_FLUSH));
     }
 
     void notifyViewAppeared(int sessionId, @NonNull ViewStructureImpl node) {
-        sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED)
-                .setViewNode(node.mNode));
+        mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED)
+                .setViewNode(node.mNode)));
     }
 
     /** Public because is also used by ViewRootImpl */
     public void notifyViewDisappeared(int sessionId, @NonNull AutofillId id) {
-        sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED).setAutofillId(id));
+        mHandler.post(() -> sendEvent(
+                new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED).setAutofillId(id)));
     }
 
     void notifyViewTextChanged(int sessionId, @NonNull AutofillId id, @Nullable CharSequence text) {
-        sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED).setAutofillId(id)
-                .setText(text));
+        mHandler.post(() -> sendEvent(
+                new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED)
+                        .setAutofillId(id).setText(text)));
     }
 
     /** Public because is also used by ViewRootImpl */
     public void notifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) {
-        sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_INSETS_CHANGED)
-                .setInsets(viewInsets));
+        mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_INSETS_CHANGED)
+                .setInsets(viewInsets)));
     }
 
     /** Public because is also used by ViewRootImpl */
     public void notifyViewTreeEvent(int sessionId, boolean started) {
         final int type = started ? TYPE_VIEW_TREE_APPEARING : TYPE_VIEW_TREE_APPEARED;
-        sendEvent(new ContentCaptureEvent(sessionId, type), FORCE_FLUSH);
+        mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, type), FORCE_FLUSH));
     }
 
     void notifySessionResumed(int sessionId) {
-        sendEvent(new ContentCaptureEvent(sessionId, TYPE_SESSION_RESUMED), FORCE_FLUSH);
+        mHandler.post(() -> sendEvent(
+                new ContentCaptureEvent(sessionId, TYPE_SESSION_RESUMED), FORCE_FLUSH));
     }
 
     void notifySessionPaused(int sessionId) {
-        sendEvent(new ContentCaptureEvent(sessionId, TYPE_SESSION_PAUSED), FORCE_FLUSH);
+        mHandler.post(() -> sendEvent(
+                new ContentCaptureEvent(sessionId, TYPE_SESSION_PAUSED), FORCE_FLUSH));
     }
 
     void notifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) {
-        sendEvent(new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED)
-                .setClientContext(context));
+        mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED)
+                .setClientContext(context)));
     }
 
     @Override
diff --git a/core/java/android/window/ITransitionPlayer.aidl b/core/java/android/window/ITransitionPlayer.aidl
new file mode 100644
index 0000000..a8a29b2
--- /dev/null
+++ b/core/java/android/window/ITransitionPlayer.aidl
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.WindowContainerTransaction;
+
+/**
+ * Implemented by WMShell to initiate and play transition animations.
+ * The flow (with {@link IWindowOrganizerController}) looks like this:
+ * <p><ol>
+ *  <li>Core starts an activity and calls {@link #requestStartTransition}
+ *  <li>This TransitionPlayer impl does whatever, then calls
+ *      {@link IWindowOrganizerController#startTransition} to tell Core to formally start (until
+ *      this happens, Core will collect changes on the transition, but won't consider it ready to
+ *      animate).
+ *  <li>Once all collected changes on the transition have finished drawing, Core will then call
+ *      {@link #onTransitionReady} here to delegate the actual animation.
+ *  <li>Once this TransitionPlayer impl finishes animating, it notifies Core via
+ *      {@link IWindowOrganizerController#finishTransition}. At this point, ITransitionPlayer's
+ *      responsibilities end.
+ * </ul>
+ *
+ * {@hide}
+ */
+oneway interface ITransitionPlayer {
+
+    /**
+     * Called when all participants of a transition are ready to animate. This is in response to
+     * {@link IWindowOrganizerController#startTransition}.
+     *
+     * @param transitionToken An identifying token for the transition that is now ready to animate.
+     * @param info A collection of all the changes encapsulated by this transition.
+     * @param t A surface transaction containing the surface state prior to animating.
+     */
+    void onTransitionReady(in IBinder transitionToken, in TransitionInfo info,
+            in SurfaceControl.Transaction t);
+
+    /**
+     * Called when something in WMCore requires a transition to play -- for example when an Activity
+     * is started in a new Task.
+     *
+     * @param type The {@link WindowManager#TransitionType} of the transition to start.
+     * @param transitionToken An identifying token for the transition that needs to be started.
+     *                        Pass this to {@link IWindowOrganizerController#startTransition}.
+     */
+    void requestStartTransition(int type, in IBinder transitionToken);
+}
diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl
index 7e9c783..0cd9b36 100644
--- a/core/java/android/window/IWindowOrganizerController.aidl
+++ b/core/java/android/window/IWindowOrganizerController.aidl
@@ -18,8 +18,10 @@
 
 import android.view.SurfaceControl;
 
+import android.os.IBinder;
 import android.window.IDisplayAreaOrganizerController;
 import android.window.ITaskOrganizerController;
+import android.window.ITransitionPlayer;
 import android.window.IWindowContainerTransactionCallback;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
@@ -45,6 +47,30 @@
     int applySyncTransaction(in WindowContainerTransaction t,
             in IWindowContainerTransactionCallback callback);
 
+    /**
+     * Starts a transition.
+     * @param type The transition type.
+     * @param transitionToken A token associated with the transition to start. If null, a new
+     *                        transition will be created of the provided type.
+     * @param t Operations that are part of the transition.
+     * @return a token representing the transition. This will just be transitionToken if it was
+     *         non-null.
+     */
+    IBinder startTransition(int type, in @nullable IBinder transitionToken,
+            in @nullable WindowContainerTransaction t);
+
+    /**
+     * Finishes a transition. This must be called for all created transitions.
+     * @param transitionToken Which transition to finish
+     * @param t Changes to make before finishing but in the same SF Transaction. Can be null.
+     * @param callback Called when t is finished applying.
+     * @return An ID for the sync operation (see {@link #applySyncTransaction}. This will be
+     *         negative if no sync transaction was attached (null t or callback)
+     */
+    int finishTransition(in IBinder transitionToken,
+            in @nullable WindowContainerTransaction t,
+            in IWindowContainerTransactionCallback callback);
+
     /** @return An interface enabling the management of task organizers. */
     ITaskOrganizerController getTaskOrganizerController();
 
@@ -61,4 +87,10 @@
      * @return true if the screenshot was successful, false otherwise.
      */
     boolean takeScreenshot(in WindowContainerToken token, out SurfaceControl outSurfaceControl);
+
+    /**
+     * Registers a transition player with Core. There is only one of these at a time and calling
+     * this will replace the existing one if set.
+     */
+    void registerTransitionPlayer(in ITransitionPlayer player);
 }
diff --git a/core/java/android/app/timezonedetector/TimeZoneConfiguration.aidl b/core/java/android/window/TransitionInfo.aidl
similarity index 87%
copy from core/java/android/app/timezonedetector/TimeZoneConfiguration.aidl
copy to core/java/android/window/TransitionInfo.aidl
index 62240ba..6c33e97 100644
--- a/core/java/android/app/timezonedetector/TimeZoneConfiguration.aidl
+++ b/core/java/android/window/TransitionInfo.aidl
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
-package android.app.timezonedetector;
+package android.window;
 
-parcelable TimeZoneConfiguration;
+parcelable TransitionInfo;
+parcelable TransitionInfo.Change;
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
new file mode 100644
index 0000000..34d1d4e
--- /dev/null
+++ b/core/java/android/window/TransitionInfo.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Used to communicate information about what is changing during a transition to a TransitionPlayer.
+ * @hide
+ */
+public final class TransitionInfo implements Parcelable {
+
+    /** No transition mode. This is a placeholder, don't use this as an actual mode. */
+    public static final int TRANSIT_NONE = 0;
+
+    /** The container didn't exist before but will exist and be visible after. */
+    public static final int TRANSIT_OPEN = 1;
+
+    /** The container existed and was visible before but won't exist after. */
+    public static final int TRANSIT_CLOSE = 2;
+
+    /** The container existed before but was invisible and will be visible after. */
+    public static final int TRANSIT_SHOW = 3;
+
+    /** The container is going from visible to invisible but it will still exist after. */
+    public static final int TRANSIT_HIDE = 4;
+
+    /** The container exists and is visible before and after but it changes. */
+    public static final int TRANSIT_CHANGE = 5;
+
+    /** @hide */
+    @IntDef(prefix = { "TRANSIT_" }, value = {
+            TRANSIT_NONE,
+            TRANSIT_OPEN,
+            TRANSIT_CLOSE,
+            TRANSIT_SHOW,
+            TRANSIT_HIDE,
+            TRANSIT_CHANGE
+    })
+    public @interface TransitionMode {}
+
+    private final @WindowManager.TransitionType int mType;
+    private final ArrayList<Change> mChanges = new ArrayList<>();
+
+    /** @hide */
+    public TransitionInfo(@WindowManager.TransitionType int type) {
+        mType = type;
+    }
+
+    private TransitionInfo(Parcel in) {
+        mType = in.readInt();
+        in.readList(mChanges, null /* classLoader */);
+    }
+
+    @Override
+    /** @hide */
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mType);
+        dest.writeList(mChanges);
+    }
+
+    @NonNull
+    public static final Creator<TransitionInfo> CREATOR =
+            new Creator<TransitionInfo>() {
+                @Override
+                public TransitionInfo createFromParcel(Parcel in) {
+                    return new TransitionInfo(in);
+                }
+
+                @Override
+                public TransitionInfo[] newArray(int size) {
+                    return new TransitionInfo[size];
+                }
+            };
+
+    @Override
+    /** @hide */
+    public int describeContents() {
+        return 0;
+    }
+
+    public int getType() {
+        return mType;
+    }
+
+    @NonNull
+    public List<Change> getChanges() {
+        return mChanges;
+    }
+
+    /**
+     * @return the Change that a window is undergoing or {@code null} if not directly
+     * represented.
+     */
+    @Nullable
+    public Change getChange(@NonNull WindowContainerToken token) {
+        for (int i = mChanges.size() - 1; i >= 0; --i) {
+            if (mChanges.get(i).mContainer == token) {
+                return mChanges.get(i);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Add a {@link Change} to this transition.
+     */
+    public void addChange(@NonNull Change change) {
+        mChanges.add(change);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("{t=" + mType + " c=[");
+        for (int i = 0; i < mChanges.size(); ++i) {
+            if (i > 0) {
+                sb.append(',');
+            }
+            sb.append(mChanges.get(i));
+        }
+        sb.append("]}");
+        return sb.toString();
+    }
+
+    /** Converts a transition mode/action to its string representation. */
+    @NonNull
+    public static String modeToString(@TransitionMode int mode) {
+        switch(mode) {
+            case TRANSIT_NONE: return "NONE";
+            case TRANSIT_OPEN: return "OPEN";
+            case TRANSIT_CLOSE: return "CLOSE";
+            case TRANSIT_SHOW: return "SHOW";
+            case TRANSIT_HIDE: return "HIDE";
+            case TRANSIT_CHANGE: return "CHANGE";
+            default: return "<unknown:" + mode + ">";
+        }
+    }
+
+    /** Represents the change a WindowContainer undergoes during a transition */
+    public static final class Change implements Parcelable {
+        private final WindowContainerToken mContainer;
+        private WindowContainerToken mParent;
+        private final SurfaceControl mLeash;
+        private int mMode = TRANSIT_NONE;
+        private final Rect mStartBounds = new Rect();
+        private final Rect mEndBounds = new Rect();
+
+        public Change(@NonNull WindowContainerToken container, @NonNull SurfaceControl leash) {
+            mContainer = container;
+            mLeash = leash;
+        }
+
+        private Change(Parcel in) {
+            mContainer = WindowContainerToken.CREATOR.createFromParcel(in);
+            mParent = in.readParcelable(WindowContainerToken.class.getClassLoader());
+            mLeash = new SurfaceControl();
+            mLeash.readFromParcel(in);
+            mMode = in.readInt();
+            mStartBounds.readFromParcel(in);
+            mEndBounds.readFromParcel(in);
+        }
+
+        /** Sets the parent of this change's container. The parent must be a participant or null. */
+        public void setParent(@Nullable WindowContainerToken parent) {
+            mParent = parent;
+        }
+
+        /** Sets the transition mode for this change */
+        public void setMode(@TransitionMode int mode) {
+            mMode = mode;
+        }
+
+        /** Sets the bounds this container occupied before the change */
+        public void setStartBounds(@Nullable Rect rect) {
+            mStartBounds.set(rect);
+        }
+
+        /** Sets the bounds this container will occupy after the change */
+        public void setEndBounds(@Nullable Rect rect) {
+            mEndBounds.set(rect);
+        }
+
+        /** @return the container that is changing */
+        @NonNull
+        public WindowContainerToken getContainer() {
+            return mContainer;
+        }
+
+        /**
+         * @return the parent of the changing container. This is the parent within the participants,
+         * not necessarily the actual parent.
+         */
+        @Nullable
+        public WindowContainerToken getParent() {
+            return mParent;
+        }
+
+        /** @return which action this change represents. */
+        public @TransitionMode int getMode() {
+            return mMode;
+        }
+
+        /**
+         * @return the bounds of the container before the change. It may be empty if the container
+         * is coming into existence.
+         */
+        @NonNull
+        public Rect getStartBounds() {
+            return mStartBounds;
+        }
+
+        /**
+         * @return the bounds of the container after the change. It may be empty if the container
+         * is disappearing.
+         */
+        @NonNull
+        public Rect getEndBounds() {
+            return mEndBounds;
+        }
+
+        /** @return the leash or surface to animate for this container */
+        @NonNull
+        public SurfaceControl getLeash() {
+            return mLeash;
+        }
+
+        @Override
+        /** @hide */
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            mContainer.writeToParcel(dest, flags);
+            dest.writeParcelable(mParent, 0);
+            mLeash.writeToParcel(dest, flags);
+            dest.writeInt(mMode);
+            mStartBounds.writeToParcel(dest, flags);
+            mEndBounds.writeToParcel(dest, flags);
+        }
+
+        @NonNull
+        public static final Creator<Change> CREATOR =
+                new Creator<Change>() {
+                    @Override
+                    public Change createFromParcel(Parcel in) {
+                        return new Change(in);
+                    }
+
+                    @Override
+                    public Change[] newArray(int size) {
+                        return new Change[size];
+                    }
+                };
+
+        @Override
+        /** @hide */
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public String toString() {
+            return "{" + mContainer + "(" + mParent + ") leash=" + mLeash
+                    + " m=" + modeToString(mMode) + " sb=" + mStartBounds
+                    + " eb=" + mEndBounds + "}";
+        }
+    }
+}
diff --git a/core/java/android/window/WindowContainerToken.java b/core/java/android/window/WindowContainerToken.java
index c92ccae..96e8b44 100644
--- a/core/java/android/window/WindowContainerToken.java
+++ b/core/java/android/window/WindowContainerToken.java
@@ -78,6 +78,11 @@
     }
 
     @Override
+    public String toString() {
+        return "WCT{" + mRealToken + "}";
+    }
+
+    @Override
     public boolean equals(Object obj) {
         if (!(obj instanceof WindowContainerToken)) {
             return false;
diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java
index 97a97d9..5ac19fa 100644
--- a/core/java/android/window/WindowOrganizer.java
+++ b/core/java/android/window/WindowOrganizer.java
@@ -19,8 +19,10 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.app.ActivityTaskManager;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Singleton;
 import android.view.SurfaceControl;
@@ -66,6 +68,48 @@
     }
 
     /**
+     * Start a transition.
+     * @param type The type of the transition. This is ignored if a transitionToken is provided.
+     * @param transitionToken An existing transition to start. If null, a new transition is created.
+     * @param t The set of window operations that are part of this transition.
+     * @return A token identifying the transition. This will be the same as transitionToken if it
+     *         was provided.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    @NonNull
+    public IBinder startTransition(int type, @Nullable IBinder transitionToken,
+            @Nullable WindowContainerTransaction t) {
+        try {
+            return getWindowOrganizerController().startTransition(type, transitionToken, t);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Finishes a running transition.
+     * @param transitionToken The transition to finish. Can't be null.
+     * @param t A set of window operations to apply before finishing.
+     * @param callback A sync callback (if provided). See {@link #applySyncTransaction}.
+     * @return An ID for the sync operation if performed. See {@link #applySyncTransaction}.
+     *
+     * @hide
+     */
+    @SuppressLint("ExecutorRegistration")
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public int finishTransition(@NonNull IBinder transitionToken,
+            @Nullable WindowContainerTransaction t,
+            @Nullable WindowContainerTransactionCallback callback) {
+        try {
+            return getWindowOrganizerController().finishTransition(transitionToken, t,
+                    callback != null ? callback.mInterface : null);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Take a screenshot for a specified Window
      * @param token The token for the WindowContainer that should get a screenshot taken.
      * @return A SurfaceControl where the screenshot will be attached, or null if failed.
@@ -87,6 +131,19 @@
         }
     }
 
+    /**
+     * Register an ITransitionPlayer to handle transition animations.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void registerTransitionPlayer(@Nullable ITransitionPlayer player) {
+        try {
+            getWindowOrganizerController().registerTransitionPlayer(player);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
     IWindowOrganizerController getWindowOrganizerController() {
         return IWindowOrganizerControllerSingleton.get();
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index 9874c6a..50ba42f 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -74,6 +74,8 @@
             Consts.TAG_WM),
     WM_DEBUG_SYNC_ENGINE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM),
+    WM_DEBUG_WINDOW_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+            Consts.TAG_WM),
     TEST_GROUP(true, true, false, "WindowManagerProtoLogTest");
 
     private final boolean mEnabled;
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index cc2934f..caae518 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -242,4 +242,10 @@
      * @param connect {@code true} if needs connection, otherwise set the connection to null.
      */
     void requestWindowMagnificationConnection(boolean connect);
+
+    /**
+     * Allow for pass-through arguments from `adb shell cmd statusbar <args>`, and write to the
+     * file descriptor passed in.
+     */
+     void passThroughShellCommand(in String[] args, in ParcelFileDescriptor pfd);
 }
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 396a84f..ed663cf 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -46,6 +46,7 @@
 import com.android.internal.util.XmlUtils;
 
 import libcore.io.IoUtils;
+import libcore.util.EmptyArray;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -55,7 +56,6 @@
 import java.io.FileNotFoundException;
 import java.io.FileReader;
 import java.io.IOException;
-import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -95,7 +95,7 @@
     private static final String VENDOR_SKU_PROPERTY = "ro.boot.product.vendor.sku";
 
     // Group-ids that are given to all packages as read from etc/permissions/*.xml.
-    int[] mGlobalGids;
+    int[] mGlobalGids = EmptyArray.INT;
 
     // These are the built-in uid -> permission mappings that were read from the
     // system configuration files.
diff --git a/core/jni/android_hardware_camera2_CameraMetadata.cpp b/core/jni/android_hardware_camera2_CameraMetadata.cpp
index 859b40a..919e351 100644
--- a/core/jni/android_hardware_camera2_CameraMetadata.cpp
+++ b/core/jni/android_hardware_camera2_CameraMetadata.cpp
@@ -249,6 +249,16 @@
     return metadata->entryCount();
 }
 
+static jlong CameraMetadata_getBufferSize(JNIEnv *env, jclass thiz, jlong ptr) {
+    ALOGV("%s", __FUNCTION__);
+
+    CameraMetadata* metadata = CameraMetadata_getPointerThrow(env, ptr);
+
+    if (metadata == NULL) return 0;
+
+    return metadata->bufferSize();
+}
+
 // idempotent. calling more than once has no effect.
 static void CameraMetadata_close(JNIEnv *env, jclass thiz, jlong ptr) {
     ALOGV("%s", __FUNCTION__);
@@ -561,6 +571,9 @@
   { "nativeGetEntryCount",
     "(J)I",
     (void*)CameraMetadata_getEntryCount },
+  { "nativeGetBufferSize",
+    "(J)J",
+    (void*)CameraMetadata_getBufferSize },
   { "nativeClose",
     "(J)V",
     (void*)CameraMetadata_close },
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 723cceb..7247e4f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1244,8 +1244,19 @@
         android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_recordAudio"
         android:description="@string/permdesc_recordAudio"
+        android:backgroundPermission="android.permission.RECORD_BACKGROUND_AUDIO"
         android:protectionLevel="dangerous|instant" />
 
+    <!-- Allows an application to record audio while in the background.
+         <p>Protection level: dangerous
+    -->
+    <permission android:name="android.permission.RECORD_BACKGROUND_AUDIO"
+        android:permissionGroup="android.permission-group.UNDEFINED"
+        android:label="@string/permlab_recordBackgroundAudio"
+        android:description="@string/permdesc_recordBackgroundAudio"
+        android:permissionFlags="hardRestricted|installerExemptIgnored"
+        android:protectionLevel="dangerous" />
+
     <!-- ====================================================================== -->
     <!-- Permissions for activity recognition                        -->
     <!-- ====================================================================== -->
@@ -1313,8 +1324,19 @@
         android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_camera"
         android:description="@string/permdesc_camera"
+        android:backgroundPermission="android.permission.BACKGROUND_CAMERA"
         android:protectionLevel="dangerous|instant" />
 
+    <!-- Required to be able to access the camera device in the background.
+         <p>Protection level: dangerous
+    -->
+    <permission android:name="android.permission.BACKGROUND_CAMERA"
+            android:permissionGroup="android.permission-group.UNDEFINED"
+            android:label="@string/permlab_backgroundCamera"
+            android:description="@string/permdesc_backgroundCamera"
+            android:permissionFlags="hardRestricted|installerExemptIgnored"
+            android:protectionLevel="dangerous" />
+
     <!-- @SystemApi Required in addition to android.permission.CAMERA to be able to access
            system only camera devices.
            <p>Protection level: system|signature
@@ -2687,6 +2709,14 @@
     <permission android:name="android.permission.SUGGEST_MANUAL_TIME_AND_ZONE"
         android:protectionLevel="signature" />
 
+    <!-- Allows applications like settings to manage configuration associated with automatic time
+         and time zone detection.
+         <p>Not for use by third-party applications.
+         @hide
+    -->
+    <permission android:name="android.permission.MANAGE_TIME_AND_ZONE_DETECTION"
+        android:protectionLevel="signature|privileged" />
+
     <!-- ==================================================== -->
     <!-- Permissions related to changing status bar   -->
     <!-- ==================================================== -->
@@ -3546,6 +3576,13 @@
          android:protectionLevel="signature" />
     <uses-permission android:name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID" />
 
+    <!-- This permission is required by Media Resource Observer Service when
+         accessing its registerObserver Api.
+         <p>Protection level: signature|privileged
+         <p>Not for use by third-party applications.
+         @hide -->
+    <permission android:name="android.permission.REGISTER_MEDIA_RESOURCE_OBSERVER"
+         android:protectionLevel="signature|privileged" />
 
     <!-- Must be required by a {@link android.media.routing.MediaRouteService}
          to ensure that only the system can interact with it.
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 1c71bae..96ebc12 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -344,6 +344,11 @@
              the app is uninstalled.
         -->
         <flag name="immutablyRestricted" value="0x10" />
+        <!--
+             Modifier for permission restriction. This permission cannot
+             be exempted by the installer.
+        -->
+        <flag name="installerExemptIgnored" value="0x20" />
     </attr>
 
     <!-- Specified the name of a group that this permission is associated
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 25a9bbd..fc489b1 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1166,7 +1166,12 @@
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_recordAudio">record audio</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permdesc_recordAudio">This app can record audio using the microphone at any time.</string>
+    <string name="permdesc_recordAudio">This app can record audio using the microphone while the app is in use.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] -->
+    <string name="permlab_recordBackgroundAudio">record audio in the background</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] -->
+    <string name="permdesc_recordBackgroundAudio">This app can record audio using the microphone at any time.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_sim_communication">send commands to the SIM</string>
@@ -1181,7 +1186,12 @@
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_camera">take pictures and videos</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permdesc_camera">This app can take pictures and record videos using the camera at any time.</string>
+    <string name="permdesc_camera">This app can take pictures and record videos using the camera while the app is in use.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] -->
+    <string name="permlab_backgroundCamera">take pictures and videos in the background</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] -->
+    <string name="permdesc_backgroundCamera">This app can take pictures and record videos using the camera at any time.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
     <string name="permlab_systemCamera">Allow an application or service access to system cameras to take pictures and videos</string>
diff --git a/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
new file mode 100644
index 0000000..01a25b2
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.time;
+
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_POSSESSED;
+import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+
+import android.os.UserHandle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class TimeZoneCapabilitiesTest {
+
+    private static final UserHandle TEST_USER_HANDLE = UserHandle.of(12345);
+
+    @Test
+    public void testEquals() {
+        TimeZoneCapabilities.Builder builder1 = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
+                .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+                .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+                .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED);
+        TimeZoneCapabilities.Builder builder2 = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
+                .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+                .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+                .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED);
+        {
+            TimeZoneCapabilities one = builder1.build();
+            TimeZoneCapabilities two = builder2.build();
+            assertEquals(one, two);
+        }
+
+        builder2.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
+        {
+            TimeZoneCapabilities one = builder1.build();
+            TimeZoneCapabilities two = builder2.build();
+            assertNotEquals(one, two);
+        }
+
+        builder1.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
+        {
+            TimeZoneCapabilities one = builder1.build();
+            TimeZoneCapabilities two = builder2.build();
+            assertEquals(one, two);
+        }
+
+        builder2.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
+        {
+            TimeZoneCapabilities one = builder1.build();
+            TimeZoneCapabilities two = builder2.build();
+            assertNotEquals(one, two);
+        }
+
+        builder1.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
+        {
+            TimeZoneCapabilities one = builder1.build();
+            TimeZoneCapabilities two = builder2.build();
+            assertEquals(one, two);
+        }
+
+        builder2.setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED);
+        {
+            TimeZoneCapabilities one = builder1.build();
+            TimeZoneCapabilities two = builder2.build();
+            assertNotEquals(one, two);
+        }
+
+        builder1.setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED);
+        {
+            TimeZoneCapabilities one = builder1.build();
+            TimeZoneCapabilities two = builder2.build();
+            assertEquals(one, two);
+        }
+    }
+
+    @Test
+    public void testParcelable() {
+        TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
+                .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+                .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+                .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED);
+        assertRoundTripParcelable(builder.build());
+
+        builder.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
+        assertRoundTripParcelable(builder.build());
+
+        builder.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
+        assertRoundTripParcelable(builder.build());
+
+        builder.setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED);
+        assertRoundTripParcelable(builder.build());
+    }
+
+    @Test
+    public void testTryApplyConfigChanges_permitted() {
+        TimeZoneConfiguration oldConfiguration =
+                new TimeZoneConfiguration.Builder()
+                        .setAutoDetectionEnabled(true)
+                        .setGeoDetectionEnabled(true)
+                        .build();
+        TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
+                .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+                .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+                .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED)
+                .build();
+
+        TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder()
+                .setAutoDetectionEnabled(false)
+                .build();
+
+        TimeZoneConfiguration expected = new TimeZoneConfiguration.Builder(oldConfiguration)
+                .setAutoDetectionEnabled(false)
+                .build();
+        assertEquals(expected, capabilities.tryApplyConfigChanges(oldConfiguration, configChange));
+    }
+
+    @Test
+    public void testTryApplyConfigChanges_notPermitted() {
+        TimeZoneConfiguration oldConfiguration =
+                new TimeZoneConfiguration.Builder()
+                        .setAutoDetectionEnabled(true)
+                        .setGeoDetectionEnabled(true)
+                        .build();
+        TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
+                .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
+                .setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
+                .setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
+                .build();
+
+        TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder()
+                .setAutoDetectionEnabled(false)
+                .build();
+
+        assertNull(capabilities.tryApplyConfigChanges(oldConfiguration, configChange));
+    }
+}
diff --git a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java b/core/tests/coretests/src/android/app/time/TimeZoneConfigurationTest.java
similarity index 81%
rename from core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java
rename to core/tests/coretests/src/android/app/time/TimeZoneConfigurationTest.java
index faf908d..3948eb8 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeZoneConfigurationTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.app.timezonedetector;
+package android.app.time;
 
 import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
 
@@ -23,16 +23,20 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
+@RunWith(AndroidJUnit4.class)
+@SmallTest
 public class TimeZoneConfigurationTest {
 
-    private static final int ARBITRARY_USER_ID = 9876;
-
     @Test
     public void testBuilder_copyConstructor() {
         TimeZoneConfiguration.Builder builder1 =
-                new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+                new TimeZoneConfiguration.Builder()
                         .setAutoDetectionEnabled(true)
                         .setGeoDetectionEnabled(true);
         TimeZoneConfiguration configuration1 = builder1.build();
@@ -45,27 +49,27 @@
 
     @Test
     public void testIntrospectionMethods() {
-        TimeZoneConfiguration empty = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID).build();
+        TimeZoneConfiguration empty = new TimeZoneConfiguration.Builder().build();
         assertFalse(empty.isComplete());
-        assertFalse(empty.hasSetting(TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED));
+        assertFalse(empty.hasIsAutoDetectionEnabled());
 
-        TimeZoneConfiguration completeConfig = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+        TimeZoneConfiguration completeConfig = new TimeZoneConfiguration.Builder()
                 .setAutoDetectionEnabled(true)
                 .setGeoDetectionEnabled(true)
                 .build();
         assertTrue(completeConfig.isComplete());
-        assertTrue(completeConfig.hasSetting(TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED));
+        assertTrue(completeConfig.hasIsGeoDetectionEnabled());
     }
 
     @Test
     public void testBuilder_mergeProperties() {
-        TimeZoneConfiguration configuration1 = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+        TimeZoneConfiguration configuration1 = new TimeZoneConfiguration.Builder()
                 .setAutoDetectionEnabled(true)
                 .build();
 
         {
             TimeZoneConfiguration mergedEmptyAnd1 =
-                    new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+                    new TimeZoneConfiguration.Builder()
                             .mergeProperties(configuration1)
                             .build();
             assertEquals(configuration1, mergedEmptyAnd1);
@@ -73,7 +77,7 @@
 
         {
             TimeZoneConfiguration configuration2 =
-                    new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+                    new TimeZoneConfiguration.Builder()
                             .setAutoDetectionEnabled(false)
                             .build();
 
@@ -90,22 +94,14 @@
     @Test
     public void testEquals() {
         TimeZoneConfiguration.Builder builder1 =
-                new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID);
+                new TimeZoneConfiguration.Builder();
         {
             TimeZoneConfiguration one = builder1.build();
             assertEquals(one, one);
         }
 
-        {
-            TimeZoneConfiguration.Builder differentUserBuilder =
-                    new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID + 1);
-            TimeZoneConfiguration one = builder1.build();
-            TimeZoneConfiguration two = differentUserBuilder.build();
-            assertNotEquals(one, two);
-        }
-
         TimeZoneConfiguration.Builder builder2 =
-                new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID);
+                new TimeZoneConfiguration.Builder();
         {
             TimeZoneConfiguration one = builder1.build();
             TimeZoneConfiguration two = builder2.build();
@@ -159,7 +155,7 @@
     @Test
     public void testParcelable() {
         TimeZoneConfiguration.Builder builder =
-                new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID);
+                new TimeZoneConfiguration.Builder();
         assertRoundTripParcelable(builder.build());
 
         builder.setAutoDetectionEnabled(true);
diff --git a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java b/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java
deleted file mode 100644
index db127c6..0000000
--- a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.timezonedetector;
-
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNull;
-
-import org.junit.Test;
-
-public class TimeZoneCapabilitiesTest {
-
-    private static final int ARBITRARY_USER_ID = 12345;
-
-    @Test
-    public void testEquals() {
-        TimeZoneConfiguration configuration1 = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
-                .setAutoDetectionEnabled(true)
-                .setGeoDetectionEnabled(true)
-                .build();
-        TimeZoneConfiguration configuration2 = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
-                .setAutoDetectionEnabled(false)
-                .setGeoDetectionEnabled(false)
-                .build();
-
-        TimeZoneCapabilities.Builder builder1 = new TimeZoneCapabilities.Builder()
-                .setConfiguration(configuration1)
-                .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
-                .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
-                .setSuggestManualTimeZone(CAPABILITY_POSSESSED);
-        TimeZoneCapabilities.Builder builder2 = new TimeZoneCapabilities.Builder()
-                .setConfiguration(configuration1)
-                .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
-                .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
-                .setSuggestManualTimeZone(CAPABILITY_POSSESSED);
-        {
-            TimeZoneCapabilities one = builder1.build();
-            TimeZoneCapabilities two = builder2.build();
-            assertEquals(one, two);
-        }
-
-        builder2.setConfiguration(configuration2);
-        {
-            TimeZoneCapabilities one = builder1.build();
-            TimeZoneCapabilities two = builder2.build();
-            assertNotEquals(one, two);
-        }
-
-        builder1.setConfiguration(configuration2);
-        {
-            TimeZoneCapabilities one = builder1.build();
-            TimeZoneCapabilities two = builder2.build();
-            assertEquals(one, two);
-        }
-
-        builder2.setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
-        {
-            TimeZoneCapabilities one = builder1.build();
-            TimeZoneCapabilities two = builder2.build();
-            assertNotEquals(one, two);
-        }
-
-        builder1.setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
-        {
-            TimeZoneCapabilities one = builder1.build();
-            TimeZoneCapabilities two = builder2.build();
-            assertEquals(one, two);
-        }
-
-        builder2.setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
-        {
-            TimeZoneCapabilities one = builder1.build();
-            TimeZoneCapabilities two = builder2.build();
-            assertNotEquals(one, two);
-        }
-
-        builder1.setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
-        {
-            TimeZoneCapabilities one = builder1.build();
-            TimeZoneCapabilities two = builder2.build();
-            assertEquals(one, two);
-        }
-
-        builder2.setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED);
-        {
-            TimeZoneCapabilities one = builder1.build();
-            TimeZoneCapabilities two = builder2.build();
-            assertNotEquals(one, two);
-        }
-
-        builder1.setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED);
-        {
-            TimeZoneCapabilities one = builder1.build();
-            TimeZoneCapabilities two = builder2.build();
-            assertEquals(one, two);
-        }
-    }
-
-    @Test
-    public void testParcelable() {
-        TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
-                .setAutoDetectionEnabled(true)
-                .setGeoDetectionEnabled(true)
-                .build();
-        TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder()
-                .setConfiguration(configuration)
-                .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
-                .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
-                .setSuggestManualTimeZone(CAPABILITY_POSSESSED);
-        assertRoundTripParcelable(builder.build());
-
-        builder.setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
-        assertRoundTripParcelable(builder.build());
-
-        builder.setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
-        assertRoundTripParcelable(builder.build());
-
-        builder.setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED);
-        assertRoundTripParcelable(builder.build());
-    }
-
-    @Test
-    public void testApplyUpdate_permitted() {
-        TimeZoneConfiguration oldConfiguration =
-                new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
-                        .setAutoDetectionEnabled(true)
-                        .setGeoDetectionEnabled(true)
-                        .build();
-        TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder()
-                .setConfiguration(oldConfiguration)
-                .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
-                .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
-                .setSuggestManualTimeZone(CAPABILITY_POSSESSED)
-                .build();
-        assertEquals(oldConfiguration, capabilities.getConfiguration());
-
-        TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
-                .setAutoDetectionEnabled(false)
-                .build();
-
-        TimeZoneConfiguration expected = new TimeZoneConfiguration.Builder(oldConfiguration)
-                .setAutoDetectionEnabled(false)
-                .build();
-        assertEquals(expected, capabilities.applyUpdate(configChange));
-    }
-
-    @Test
-    public void testApplyUpdate_notPermitted() {
-        TimeZoneConfiguration oldConfiguration =
-                new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
-                        .setAutoDetectionEnabled(true)
-                        .setGeoDetectionEnabled(true)
-                        .build();
-        TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder()
-                .setConfiguration(oldConfiguration)
-                .setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED)
-                .setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED)
-                .setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED)
-                .build();
-        assertEquals(oldConfiguration, capabilities.getConfiguration());
-
-        TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
-                .setAutoDetectionEnabled(false)
-                .build();
-
-        assertNull(capabilities.applyUpdate(configChange));
-    }
-}
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 10c2b09..977703d 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -153,6 +153,7 @@
     <assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="media" />
     <assign-permission name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" uid="media" />
     <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="media" />
+    <assign-permission name="android.permission.REGISTER_MEDIA_RESOURCE_OBSERVER" uid="media" />
 
     <assign-permission name="android.permission.INTERNET" uid="media" />
 
@@ -222,6 +223,15 @@
                       targetSdk="29">
         <new-permission name="android.permission.ACCESS_MEDIA_LOCATION" />
     </split-permission>
+    <split-permission name="android.permission.RECORD_AUDIO"
+                      targetSdk="31">
+        <new-permission name="android.permission.RECORD_BACKGROUND_AUDIO" />
+    </split-permission>
+    <split-permission name="android.permission.CAMERA"
+                      targetSdk="31">
+        <new-permission name="android.permission.BACKGROUND_CAMERA" />
+    </split-permission>
+
 
     <!-- This is a list of all the libraries available for application
          code to link against. -->
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 2779a75..81da5c8 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -435,6 +435,8 @@
         <permission name="android.permission.CAPTURE_AUDIO_OUTPUT" />
         <!-- Permissions required for CTS test - AdbManagerTest -->
         <permission name="android.permission.MANAGE_DEBUGGING" />
+        <!-- Permissions required for CTS test - TimeManagerTest -->
+        <permission name="android.permission.MANAGE_TIME_AND_ZONE_DETECTION" />
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 9eaeed1..c53ea87 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -205,6 +205,12 @@
       "group": "WM_DEBUG_APP_TRANSITIONS",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-1844540996": {
+      "message": "  Initial targets: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "-1838803135": {
       "message": "Attempted to set windowing mode to a display that does not exist: %d",
       "level": "WARN",
@@ -343,12 +349,24 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
+    "-1587921395": {
+      "message": "  Top targets: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "-1568331821": {
       "message": "Enabling listeners",
       "level": "VERBOSE",
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/DisplayRotation.java"
     },
+    "-1567866547": {
+      "message": "Collecting in transition %d: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "-1554521902": {
       "message": "showInsets(ime) was requested by different window: %s ",
       "level": "WARN",
@@ -421,6 +439,12 @@
       "group": "WM_DEBUG_ADD_REMOVE",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-1452274694": {
+      "message": "      CAN PROMOTE: promoting to parent %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "-1443029505": {
       "message": "SAFE MODE ENABLED (menu=%d s=%d dpad=%d trackball=%d)",
       "level": "INFO",
@@ -475,6 +499,12 @@
       "group": "WM_DEBUG_SYNC_ENGINE",
       "at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
     },
+    "-1375751630": {
+      "message": "  --- Start combine pass ---",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "-1364754753": {
       "message": "Task vanished taskId=%d",
       "level": "VERBOSE",
@@ -787,6 +817,12 @@
       "group": "WM_SHOW_TRANSACTIONS",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-855366859": {
+      "message": "        merging children in from %s: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "-853404763": {
       "message": "\twallpaper=%s",
       "level": "DEBUG",
@@ -871,6 +907,12 @@
       "group": "WM_DEBUG_FOCUS",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-703543418": {
+      "message": "      check sibling %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "-694710814": {
       "message": "Pausing rotation during drag",
       "level": "DEBUG",
@@ -925,6 +967,12 @@
       "group": "WM_DEBUG_ADD_REMOVE",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-622017164": {
+      "message": "Finish Transition: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/TransitionController.java"
+    },
     "-618015844": {
       "message": "performEnableScreen: mDisplayEnabled=%b mForceDisplayEnabled=%b mShowingBootMessages=%b mSystemBooted=%b mOnlyCore=%b. %s",
       "level": "INFO",
@@ -979,12 +1027,24 @@
       "group": "WM_SHOW_TRANSACTIONS",
       "at": "com\/android\/server\/wm\/WindowAnimator.java"
     },
+    "-532081937": {
+      "message": "  Commit activity becoming invisible: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "-519504830": {
       "message": "applyAnimation: anim=%s nextAppTransition=ANIM_CUSTOM transit=%s isEntrance=%b Callers=%s",
       "level": "VERBOSE",
       "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
       "at": "com\/android\/server\/wm\/AppTransition.java"
     },
+    "-509601642": {
+      "message": "    checking %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "-507657818": {
       "message": "Window %s is already added",
       "level": "WARN",
@@ -1027,6 +1087,12 @@
       "group": "WM_DEBUG_CONFIGURATION",
       "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
     },
+    "-446752714": {
+      "message": "        SKIP: sibling contains top target %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "-445944810": {
       "message": "finish(%b): mCanceled=%b",
       "level": "DEBUG",
@@ -1153,6 +1219,12 @@
       "group": "WM_DEBUG_ADD_REMOVE",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-302335479": {
+      "message": "        remove from topTargets %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "-272719931": {
       "message": "startLockTaskModeLocked: %s",
       "level": "WARN",
@@ -1441,12 +1513,24 @@
       "group": "WM_DEBUG_WINDOW_ORGANIZER",
       "at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java"
     },
+    "182319432": {
+      "message": "        remove from targets %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "184362060": {
       "message": "screenshotTask(%d): mCanceled=%b",
       "level": "DEBUG",
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimationController.java"
     },
+    "184610856": {
+      "message": "Start calculating TransitionInfo based on participants: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "186668272": {
       "message": "Now changing app %s",
       "level": "VERBOSE",
@@ -1525,6 +1609,12 @@
       "group": "WM_DEBUG_APP_TRANSITIONS",
       "at": "com\/android\/server\/wm\/AppTransitionController.java"
     },
+    "259206414": {
+      "message": "Creating Transition: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/TransitionController.java"
+    },
     "269576220": {
       "message": "Resuming rotation after drag",
       "level": "DEBUG",
@@ -1657,6 +1747,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "430260320": {
+      "message": "        sibling is a top target with mode %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "435494046": {
       "message": "Attempted to add window to a display for which the application does not have access: %d.  Aborting.",
       "level": "WARN",
@@ -1711,6 +1807,12 @@
       "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
       "at": "com\/android\/server\/wm\/AppTransition.java"
     },
+    "528150092": {
+      "message": "        keep as target %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "531242746": {
       "message": "  THUMBNAIL %s: CREATE",
       "level": "INFO",
@@ -1903,6 +2005,12 @@
       "group": "WM_DEBUG_CONFIGURATION",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "744171317": {
+      "message": "      SKIP: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "745391677": {
       "message": "  CREATE SURFACE %s IN SESSION %s: pid=%d format=%d flags=0x%x \/ %s",
       "level": "INFO",
@@ -1921,6 +2029,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/TaskPositioner.java"
     },
+    "793568608": {
+      "message": "        SKIP: sibling is visible but not part of transition",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "794570322": {
       "message": "Now closing app %s",
       "level": "VERBOSE",
@@ -1957,6 +2071,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/DisplayContent.java"
     },
+    "849147756": {
+      "message": "Finish collecting in transition %d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "853091290": {
       "message": "Moved stack=%s behind stack=%s",
       "level": "DEBUG",
@@ -2041,6 +2161,12 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
+    "996960396": {
+      "message": "Starting Transition %d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "1000601037": {
       "message": "SyncSet{%x:%d} Set ready",
       "level": "VERBOSE",
@@ -2101,6 +2227,12 @@
       "group": "WM_DEBUG_ADD_REMOVE",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1115248873": {
+      "message": "Calling onTransitionReady: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "1115417974": {
       "message": "FORCED DISPLAY SIZE: %dx%d",
       "level": "INFO",
@@ -2125,6 +2257,12 @@
       "group": "WM_DEBUG_APP_TRANSITIONS",
       "at": "com\/android\/server\/wm\/DisplayContent.java"
     },
+    "1186730970": {
+      "message": "          no common mode yet, so set it",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "1208313423": {
       "message": "addWindowToken: Attempted to add token: %s for non-exiting displayId=%d",
       "level": "WARN",
@@ -2293,6 +2431,12 @@
       "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
       "at": "com\/android\/server\/wm\/AppTransitionController.java"
     },
+    "1469310004": {
+      "message": "          SKIP: common mode mismatch. was %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "1495525537": {
       "message": "createWallpaperAnimations()",
       "level": "DEBUG",
@@ -2509,6 +2653,12 @@
       "group": "WM_DEBUG_ADD_REMOVE",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "1794249572": {
+      "message": "Requesting StartTransition: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/TransitionController.java"
+    },
     "1822843721": {
       "message": "Aborted starting %s: startingData=%s",
       "level": "VERBOSE",
@@ -2826,6 +2976,9 @@
     "WM_DEBUG_WINDOW_ORGANIZER": {
       "tag": "WindowManager"
     },
+    "WM_DEBUG_WINDOW_TRANSITIONS": {
+      "tag": "WindowManager"
+    },
     "WM_ERROR": {
       "tag": "WindowManager"
     },
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 964640b..28d7911 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -3111,7 +3111,7 @@
     @CriticalNative
     private static native boolean nGetFillPath(long paintPtr, long src, long dst);
     @CriticalNative
-    private static native void nSetShader(long paintPtr, long shader);
+    private static native long nSetShader(long paintPtr, long shader);
     @CriticalNative
     private static native long nSetColorFilter(long paintPtr, long filter);
     @CriticalNative
diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java
index 2a52ce9..e1a1795 100644
--- a/graphics/java/android/graphics/fonts/Font.java
+++ b/graphics/java/android/graphics/fonts/Font.java
@@ -591,14 +591,14 @@
      *
      * @param glyphId a glyph ID
      * @param paint a paint object used for resolving glyph style
-     * @param rect a nullable destination object. If null is passed, this function just return the
-     *             horizontal advance. If non-null is passed, this function fills bounding box
-     *             information to this object.
+     * @param outBoundingBox a nullable destination object. If null is passed, this function just
+     *                      return the horizontal advance. If non-null is passed, this function
+     *                      fills bounding box information to this object.
      * @return the amount of horizontal advance in pixels
      */
     public float getGlyphBounds(@IntRange(from = 0) int glyphId, @NonNull Paint paint,
-            @Nullable RectF rect) {
-        return nGetGlyphBounds(mNativePtr, glyphId, paint.getNativeInstance(), rect);
+            @Nullable RectF outBoundingBox) {
+        return nGetGlyphBounds(mNativePtr, glyphId, paint.getNativeInstance(), outBoundingBox);
     }
 
     /**
@@ -607,15 +607,15 @@
      * Note that {@link android.graphics.Typeface} in {@link android.graphics.Paint} is ignored.
      *
      * @param paint a paint object used for retrieving font metrics.
-     * @param metrics a nullable destination object. If null is passed, this function only retrieve
-     *                recommended interline spacing. If non-null is passed, this function fills to
-     *                font metrics to it.
+     * @param outMetrics a nullable destination object. If null is passed, this function only
+     *                  retrieve recommended interline spacing. If non-null is passed, this function
+     *                  fills to font metrics to it.
      *
      * @see Paint#getFontMetrics()
      * @see Paint#getFontMetricsInt()
      */
-    public void getMetrics(@NonNull Paint paint, @Nullable Paint.FontMetrics metrics) {
-        nGetFontMetrics(mNativePtr, paint.getNativeInstance(), metrics);
+    public void getMetrics(@NonNull Paint paint, @Nullable Paint.FontMetrics outMetrics) {
+        nGetFontMetrics(mNativePtr, paint.getNativeInstance(), outMetrics);
     }
 
     /** @hide */
diff --git a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
index a13e98c..227eec2 100644
--- a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
+++ b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
@@ -1,12 +1,24 @@
 {
   "version": "1.0.0",
   "messages": {
+    "-1534364071": {
+      "message": "onTransitionReady %s: %s",
+      "level": "VERBOSE",
+      "group": "WM_SHELL_TRANSITIONS",
+      "at": "com\/android\/wm\/shell\/Transitions.java"
+    },
     "-1501874464": {
       "message": "Fullscreen Task Appeared: #%d",
       "level": "VERBOSE",
       "group": "WM_SHELL_TASK_ORG",
       "at": "com\/android\/wm\/shell\/FullscreenTaskListener.java"
     },
+    "-1480787369": {
+      "message": "Transition requested: type=%d %s",
+      "level": "VERBOSE",
+      "group": "WM_SHELL_TRANSITIONS",
+      "at": "com\/android\/wm\/shell\/Transitions.java"
+    },
     "-1340279385": {
       "message": "Remove listener=%s",
       "level": "VERBOSE",
@@ -31,6 +43,12 @@
       "group": "WM_SHELL_TASK_ORG",
       "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
     },
+    "-191422040": {
+      "message": "Transition animations finished, notifying core %s",
+      "level": "VERBOSE",
+      "group": "WM_SHELL_TRANSITIONS",
+      "at": "com\/android\/wm\/shell\/Transitions.java"
+    },
     "157713005": {
       "message": "Task info changed taskId=%d",
       "level": "VERBOSE",
@@ -53,6 +71,9 @@
   "groups": {
     "WM_SHELL_TASK_ORG": {
       "tag": "WindowManagerShell"
+    },
+    "WM_SHELL_TRANSITIONS": {
+      "tag": "WindowManagerShell"
     }
   }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
index b275331..9d6271b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
@@ -50,9 +50,13 @@
                 // properties in a bad state).
                 t.setPosition(leash, 0, 0);
                 t.setWindowCrop(leash, null);
-                t.setAlpha(leash, 1f);
-                t.setMatrix(leash, 1, 0, 0, 1);
-                t.show(leash);
+                // TODO(shell-transitions): Eventually set everything in transition so there's no
+                //                          SF Transaction here.
+                if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
+                    t.setAlpha(leash, 1f);
+                    t.setMatrix(leash, 1, 0, 0, 1);
+                    t.show(leash);
+                }
             });
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index d650a95..8f496d0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -28,7 +28,9 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
 import java.util.ArrayList;
@@ -59,16 +61,25 @@
     // require us to report to both old and new listeners)
     private final SparseArray<Pair<RunningTaskInfo, SurfaceControl>> mTasks = new SparseArray<>();
 
-    public ShellTaskOrganizer(SyncTransactionQueue syncQueue) {
+    // TODO(shell-transitions): move to a more "global" Shell location as this isn't only for Tasks
+    private final Transitions mTransitions;
+
+    public ShellTaskOrganizer(SyncTransactionQueue syncQueue, TransactionPool transactionPool,
+            ShellExecutor mainExecutor, ShellExecutor animExecutor) {
         super();
         addListener(new FullscreenTaskListener(syncQueue), WINDOWING_MODE_FULLSCREEN);
+        mTransitions = new Transitions(this, transactionPool, mainExecutor, animExecutor);
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) registerTransitionPlayer(mTransitions);
     }
 
     @VisibleForTesting
     ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController,
-                       SyncTransactionQueue syncQueue) {
+            SyncTransactionQueue syncQueue, TransactionPool transactionPool,
+            ShellExecutor mainExecutor, ShellExecutor animExecutor) {
         super(taskOrganizerController);
         addListener(new FullscreenTaskListener(syncQueue), WINDOWING_MODE_FULLSCREEN);
+        mTransitions = new Transitions(this, transactionPool, mainExecutor, animExecutor);
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) registerTransitionPlayer(mTransitions);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java
new file mode 100644
index 0000000..36e49d9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2020 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.wm.shell;
+
+import static android.window.TransitionInfo.TRANSIT_CLOSE;
+import static android.window.TransitionInfo.TRANSIT_HIDE;
+import static android.window.TransitionInfo.TRANSIT_OPEN;
+import static android.window.TransitionInfo.TRANSIT_SHOW;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.os.IBinder;
+import android.os.SystemProperties;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.ITransitionPlayer;
+import android.window.TransitionInfo;
+import android.window.WindowOrganizer;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.util.ArrayList;
+
+/** Plays transition animations */
+public class Transitions extends ITransitionPlayer.Stub {
+    private static final String TAG = "ShellTransitions";
+
+    /** Set to {@code true} to enable shell transitions. */
+    public static final boolean ENABLE_SHELL_TRANSITIONS =
+            SystemProperties.getBoolean("persist.debug.shell_transit", false);
+
+    private final WindowOrganizer mOrganizer;
+    private final TransactionPool mTransactionPool;
+    private final ShellExecutor mMainExecutor;
+    private final ShellExecutor mAnimExecutor;
+
+    /** Keeps track of currently tracked transitions and all the animations associated with each */
+    private final ArrayMap<IBinder, ArrayList<Animator>> mActiveTransitions = new ArrayMap<>();
+
+    Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
+            @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
+        mOrganizer = organizer;
+        mTransactionPool = pool;
+        mMainExecutor = mainExecutor;
+        mAnimExecutor = animExecutor;
+    }
+
+    // TODO(shell-transitions): real animations
+    private void startExampleAnimation(@NonNull IBinder transition, @NonNull SurfaceControl leash,
+            boolean show) {
+        final float end = show ? 1.f : 0.f;
+        final float start = 1.f - end;
+        final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+        final ValueAnimator va = ValueAnimator.ofFloat(start, end);
+        va.setDuration(500);
+        va.addUpdateListener(animation -> {
+            float fraction = animation.getAnimatedFraction();
+            transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
+            transaction.apply();
+        });
+        final Runnable finisher = () -> {
+            transaction.setAlpha(leash, end);
+            transaction.apply();
+            mTransactionPool.release(transaction);
+            mMainExecutor.execute(() -> {
+                mActiveTransitions.get(transition).remove(va);
+                onFinish(transition);
+            });
+        };
+        va.addListener(new Animator.AnimatorListener() {
+            @Override
+            public void onAnimationStart(Animator animation) { }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                finisher.run();
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                finisher.run();
+            }
+
+            @Override
+            public void onAnimationRepeat(Animator animation) { }
+        });
+        mActiveTransitions.get(transition).add(va);
+        mAnimExecutor.execute(va::start);
+    }
+
+    private static boolean isOpeningType(@WindowManager.TransitionType int legacyType) {
+        // TODO(shell-transitions): consider providing and using z-order vs the global type for
+        //                          this determination.
+        return legacyType == WindowManager.TRANSIT_TASK_OPEN
+                || legacyType == WindowManager.TRANSIT_TASK_TO_FRONT
+                || legacyType == WindowManager.TRANSIT_TASK_OPEN_BEHIND
+                || legacyType == WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
+    }
+
+    @Override
+    public void onTransitionReady(@NonNull IBinder transitionToken, TransitionInfo info,
+            @NonNull SurfaceControl.Transaction t) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
+                transitionToken, info);
+        // start task
+        mMainExecutor.execute(() -> {
+            if (!mActiveTransitions.containsKey(transitionToken)) {
+                Slog.e(TAG, "Got transitionReady for non-active transition " + transitionToken
+                        + " expecting one of " + mActiveTransitions.keySet());
+            }
+            if (mActiveTransitions.get(transitionToken) != null) {
+                throw new IllegalStateException("Got a duplicate onTransitionReady call for "
+                        + transitionToken);
+            }
+            mActiveTransitions.put(transitionToken, new ArrayList<>());
+            for (int i = 0; i < info.getChanges().size(); ++i) {
+                final SurfaceControl leash = info.getChanges().get(i).getLeash();
+                final int mode = info.getChanges().get(i).getMode();
+                if (mode == TRANSIT_OPEN || mode == TRANSIT_SHOW) {
+                    t.show(leash);
+                    t.setMatrix(leash, 1, 0, 0, 1);
+                    if (isOpeningType(info.getType())) {
+                        t.setAlpha(leash, 0.f);
+                        startExampleAnimation(transitionToken, leash, true /* show */);
+                    } else {
+                        t.setAlpha(leash, 1.f);
+                    }
+                } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_HIDE) {
+                    if (!isOpeningType(info.getType())) {
+                        startExampleAnimation(transitionToken, leash, false /* show */);
+                    }
+                }
+            }
+            t.apply();
+            onFinish(transitionToken);
+        });
+    }
+
+    @MainThread
+    private void onFinish(IBinder transition) {
+        if (!mActiveTransitions.get(transition).isEmpty()) return;
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+                "Transition animations finished, notifying core %s", transition);
+        mActiveTransitions.remove(transition);
+        mOrganizer.finishTransition(transition, null, null);
+    }
+
+    @Override
+    public void requestStartTransition(int type, @NonNull IBinder transitionToken) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: type=%d %s",
+                type, transitionToken);
+        mMainExecutor.execute(() -> {
+            if (mActiveTransitions.containsKey(transitionToken)) {
+                throw new RuntimeException("Transition already started " + transitionToken);
+            }
+            IBinder transition = mOrganizer.startTransition(type, transitionToken, null /* wct */);
+            mActiveTransitions.put(transition, null);
+        });
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/AnimationThread.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/AnimationThread.java
new file mode 100644
index 0000000..96b9f86
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/AnimationThread.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 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.wm.shell.common;
+
+import static android.os.Process.THREAD_PRIORITY_DISPLAY;
+
+import android.annotation.NonNull;
+import android.os.HandlerThread;
+import android.util.Singleton;
+
+/**
+ * A singleton thread for Shell to run animations on.
+ */
+public class AnimationThread extends HandlerThread {
+    private ShellExecutor mExecutor;
+
+    private AnimationThread() {
+        super("wmshell.anim", THREAD_PRIORITY_DISPLAY);
+    }
+
+    /** Get the singleton instance of this thread */
+    public static AnimationThread instance() {
+        return sAnimationThreadSingleton.get();
+    }
+
+    /**
+     * @return a shared {@link ShellExecutor} associated with this thread
+     * @hide
+     */
+    @NonNull
+    public ShellExecutor getExecutor() {
+        if (mExecutor == null) {
+            mExecutor = new HandlerExecutor(getThreadHandler());
+        }
+        return mExecutor;
+    }
+
+    private static final Singleton<AnimationThread> sAnimationThreadSingleton =
+            new Singleton<AnimationThread>() {
+                @Override
+                protected AnimationThread create() {
+                    final AnimationThread animThread = new AnimationThread();
+                    animThread.start();
+                    return animThread;
+                }
+            };
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
new file mode 100644
index 0000000..cd75840
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 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.wm.shell.common;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+
+/** Executor implementation which is backed by a Handler. */
+public class HandlerExecutor implements ShellExecutor {
+    private final Handler mHandler;
+
+    public HandlerExecutor(@NonNull Handler handler) {
+        mHandler = handler;
+    }
+
+    @Override
+    public void executeDelayed(@NonNull Runnable r, long delayMillis) {
+        if (!mHandler.postDelayed(r, delayMillis)) {
+            throw new RuntimeException(mHandler + " is probably exiting");
+        }
+    }
+
+    @Override
+    public void removeCallbacks(@NonNull Runnable r) {
+        mHandler.removeCallbacks(r);
+    }
+
+    @Override
+    public void execute(@NonNull Runnable command) {
+        if (!mHandler.post(command)) {
+            throw new RuntimeException(mHandler + " is probably exiting");
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
new file mode 100644
index 0000000..aafe240
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 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.wm.shell.common;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Super basic Executor interface that adds support for delayed execution and removing callbacks.
+ * Intended to wrap Handler while better-supporting testing.
+ */
+public interface ShellExecutor extends Executor {
+    /**
+     * See {@link android.os.Handler#postDelayed(Runnable, long)}.
+     */
+    void executeDelayed(Runnable r, long delayMillis);
+
+    /**
+     * See {@link android.os.Handler#removeCallbacks}.
+     */
+    void removeCallbacks(Runnable r);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index e3029e5..a0ce9da 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -28,6 +28,8 @@
     // with those in the framework ProtoLogGroup
     WM_SHELL_TASK_ORG(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM_SHELL),
+    WM_SHELL_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+            Consts.TAG_WM_SHELL),
     TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
 
     private final boolean mEnabled;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 823e0b7..1bc5cea 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -33,7 +33,9 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.TransactionPool;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -55,6 +57,8 @@
 
     ShellTaskOrganizer mOrganizer;
     private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class);
+    private final TransactionPool mTransactionPool = mock(TransactionPool.class);
+    private final ShellExecutor mTestExecutor = mock(ShellExecutor.class);
 
     private class TrackingTaskListener implements ShellTaskOrganizer.TaskListener {
         final ArrayList<RunningTaskInfo> appeared = new ArrayList<>();
@@ -85,7 +89,8 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mOrganizer = new ShellTaskOrganizer(mTaskOrganizerController, mSyncTransactionQueue);
+        mOrganizer = new ShellTaskOrganizer(mTaskOrganizerController, mSyncTransactionQueue,
+                mTransactionPool, mTestExecutor, mTestExecutor);
     }
 
     @Test
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 975265e..155bb6b 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -464,14 +464,6 @@
         "RenderNode.cpp",
         "RenderProperties.cpp",
         "RootRenderNode.cpp",
-        "shader/Shader.cpp",
-        "shader/BitmapShader.cpp",
-        "shader/BlurShader.cpp",
-        "shader/ComposeShader.cpp",
-        "shader/LinearGradientShader.cpp",
-        "shader/RadialGradientShader.cpp",
-        "shader/RuntimeShader.cpp",
-        "shader/SweepGradientShader.cpp",
         "SkiaCanvas.cpp",
         "VectorDrawable.cpp",
     ],
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index a690840..1dbce58 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -42,8 +42,6 @@
 #include <SkTextBlob.h>
 #include <SkVertices.h>
 
-#include <shader/BitmapShader.h>
-
 #include <memory>
 #include <optional>
 #include <utility>
@@ -51,7 +49,6 @@
 namespace android {
 
 using uirenderer::PaintUtils;
-using uirenderer::BitmapShader;
 
 Canvas* Canvas::create_canvas(const SkBitmap& bitmap) {
     return new SkiaCanvas(bitmap);
@@ -684,9 +681,7 @@
     if (paint) {
         pnt = *paint;
     }
-
-    pnt.setShader(sk_ref_sp(new BitmapShader(
-            bitmap.makeImage(), SkTileMode::kClamp, SkTileMode::kClamp, nullptr)));
+    pnt.setShader(bitmap.makeImage()->makeShader());
     auto v = builder.detach();
     apply_looper(&pnt, [&](const SkPaint& p) {
         mCanvas->drawVertices(v, SkBlendMode::kModulate, p);
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index 0f566e4..cd908354 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -23,6 +23,7 @@
 #include "PathParser.h"
 #include "SkColorFilter.h"
 #include "SkImageInfo.h"
+#include "SkShader.h"
 #include "hwui/Paint.h"
 
 #ifdef __ANDROID__
@@ -158,10 +159,10 @@
 
     // Draw path's fill, if fill color or gradient is valid
     bool needsFill = false;
-    Paint paint;
+    SkPaint paint;
     if (properties.getFillGradient() != nullptr) {
         paint.setColor(applyAlpha(SK_ColorBLACK, properties.getFillAlpha()));
-        paint.setShader(sk_sp<Shader>(SkSafeRef(properties.getFillGradient())));
+        paint.setShader(sk_sp<SkShader>(SkSafeRef(properties.getFillGradient())));
         needsFill = true;
     } else if (properties.getFillColor() != SK_ColorTRANSPARENT) {
         paint.setColor(applyAlpha(properties.getFillColor(), properties.getFillAlpha()));
@@ -178,7 +179,7 @@
     bool needsStroke = false;
     if (properties.getStrokeGradient() != nullptr) {
         paint.setColor(applyAlpha(SK_ColorBLACK, properties.getStrokeAlpha()));
-        paint.setShader(sk_sp<Shader>(SkSafeRef(properties.getStrokeGradient())));
+        paint.setShader(sk_sp<SkShader>(SkSafeRef(properties.getStrokeGradient())));
         needsStroke = true;
     } else if (properties.getStrokeColor() != SK_ColorTRANSPARENT) {
         paint.setColor(applyAlpha(properties.getStrokeColor(), properties.getStrokeAlpha()));
diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h
index d4086f1..ac7d41e 100644
--- a/libs/hwui/VectorDrawable.h
+++ b/libs/hwui/VectorDrawable.h
@@ -31,8 +31,8 @@
 #include <SkPath.h>
 #include <SkPathMeasure.h>
 #include <SkRect.h>
+#include <SkShader.h>
 #include <SkSurface.h>
-#include <shader/Shader.h>
 
 #include <cutils/compiler.h>
 #include <stddef.h>
@@ -227,20 +227,20 @@
             strokeGradient = prop.strokeGradient;
             onPropertyChanged();
         }
-        void setFillGradient(Shader* gradient) {
+        void setFillGradient(SkShader* gradient) {
             if (fillGradient.get() != gradient) {
                 fillGradient = sk_ref_sp(gradient);
                 onPropertyChanged();
             }
         }
-        void setStrokeGradient(Shader* gradient) {
+        void setStrokeGradient(SkShader* gradient) {
             if (strokeGradient.get() != gradient) {
                 strokeGradient = sk_ref_sp(gradient);
                 onPropertyChanged();
             }
         }
-        Shader* getFillGradient() const { return fillGradient.get(); }
-        Shader* getStrokeGradient() const { return strokeGradient.get(); }
+        SkShader* getFillGradient() const { return fillGradient.get(); }
+        SkShader* getStrokeGradient() const { return strokeGradient.get(); }
         float getStrokeWidth() const { return mPrimitiveFields.strokeWidth; }
         void setStrokeWidth(float strokeWidth) {
             VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(strokeWidth, strokeWidth);
@@ -320,8 +320,8 @@
             count,
         };
         PrimitiveFields mPrimitiveFields;
-        sk_sp<Shader> fillGradient;
-        sk_sp<Shader> strokeGradient;
+        sk_sp<SkShader> fillGradient;
+        sk_sp<SkShader> strokeGradient;
     };
 
     // Called from UI thread
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index 2001b56..8df2770b 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -73,7 +73,7 @@
 
 static void simplifyPaint(int color, Paint* paint) {
     paint->setColor(color);
-    paint->setShader((sk_sp<uirenderer::Shader>)nullptr);
+    paint->setShader(nullptr);
     paint->setColorFilter(nullptr);
     paint->setLooper(nullptr);
     paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize());
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index 4e2016a..e75e9e7 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -30,8 +30,6 @@
 #include <minikin/FamilyVariant.h>
 #include <minikin/Hyphenator.h>
 
-#include <shader/Shader.h>
-
 namespace android {
 
 class Paint : public SkPaint {
@@ -151,13 +149,8 @@
     // The only respected flags are : [ antialias, dither, filterBitmap ]
     static uint32_t GetSkPaintJavaFlags(const SkPaint&);
     static void SetSkPaintJavaFlags(SkPaint*, uint32_t flags);
-
-    void setShader(sk_sp<uirenderer::Shader> shader);
  
 private:
-
-    using SkPaint::setShader;
-
     SkFont mFont;
     sk_sp<SkDrawLooper> mLooper;
 
@@ -176,7 +169,6 @@
     bool mStrikeThru = false;
     bool mUnderline = false;
     bool mDevKern = false;
-    sk_sp<uirenderer::Shader> mShader;
 };
 
 }  // namespace android
diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp
index 21f60fd..fa2674f 100644
--- a/libs/hwui/hwui/PaintImpl.cpp
+++ b/libs/hwui/hwui/PaintImpl.cpp
@@ -24,8 +24,7 @@
         , mWordSpacing(0)
         , mFontFeatureSettings()
         , mMinikinLocaleListId(0)
-        , mFamilyVariant(minikin::FamilyVariant::DEFAULT)
-        , mShader(nullptr) {
+        , mFamilyVariant(minikin::FamilyVariant::DEFAULT) {
     // SkPaint::antialiasing defaults to false, but
     // SkFont::edging defaults to kAntiAlias. To keep them
     // insync, we manually set the font to kAilas.
@@ -46,8 +45,7 @@
         , mAlign(paint.mAlign)
         , mStrikeThru(paint.mStrikeThru)
         , mUnderline(paint.mUnderline)
-        , mDevKern(paint.mDevKern)
-        , mShader(paint.mShader){}
+        , mDevKern(paint.mDevKern) {}
 
 
 Paint::~Paint() {}
@@ -67,30 +65,9 @@
     mStrikeThru = other.mStrikeThru;
     mUnderline = other.mUnderline;
     mDevKern = other.mDevKern;
-    mShader = other.mShader;
     return *this;
 }
 
-void Paint::setShader(sk_sp<uirenderer::Shader> shader) {
-    if (shader) {
-        // If there is an SkShader compatible shader, apply it
-        sk_sp<SkShader> skShader = shader->asSkShader();
-        if (skShader.get()) {
-            SkPaint::setShader(skShader);
-            SkPaint::setImageFilter(nullptr);
-        } else {
-            // ... otherwise the specified shader can only be represented as an ImageFilter
-            SkPaint::setShader(nullptr);
-            SkPaint::setImageFilter(shader->asSkImageFilter());
-        }
-    } else {
-        // No shader is provided at all, clear out both the SkShader and SkImageFilter slots
-        SkPaint::setShader(nullptr);
-        SkPaint::setImageFilter(nullptr);
-    }
-    mShader = shader;
-}
-
 bool operator==(const Paint& a, const Paint& b) {
     return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) &&
            a.mFont == b.mFont &&
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index f6c8496..3c86b28 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -47,7 +47,6 @@
 #include <minikin/LocaleList.h>
 #include <minikin/Measurement.h>
 #include <minikin/MinikinPaint.h>
-#include <shader/Shader.h>
 #include <unicode/utf16.h>
 
 #include <cassert>
@@ -55,8 +54,6 @@
 #include <memory>
 #include <vector>
 
-using namespace android::uirenderer;
-
 namespace android {
 
 static void getPosTextPath(const SkFont& font, const uint16_t glyphs[], int count,
@@ -745,10 +742,11 @@
         return obj->getFillPath(*src, dst) ? JNI_TRUE : JNI_FALSE;
     }
 
-    static void setShader(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong shaderHandle) {
-        auto* paint = reinterpret_cast<Paint*>(objHandle);
-        auto* shader = reinterpret_cast<Shader*>(shaderHandle);
-        paint->setShader(sk_ref_sp(shader));
+    static jlong setShader(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong shaderHandle) {
+        Paint* obj = reinterpret_cast<Paint*>(objHandle);
+        SkShader* shader = reinterpret_cast<SkShader*>(shaderHandle);
+        obj->setShader(sk_ref_sp(shader));
+        return reinterpret_cast<jlong>(obj->getShader());
     }
 
     static jlong setColorFilter(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong filterHandle) {
@@ -1059,7 +1057,7 @@
     {"nGetStrokeJoin","(J)I", (void*) PaintGlue::getStrokeJoin},
     {"nSetStrokeJoin","(JI)V", (void*) PaintGlue::setStrokeJoin},
     {"nGetFillPath","(JJJ)Z", (void*) PaintGlue::getFillPath},
-    {"nSetShader","(JJ)V", (void*) PaintGlue::setShader},
+    {"nSetShader","(JJ)J", (void*) PaintGlue::setShader},
     {"nSetColorFilter","(JJ)J", (void*) PaintGlue::setColorFilter},
     {"nSetXfermode","(JI)V", (void*) PaintGlue::setXfermode},
     {"nSetPathEffect","(JJ)J", (void*) PaintGlue::setPathEffect},
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 0a194f9..e76aace 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -5,14 +5,6 @@
 #include "SkShader.h"
 #include "SkBlendMode.h"
 #include "include/effects/SkRuntimeEffect.h"
-#include "shader/Shader.h"
-#include "shader/BitmapShader.h"
-#include "shader/BlurShader.h"
-#include "shader/ComposeShader.h"
-#include "shader/LinearGradientShader.h"
-#include "shader/RadialGradientShader.h"
-#include "shader/RuntimeShader.h"
-#include "shader/SweepGradientShader.h"
 
 #include <vector>
 
@@ -58,7 +50,7 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
-static void Shader_safeUnref(Shader* shader) {
+static void Shader_safeUnref(SkShader* shader) {
     SkSafeUnref(shader);
 }
 
@@ -82,15 +74,15 @@
         SkBitmap bitmap;
         image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode);
     }
+    sk_sp<SkShader> shader = image->makeShader(
+            (SkTileMode)tileModeX, (SkTileMode)tileModeY);
+    ThrowIAE_IfNull(env, shader.get());
 
-    auto* shader = new BitmapShader(
-            image,
-            static_cast<SkTileMode>(tileModeX),
-            static_cast<SkTileMode>(tileModeY),
-            matrix
-        );
+    if (matrix) {
+        shader = shader->makeWithLocalMatrix(*matrix);
+    }
 
-    return reinterpret_cast<jlong>(shader);
+    return reinterpret_cast<jlong>(shader.release());
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
@@ -126,18 +118,17 @@
     #error Need to convert float array to SkScalar array before calling the following function.
 #endif
 
-    auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
-    auto* shader = new LinearGradientShader(
-                pts,
-                colors,
-                GraphicsJNI::getNativeColorSpace(colorSpaceHandle),
-                pos,
-                static_cast<SkTileMode>(tileMode),
-                sGradientShaderFlags,
-                matrix
-            );
+    sk_sp<SkShader> shader(SkGradientShader::MakeLinear(pts, &colors[0],
+                GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(),
+                static_cast<SkTileMode>(tileMode), sGradientShaderFlags, nullptr));
+    ThrowIAE_IfNull(env, shader);
 
-    return reinterpret_cast<jlong>(shader);
+    const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+    if (matrix) {
+        shader = shader->makeWithLocalMatrix(*matrix);
+    }
+
+    return reinterpret_cast<jlong>(shader.release());
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
@@ -157,20 +148,17 @@
     #error Need to convert float array to SkScalar array before calling the following function.
 #endif
 
-    auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+    sk_sp<SkShader> shader = SkGradientShader::MakeRadial(center, radius, &colors[0],
+            GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(),
+            static_cast<SkTileMode>(tileMode), sGradientShaderFlags, nullptr);
+    ThrowIAE_IfNull(env, shader);
 
-    auto* shader = new RadialGradientShader(
-                center,
-                radius,
-                colors,
-                GraphicsJNI::getNativeColorSpace(colorSpaceHandle),
-                pos,
-                static_cast<SkTileMode>(tileMode),
-                sGradientShaderFlags,
-                matrix
-            );
+    const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+    if (matrix) {
+        shader = shader->makeWithLocalMatrix(*matrix);
+    }
 
-    return reinterpret_cast<jlong>(shader);
+    return reinterpret_cast<jlong>(shader.release());
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -186,75 +174,54 @@
     #error Need to convert float array to SkScalar array before calling the following function.
 #endif
 
-    auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+    sk_sp<SkShader> shader = SkGradientShader::MakeSweep(x, y, &colors[0],
+            GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(),
+            sGradientShaderFlags, nullptr);
+    ThrowIAE_IfNull(env, shader);
 
-    auto* shader = new SweepGradientShader(
-                x,
-                y,
-                colors,
-                GraphicsJNI::getNativeColorSpace(colorSpaceHandle),
-                pos,
-                sGradientShaderFlags,
-                matrix
-            );
+    const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+    if (matrix) {
+        shader = shader->makeWithLocalMatrix(*matrix);
+    }
 
-    return reinterpret_cast<jlong>(shader);
+    return reinterpret_cast<jlong>(shader.release());
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
 static jlong ComposeShader_create(JNIEnv* env, jobject o, jlong matrixPtr,
         jlong shaderAHandle, jlong shaderBHandle, jint xfermodeHandle) {
-    auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
-    auto* shaderA = reinterpret_cast<Shader*>(shaderAHandle);
-    auto* shaderB = reinterpret_cast<Shader*>(shaderBHandle);
+    const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+    SkShader* shaderA = reinterpret_cast<SkShader *>(shaderAHandle);
+    SkShader* shaderB = reinterpret_cast<SkShader *>(shaderBHandle);
+    SkBlendMode mode = static_cast<SkBlendMode>(xfermodeHandle);
+    sk_sp<SkShader> baseShader(SkShaders::Blend(mode,
+            sk_ref_sp(shaderA), sk_ref_sp(shaderB)));
 
-    auto mode = static_cast<SkBlendMode>(xfermodeHandle);
+    SkShader* shader;
 
-    auto* composeShader = new ComposeShader(
-            *shaderA,
-            *shaderB,
-            mode,
-            matrix
-        );
-
-    return reinterpret_cast<jlong>(composeShader);
-}
-
-///////////////////////////////////////////////////////////////////////////////////////////////
-
-static jlong BlurShader_create(JNIEnv* env , jobject o, jlong matrixPtr, jfloat sigmaX,
-        jfloat sigmaY, jlong shaderHandle, jint edgeTreatment) {
-    auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
-    auto* inputShader = reinterpret_cast<Shader*>(shaderHandle);
-
-    auto* blurShader = new BlurShader(
-                sigmaX,
-                sigmaY,
-                inputShader,
-                static_cast<SkTileMode>(edgeTreatment),
-                matrix
-            );
-    return reinterpret_cast<jlong>(blurShader);
+    if (matrix) {
+        shader = baseShader->makeWithLocalMatrix(*matrix).release();
+    } else {
+        shader = baseShader.release();
+    }
+    return reinterpret_cast<jlong>(shader);
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
 static jlong RuntimeShader_create(JNIEnv* env, jobject, jlong shaderFactory, jlong matrixPtr,
         jbyteArray inputs, jlong colorSpaceHandle, jboolean isOpaque) {
-    auto* effect = reinterpret_cast<SkRuntimeEffect*>(shaderFactory);
+    SkRuntimeEffect* effect = reinterpret_cast<SkRuntimeEffect*>(shaderFactory);
     AutoJavaByteArray arInputs(env, inputs);
 
-    auto data = SkData::MakeWithCopy(arInputs.ptr(), arInputs.length());
-    auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+    sk_sp<SkData> fData;
+    fData = SkData::MakeWithCopy(arInputs.ptr(), arInputs.length());
+    const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+    sk_sp<SkShader> shader = effect->makeShader(fData, nullptr, 0, matrix, isOpaque == JNI_TRUE);
+    ThrowIAE_IfNull(env, shader);
 
-    auto* shader = new RuntimeShader(
-            *effect,
-            std::move(data),
-            isOpaque == JNI_TRUE,
-            matrix
-        );
-    return reinterpret_cast<jlong>(shader);
+    return reinterpret_cast<jlong>(shader.release());
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
@@ -272,8 +239,12 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
+static void Effect_safeUnref(SkRuntimeEffect* effect) {
+    SkSafeUnref(effect);
+}
+
 static jlong RuntimeShader_getNativeFinalizer(JNIEnv*, jobject) {
-    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Shader_safeUnref));
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Effect_safeUnref));
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
@@ -291,10 +262,6 @@
     { "nativeCreate",      "(JJII)J",  (void*)BitmapShader_constructor },
 };
 
-static const JNINativeMethod gBlurShaderMethods[] = {
-    { "nativeCreate",      "(JFFJI)J", (void*)BlurShader_create }
-};
-
 static const JNINativeMethod gLinearGradientMethods[] = {
     { "nativeCreate",     "(JFFFF[J[FIJ)J",  (void*)LinearGradient_create     },
 };
@@ -326,8 +293,6 @@
                                   NELEM(gShaderMethods));
     android::RegisterMethodsOrDie(env, "android/graphics/BitmapShader", gBitmapShaderMethods,
                                   NELEM(gBitmapShaderMethods));
-    android::RegisterMethodsOrDie(env, "android/graphics/BlurShader", gBlurShaderMethods,
-                                  NELEM(gBlurShaderMethods));
     android::RegisterMethodsOrDie(env, "android/graphics/LinearGradient", gLinearGradientMethods,
                                   NELEM(gLinearGradientMethods));
     android::RegisterMethodsOrDie(env, "android/graphics/RadialGradient", gRadialGradientMethods,
diff --git a/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp b/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
index a1adcb3..9cffceb 100644
--- a/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
+++ b/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
@@ -143,13 +143,13 @@
 
 static void updateFullPathFillGradient(JNIEnv*, jobject, jlong pathPtr, jlong fillGradientPtr) {
     VectorDrawable::FullPath* path = reinterpret_cast<VectorDrawable::FullPath*>(pathPtr);
-    auto* fillShader = reinterpret_cast<Shader*>(fillGradientPtr);
+    SkShader* fillShader = reinterpret_cast<SkShader*>(fillGradientPtr);
     path->mutateStagingProperties()->setFillGradient(fillShader);
 }
 
 static void updateFullPathStrokeGradient(JNIEnv*, jobject, jlong pathPtr, jlong strokeGradientPtr) {
     VectorDrawable::FullPath* path = reinterpret_cast<VectorDrawable::FullPath*>(pathPtr);
-    auto* strokeShader = reinterpret_cast<Shader*>(strokeGradientPtr);
+    SkShader* strokeShader = reinterpret_cast<SkShader*>(strokeGradientPtr);
     path->mutateStagingProperties()->setStrokeGradient(strokeShader);
 }
 
diff --git a/libs/hwui/shader/BitmapShader.cpp b/libs/hwui/shader/BitmapShader.cpp
deleted file mode 100644
index fe653e8..0000000
--- a/libs/hwui/shader/BitmapShader.cpp
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "BitmapShader.h"
-
-#include "SkImagePriv.h"
-
-namespace android::uirenderer {
-BitmapShader::BitmapShader(const sk_sp<SkImage>& image, const SkTileMode tileModeX,
-                           const SkTileMode tileModeY, const SkMatrix* matrix)
-        : Shader(matrix), skShader(image->makeShader(tileModeX, tileModeY)) {}
-
-sk_sp<SkShader> BitmapShader::makeSkShader() {
-    return skShader;
-}
-
-BitmapShader::~BitmapShader() {}
-}  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/BitmapShader.h b/libs/hwui/shader/BitmapShader.h
deleted file mode 100644
index ed6a6e6..0000000
--- a/libs/hwui/shader/BitmapShader.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "Shader.h"
-#include "SkShader.h"
-
-namespace android::uirenderer {
-
-/**
- * Shader implementation that renders a Bitmap as either a SkShader or SkImageFilter
- */
-class BitmapShader : public Shader {
-public:
-    BitmapShader(const sk_sp<SkImage>& image, const SkTileMode tileModeX,
-                 const SkTileMode tileModeY, const SkMatrix* matrix);
-    ~BitmapShader() override;
-
-protected:
-    sk_sp<SkShader> makeSkShader() override;
-
-private:
-    sk_sp<SkShader> skShader;
-};
-}  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/ComposeShader.cpp b/libs/hwui/shader/ComposeShader.cpp
deleted file mode 100644
index 3765489..0000000
--- a/libs/hwui/shader/ComposeShader.cpp
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "ComposeShader.h"
-
-#include "SkImageFilters.h"
-#include "SkShader.h"
-
-namespace android::uirenderer {
-
-ComposeShader::ComposeShader(Shader& shaderA, Shader& shaderB, const SkBlendMode blendMode,
-                             const SkMatrix* matrix)
-        : Shader(matrix) {
-    // If both Shaders can be represented as SkShaders then use those, if not
-    // create an SkImageFilter from both Shaders and create the equivalent SkImageFilter
-    sk_sp<SkShader> skShaderA = shaderA.asSkShader();
-    sk_sp<SkShader> skShaderB = shaderB.asSkShader();
-    if (skShaderA.get() && skShaderB.get()) {
-        skShader = SkShaders::Blend(blendMode, skShaderA, skShaderB);
-        skImageFilter = nullptr;
-    } else {
-        sk_sp<SkImageFilter> skImageFilterA = shaderA.asSkImageFilter();
-        sk_sp<SkImageFilter> skImageFilterB = shaderB.asSkImageFilter();
-        skShader = nullptr;
-        skImageFilter = SkImageFilters::Xfermode(blendMode, skImageFilterA, skImageFilterB);
-    }
-}
-
-sk_sp<SkShader> ComposeShader::makeSkShader() {
-    return skShader;
-}
-
-sk_sp<SkImageFilter> ComposeShader::makeSkImageFilter() {
-    return skImageFilter;
-}
-
-ComposeShader::~ComposeShader() {}
-}  // namespace android::uirenderer
diff --git a/libs/hwui/shader/ComposeShader.h b/libs/hwui/shader/ComposeShader.h
deleted file mode 100644
index a246b52..0000000
--- a/libs/hwui/shader/ComposeShader.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "Shader.h"
-#include "SkShader.h"
-
-namespace android::uirenderer {
-
-/**
- * Shader implementation that can composite 2 Shaders together with the specified blend mode.
- * This implementation can appropriately convert the composed result to either an SkShader or
- * SkImageFilter depending on the inputs
- */
-class ComposeShader : public Shader {
-public:
-    ComposeShader(Shader& shaderA, Shader& shaderB, const SkBlendMode blendMode,
-                  const SkMatrix* matrix);
-    ~ComposeShader() override;
-
-protected:
-    sk_sp<SkShader> makeSkShader() override;
-    sk_sp<SkImageFilter> makeSkImageFilter() override;
-
-private:
-    sk_sp<SkShader> skShader;
-    sk_sp<SkImageFilter> skImageFilter;
-};
-}  // namespace android::uirenderer
diff --git a/libs/hwui/shader/LinearGradientShader.cpp b/libs/hwui/shader/LinearGradientShader.cpp
deleted file mode 100644
index 868fa44..0000000
--- a/libs/hwui/shader/LinearGradientShader.cpp
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "LinearGradientShader.h"
-
-#include <vector>
-
-#include "SkGradientShader.h"
-
-namespace android::uirenderer {
-
-LinearGradientShader::LinearGradientShader(const SkPoint pts[2],
-                                           const std::vector<SkColor4f>& colors,
-                                           sk_sp<SkColorSpace> colorspace, const SkScalar pos[],
-                                           const SkTileMode tileMode, const uint32_t shaderFlags,
-                                           const SkMatrix* matrix)
-        : Shader(matrix)
-        , skShader(SkGradientShader::MakeLinear(pts, colors.data(), colorspace, pos, colors.size(),
-                                                tileMode, shaderFlags, nullptr)) {}
-
-sk_sp<SkShader> LinearGradientShader::makeSkShader() {
-    return skShader;
-}
-
-LinearGradientShader::~LinearGradientShader() {}
-}  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/LinearGradientShader.h b/libs/hwui/shader/LinearGradientShader.h
deleted file mode 100644
index 596f4e0..0000000
--- a/libs/hwui/shader/LinearGradientShader.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "Shader.h"
-#include "SkShader.h"
-
-namespace android::uirenderer {
-
-/**
- * Shader implementation that renders a color ramp of colors to either as either SkShader or
- * SkImageFilter
- */
-class LinearGradientShader : public Shader {
-public:
-    LinearGradientShader(const SkPoint pts[2], const std::vector<SkColor4f>& colors,
-                         sk_sp<SkColorSpace> colorspace, const SkScalar pos[],
-                         const SkTileMode tileMode, const uint32_t shaderFlags,
-                         const SkMatrix* matrix);
-    ~LinearGradientShader() override;
-
-protected:
-    sk_sp<SkShader> makeSkShader() override;
-
-private:
-    sk_sp<SkShader> skShader;
-};
-}  // namespace android::uirenderer
diff --git a/libs/hwui/shader/RadialGradientShader.cpp b/libs/hwui/shader/RadialGradientShader.cpp
deleted file mode 100644
index 21ff56f..0000000
--- a/libs/hwui/shader/RadialGradientShader.cpp
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include "RadialGradientShader.h"
-
-#include <vector>
-
-#include "SkGradientShader.h"
-
-namespace android::uirenderer {
-
-RadialGradientShader::RadialGradientShader(const SkPoint& center, const float radius,
-                                           const std::vector<SkColor4f>& colors,
-                                           sk_sp<SkColorSpace> colorspace, const SkScalar pos[],
-                                           const SkTileMode tileMode, const uint32_t shaderFlags,
-                                           const SkMatrix* matrix)
-        : Shader(matrix)
-        , skShader(SkGradientShader::MakeRadial(center, radius, colors.data(), colorspace, pos,
-                                                colors.size(), tileMode, shaderFlags, nullptr)) {}
-
-sk_sp<SkShader> RadialGradientShader::makeSkShader() {
-    return skShader;
-}
-
-RadialGradientShader::~RadialGradientShader() {}
-}  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/RadialGradientShader.h b/libs/hwui/shader/RadialGradientShader.h
deleted file mode 100644
index 9a2ff13..0000000
--- a/libs/hwui/shader/RadialGradientShader.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "Shader.h"
-#include "SkShader.h"
-
-namespace android::uirenderer {
-
-/**
- * Shader implementation that renders a color ramp from the center outward to either as either
- * a SkShader or SkImageFilter
- */
-class RadialGradientShader : public Shader {
-public:
-    RadialGradientShader(const SkPoint& center, const float radius,
-                         const std::vector<SkColor4f>& colors, sk_sp<SkColorSpace> colorSpace,
-                         const SkScalar pos[], const SkTileMode tileMode, const uint32_t shaderFlags,
-                         const SkMatrix* matrix);
-    ~RadialGradientShader() override;
-
-protected:
-    sk_sp<SkShader> makeSkShader() override;
-
-private:
-    sk_sp<SkShader> skShader;
-};
-}  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/RuntimeShader.cpp b/libs/hwui/shader/RuntimeShader.cpp
deleted file mode 100644
index dd0b698..0000000
--- a/libs/hwui/shader/RuntimeShader.cpp
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "RuntimeShader.h"
-
-#include "SkShader.h"
-#include "include/effects/SkRuntimeEffect.h"
-
-namespace android::uirenderer {
-
-RuntimeShader::RuntimeShader(SkRuntimeEffect& effect, sk_sp<SkData> data, bool isOpaque,
-                             const SkMatrix* matrix)
-        : Shader(nullptr)
-        ,  // Explicitly passing null as RuntimeShader is created with the
-           // matrix directly
-        skShader(effect.makeShader(std::move(data), nullptr, 0, matrix, isOpaque)) {}
-
-sk_sp<SkShader> RuntimeShader::makeSkShader() {
-    return skShader;
-}
-
-RuntimeShader::~RuntimeShader() {}
-}  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/RuntimeShader.h b/libs/hwui/shader/RuntimeShader.h
deleted file mode 100644
index 7fe0b02..0000000
--- a/libs/hwui/shader/RuntimeShader.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "Shader.h"
-#include "SkShader.h"
-#include "include/effects/SkRuntimeEffect.h"
-
-namespace android::uirenderer {
-
-/**
- * RuntimeShader implementation that can map to either a SkShader or SkImageFilter
- */
-class RuntimeShader : public Shader {
-public:
-    RuntimeShader(SkRuntimeEffect& effect, sk_sp<SkData> data, bool isOpaque,
-                  const SkMatrix* matrix);
-    ~RuntimeShader() override;
-
-protected:
-    sk_sp<SkShader> makeSkShader() override;
-
-private:
-    sk_sp<SkShader> skShader;
-};
-}  // namespace android::uirenderer
diff --git a/libs/hwui/shader/Shader.cpp b/libs/hwui/shader/Shader.cpp
deleted file mode 100644
index 45123dd..0000000
--- a/libs/hwui/shader/Shader.cpp
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "Shader.h"
-
-#include "SkImageFilters.h"
-#include "SkPaint.h"
-#include "SkRefCnt.h"
-
-namespace android::uirenderer {
-
-Shader::Shader(const SkMatrix* matrix)
-        : localMatrix(matrix ? *matrix : SkMatrix::I())
-        , skShader(nullptr)
-        , skImageFilter(nullptr) {}
-
-Shader::~Shader() {}
-
-sk_sp<SkShader> Shader::asSkShader() {
-    // If we already have created a shader with these parameters just return the existing
-    // shader we have already created
-    if (!this->skShader.get()) {
-        this->skShader = makeSkShader();
-        if (this->skShader.get()) {
-            if (!localMatrix.isIdentity()) {
-                this->skShader = this->skShader->makeWithLocalMatrix(localMatrix);
-            }
-        }
-    }
-    return this->skShader;
-}
-
-/**
- * By default return null as we cannot convert all visual effects to SkShader instances
- */
-sk_sp<SkShader> Shader::makeSkShader() {
-    return nullptr;
-}
-
-sk_sp<SkImageFilter> Shader::asSkImageFilter() {
-    // If we already have created an ImageFilter with these parameters just return the existing
-    // ImageFilter we have already created
-    if (!this->skImageFilter.get()) {
-        // Attempt to create an SkImageFilter from the current Shader implementation
-        this->skImageFilter = makeSkImageFilter();
-        if (this->skImageFilter) {
-            if (!localMatrix.isIdentity()) {
-                // If we have created an SkImageFilter and we have a transformation, wrap
-                // the created SkImageFilter to apply the given matrix
-                this->skImageFilter = SkImageFilters::MatrixTransform(
-                    localMatrix, kMedium_SkFilterQuality, this->skImageFilter);
-            }
-        } else {
-            // Otherwise if no SkImageFilter implementation is provided, create one from
-            // the result of asSkShader. Note the matrix is already applied to the shader in
-            // this case so just convert it to an SkImageFilter using SkImageFilters::Paint
-            SkPaint paint;
-            paint.setShader(asSkShader());
-            sk_sp<SkImageFilter> paintFilter = SkImageFilters::Paint(paint);
-            this->skImageFilter = SkImageFilters::Xfermode(SkBlendMode::kDstIn,
-                    std::move(paintFilter));
-        }
-    }
-    return this->skImageFilter;
-}
-
-/**
- * By default return null for subclasses to implement. If there is not a direct SkImageFilter
- * conversion
- */
-sk_sp<SkImageFilter> Shader::makeSkImageFilter() {
-    return nullptr;
-}
-}  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/Shader.h b/libs/hwui/shader/Shader.h
deleted file mode 100644
index 6403e11..0000000
--- a/libs/hwui/shader/Shader.h
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "SkImageFilter.h"
-#include "SkShader.h"
-#include "SkPaint.h"
-#include "SkRefCnt.h"
-
-class SkMatrix;
-
-namespace android::uirenderer {
-
-/**
- * Shader class that can optionally wrap an SkShader or SkImageFilter depending
- * on the implementation
- */
-class Shader: public SkRefCnt {
-public:
-    /**
-     * Creates a Shader instance with an optional transformation matrix. The transformation matrix
-     * is copied internally and ownership is unchanged. It is the responsibility of the caller to
-     * deallocate it appropriately.
-     * @param matrix Optional matrix to transform the underlying SkShader or SkImageFilter
-     */
-    Shader(const SkMatrix* matrix);
-    virtual ~Shader();
-
-    /**
-     * Create an SkShader from the current Shader instance or return a previously
-     * created instance. This can be null if no SkShader could be created from this
-     * Shader instance.
-     */
-    sk_sp<SkShader> asSkShader();
-
-    /**
-     * Create an SkImageFilter from the current Shader instance or return a previously
-     * created instance. Unlike asSkShader, this method cannot return null.
-     */
-    sk_sp<SkImageFilter> asSkImageFilter();
-
-protected:
-    /**
-     * Create a new SkShader instance based on this Shader instance
-     */
-    virtual sk_sp<SkShader> makeSkShader();
-
-    /**
-     * Create a new SkImageFilter instance based on this Shader instance. If no SkImageFilter
-     * can be created then return nullptr
-     */
-    virtual sk_sp<SkImageFilter> makeSkImageFilter();
-
-private:
-    /**
-     * Optional matrix transform
-     */
-    const SkMatrix localMatrix;
-
-    /**
-     * Cached SkShader instance to be returned on subsequent queries
-     */
-    sk_sp<SkShader> skShader;
-
-    /**
-     * Cached SkImageFilter instance to be returned on subsequent queries
-     */
-    sk_sp<SkImageFilter> skImageFilter;
-};
-}  // namespace android::uirenderer
diff --git a/libs/hwui/shader/SweepGradientShader.cpp b/libs/hwui/shader/SweepGradientShader.cpp
deleted file mode 100644
index 3b1f37f..0000000
--- a/libs/hwui/shader/SweepGradientShader.cpp
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "SweepGradientShader.h"
-
-#include <vector>
-
-#include "SkGradientShader.h"
-#include "SkImageFilters.h"
-
-namespace android::uirenderer {
-
-SweepGradientShader::SweepGradientShader(float x, float y, const std::vector<SkColor4f>& colors,
-                                         const sk_sp<SkColorSpace>& colorspace, const SkScalar pos[],
-                                         const uint32_t shaderFlags, const SkMatrix* matrix)
-        : Shader(matrix)
-        , skShader(SkGradientShader::MakeSweep(x, y, colors.data(), colorspace, pos, colors.size(),
-                                               shaderFlags, nullptr)) {}
-
-sk_sp<SkShader> SweepGradientShader::makeSkShader() {
-    return skShader;
-}
-
-SweepGradientShader::~SweepGradientShader() {}
-}  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/SweepGradientShader.h b/libs/hwui/shader/SweepGradientShader.h
deleted file mode 100644
index dad3ef0..0000000
--- a/libs/hwui/shader/SweepGradientShader.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "Shader.h"
-#include "SkShader.h"
-
-namespace android::uirenderer {
-
-/**
- * Shader implementation that renders a color ramp clockwise such that the start and end colors
- * are visible at 3 o'clock. This handles converting to either an SkShader or SkImageFilter
- */
-class SweepGradientShader : public Shader {
-public:
-    SweepGradientShader(float x, float y, const std::vector<SkColor4f>& colors,
-                        const sk_sp<SkColorSpace>& colorspace, const SkScalar pos[],
-                        const uint32_t shaderFlags, const SkMatrix* matrix);
-    virtual ~SweepGradientShader() override;
-
-protected:
-    virtual sk_sp<SkShader> makeSkShader() override;
-
-private:
-    sk_sp<SkShader> skShader;
-};
-}  // namespace android::uirenderer
diff --git a/libs/hwui/tests/common/scenes/BitmapShaders.cpp b/libs/hwui/tests/common/scenes/BitmapShaders.cpp
index e2c1651..c4067af 100644
--- a/libs/hwui/tests/common/scenes/BitmapShaders.cpp
+++ b/libs/hwui/tests/common/scenes/BitmapShaders.cpp
@@ -18,7 +18,6 @@
 #include "hwui/Paint.h"
 #include "TestSceneBase.h"
 #include "tests/common/BitmapAllocationTestUtils.h"
-#include <shader/BitmapShader.h>
 #include "utils/Color.h"
 
 class BitmapShaders;
@@ -46,24 +45,15 @@
                 });
 
         Paint paint;
-        sk_sp<BitmapShader> bitmapShader = sk_make_sp<BitmapShader>(
-                    hwuiBitmap->makeImage(),
-                    SkTileMode::kRepeat,
-                    SkTileMode::kRepeat,
-                    nullptr
-                );
-
         sk_sp<SkImage> image = hwuiBitmap->makeImage();
-        paint.setShader(std::move(bitmapShader));
+        sk_sp<SkShader> repeatShader =
+                image->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat);
+        paint.setShader(std::move(repeatShader));
         canvas.drawRoundRect(0, 0, 500, 500, 50.0f, 50.0f, paint);
 
-        sk_sp<BitmapShader> mirrorBitmapShader = sk_make_sp<BitmapShader>(
-                    image,
-                    SkTileMode::kMirror,
-                    SkTileMode::kMirror,
-                    nullptr
-                );
-        paint.setShader(std::move(mirrorBitmapShader));
+        sk_sp<SkShader> mirrorShader =
+                image->makeShader(SkTileMode::kMirror, SkTileMode::kMirror);
+        paint.setShader(std::move(mirrorShader));
         canvas.drawRoundRect(0, 600, 500, 1100, 50.0f, 50.0f, paint);
     }
 
diff --git a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
index d37bc3c..5886ea3 100644
--- a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
+++ b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
@@ -20,10 +20,6 @@
 #include <SkGradientShader.h>
 #include <SkImagePriv.h>
 #include <ui/PixelFormat.h>
-#include <shader/BitmapShader.h>
-#include <shader/LinearGradientShader.h>
-#include <shader/RadialGradientShader.h>
-#include <shader/ComposeShader.h>
 
 class HwBitmapInCompositeShader;
 
@@ -54,41 +50,20 @@
             pixels[4000 + 4 * i + 3] = 255;
         }
         buffer->unlock();
-
-        sk_sp<BitmapShader> bitmapShader = sk_make_sp<BitmapShader>(
-                Bitmap::createFrom(
-                        buffer->toAHardwareBuffer(),
-                        SkColorSpace::MakeSRGB()
-                )->makeImage(),
-                SkTileMode::kClamp,
-                SkTileMode::kClamp,
-                nullptr
-        );
+        sk_sp<Bitmap> hardwareBitmap(Bitmap::createFrom(buffer->toAHardwareBuffer(),
+                                                        SkColorSpace::MakeSRGB()));
+        sk_sp<SkShader> hardwareShader(createBitmapShader(*hardwareBitmap));
 
         SkPoint center;
         center.set(50, 50);
+        SkColor colors[2];
+        colors[0] = Color::Black;
+        colors[1] = Color::White;
+        sk_sp<SkShader> gradientShader = SkGradientShader::MakeRadial(
+                center, 50, colors, nullptr, 2, SkTileMode::kRepeat);
 
-        std::vector<SkColor4f> vColors(2);
-        vColors[0] = SkColors::kBlack;
-        vColors[1] = SkColors::kWhite;
-
-        sk_sp<RadialGradientShader> radialShader = sk_make_sp<RadialGradientShader>(
-                center,
-                50,
-                vColors,
-                SkColorSpace::MakeSRGB(),
-                nullptr,
-                SkTileMode::kRepeat,
-                0,
-                nullptr
-            );
-
-        sk_sp<ComposeShader> compositeShader = sk_make_sp<ComposeShader>(
-                    *bitmapShader.get(),
-                    *radialShader.get(),
-                    SkBlendMode::kDstATop,
-                    nullptr
-                );
+        sk_sp<SkShader> compositeShader(
+                SkShaders::Blend(SkBlendMode::kDstATop, hardwareShader, gradientShader));
 
         Paint paint;
         paint.setShader(std::move(compositeShader));
diff --git a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
index 76e39de..a9449b6 100644
--- a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
@@ -17,8 +17,7 @@
 #include "TestSceneBase.h"
 #include "tests/common/TestListViewSceneBase.h"
 #include "hwui/Paint.h"
-#include "SkColor.h"
-#include <shader/LinearGradientShader.h>
+#include <SkGradientShader.h>
 
 class ListOfFadedTextAnimation;
 
@@ -43,26 +42,15 @@
         pts[0].set(0, 0);
         pts[1].set(0, 1);
 
+        SkColor colors[2] = {Color::Black, Color::Transparent};
+        sk_sp<SkShader> s(
+                SkGradientShader::MakeLinear(pts, colors, NULL, 2, SkTileMode::kClamp));
+
         SkMatrix matrix;
         matrix.setScale(1, length);
         matrix.postRotate(-90);
-
-        std::vector<SkColor4f> vColors(2);
-        vColors[0] = SkColors::kBlack;
-        vColors[1] = SkColors::kTransparent;
-
-        sk_sp<LinearGradientShader> linearGradientShader = sk_make_sp<LinearGradientShader>(
-                    pts,
-                    vColors,
-                    SkColorSpace::MakeSRGB(),
-                    nullptr,
-                    SkTileMode::kClamp,
-                    0,
-                    &matrix
-                );
-
         Paint fadingPaint;
-        fadingPaint.setShader(linearGradientShader);
+        fadingPaint.setShader(s->makeWithLocalMatrix(matrix));
         fadingPaint.setBlendMode(SkBlendMode::kDstOut);
         canvas.drawRect(0, 0, length, itemHeight, fadingPaint);
         canvas.restore();
diff --git a/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp b/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp
index bdc157f..a0bc5aa 100644
--- a/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp
@@ -17,7 +17,7 @@
 #include "TestSceneBase.h"
 
 #include <SkColorMatrixFilter.h>
-#include <shader/LinearGradientShader.h>
+#include <SkGradientShader.h>
 
 class SimpleColorMatrixAnimation;
 
@@ -65,12 +65,9 @@
                     // enough renderer might apply it directly to the paint color)
                     float pos[] = {0, 1};
                     SkPoint pts[] = {SkPoint::Make(0, 0), SkPoint::Make(width, height)};
-                    std::vector<SkColor4f> colors(2);
-                    colors[0] = SkColor4f::FromColor(Color::DeepPurple_500);
-                    colors[1] = SkColor4f::FromColor(Color::DeepOrange_500);
-                    paint.setShader(sk_make_sp<LinearGradientShader>(
-                            pts, colors, SkColorSpace::MakeSRGB(), pos, SkTileMode::kClamp,
-                            0, nullptr));
+                    SkColor colors[2] = {Color::DeepPurple_500, Color::DeepOrange_500};
+                    paint.setShader(SkGradientShader::MakeLinear(pts, colors, pos, 2,
+                                                                 SkTileMode::kClamp));
 
                     // overdraw several times to emphasize shader cost
                     for (int i = 0; i < 10; i++) {
diff --git a/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp b/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp
index 9a15c9d..57a260c 100644
--- a/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp
@@ -17,7 +17,6 @@
 #include "TestSceneBase.h"
 
 #include <SkGradientShader.h>
-#include <shader/LinearGradientShader.h>
 
 class SimpleGradientAnimation;
 
@@ -56,24 +55,9 @@
                     // overdraw several times to emphasize shader cost
                     for (int i = 0; i < 10; i++) {
                         // use i%2 start position to pick 2 color combo with black in it
-                        std::vector<SkColor4f> vColors(2);
-                        vColors[0] = ((i % 2) == 0) ?
-                                SkColor4f::FromColor(Color::Transparent) :
-                                SkColor4f::FromColor(Color::Black);
-                        vColors[1] = (((i + 1) % 2) == 0) ?
-                                SkColor4f::FromColor(Color::Black) :
-                                SkColor4f::FromColor(Color::Cyan_500);
-
-                        sk_sp<LinearGradientShader> gradient = sk_make_sp<LinearGradientShader>(
-                                    pts,
-                                    vColors,
-                                    SkColorSpace::MakeSRGB(),
-                                    pos,
-                                    SkTileMode::kClamp,
-                                    0,
-                                    nullptr
-                                );
-                        paint.setShader(gradient);
+                        SkColor colors[3] = {Color::Transparent, Color::Black, Color::Cyan_500};
+                        paint.setShader(SkGradientShader::MakeLinear(pts, colors + (i % 2), pos, 2,
+                                                                     SkTileMode::kClamp));
                         canvas.drawRect(i, i, width, height, paint);
                     }
                 });
diff --git a/libs/hwui/tests/unit/VectorDrawableTests.cpp b/libs/hwui/tests/unit/VectorDrawableTests.cpp
index 5e56b26..6d4c574 100644
--- a/libs/hwui/tests/unit/VectorDrawableTests.cpp
+++ b/libs/hwui/tests/unit/VectorDrawableTests.cpp
@@ -17,14 +17,9 @@
 #include <gtest/gtest.h>
 
 #include "PathParser.h"
-#include "GraphicsJNI.h"
-#include "SkGradientShader.h"
-#include "SkShader.h"
 #include "VectorDrawable.h"
 #include "utils/MathUtils.h"
 #include "utils/VectorDrawableUtils.h"
-#include <shader/Shader.h>
-#include <shader/LinearGradientShader.h>
 
 #include <functional>
 
@@ -400,21 +395,7 @@
     bitmap.allocN32Pixels(5, 5, false);
     SkCanvas canvas(bitmap);
 
-    SkPoint pts[2];
-    pts[0].set(0, 0);
-    pts[1].set(0, 0);
-
-    std::vector<SkColor4f> colors(2);
-    colors[0] = SkColors::kBlack;
-    colors[1] = SkColors::kBlack;
-
-    sk_sp<LinearGradientShader> shader = sk_sp(new LinearGradientShader(pts,
-            colors,
-            SkColorSpace::MakeSRGB(),
-            nullptr,
-            SkTileMode::kClamp,
-            SkGradientShader::kInterpolateColorsInPremul_Flag,
-            nullptr));
+    sk_sp<SkShader> shader = SkShaders::Color(SK_ColorBLACK);
     // Initial ref count is 1
     EXPECT_TRUE(shader->unique());
 
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index 9aa0c87..46bd221 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -16,6 +16,8 @@
 
 package android.location;
 
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -586,6 +588,11 @@
     }
 
     /** @hide */
+    public long getElapsedRealtimeMillis() {
+        return NANOSECONDS.toMillis(getElapsedRealtimeNanos());
+    }
+
+    /** @hide */
     public long getElapsedRealtimeAgeNanos(long referenceRealtimeNs) {
         return referenceRealtimeNs - mElapsedRealtimeNanos;
     }
@@ -595,6 +602,11 @@
         return getElapsedRealtimeAgeNanos(SystemClock.elapsedRealtimeNanos());
     }
 
+    /** @hide */
+    public long getElapsedRealtimeAgeMillis() {
+        return NANOSECONDS.toMillis(getElapsedRealtimeAgeNanos());
+    }
+
     /**
      * Set the time of this fix, in elapsed real-time since system boot.
      *
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 6e597b2..ff00409 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -89,6 +89,16 @@
 public class LocationManager {
 
     /**
+     * For apps targeting Android S and above, location clients may receive historical locations
+     * (from before the present time) under some circumstances.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
+    public static final long DELIVER_HISTORICAL_LOCATIONS = 73144566L;
+
+    /**
      * For apps targeting Android R and above, {@link #getProvider(String)} will no longer throw any
      * security exceptions.
      *
@@ -1256,13 +1266,15 @@
      * arguments. The same listener may be used across multiple providers with different requests
      * for each provider.
      *
-     * <p>It may take a while to receive the first location update. If an immediate location is
-     * required, applications may use the {@link #getLastKnownLocation(String)} method.
+     * <p>It may take some time to receive the first location update depending on the conditions the
+     * device finds itself in. In order to take advantage of cached locations, application may
+     * consider using {@link #getLastKnownLocation(String)} or {@link #getCurrentLocation(String,
+     * LocationRequest, CancellationSignal, Executor, Consumer)} instead.
      *
      * <p>See {@link LocationRequest} documentation for an explanation of various request parameters
      * and how they can affect the received locations.
      *
-     * <p> If your application wants to passively observe location updates from any provider, then
+     * <p>If your application wants to passively observe location updates from all providers, then
      * use the {@link #PASSIVE_PROVIDER}. This provider does not turn on or modify active location
      * providers, so you do not need to be as careful about minimum time and minimum distance
      * parameters. However, if your application performs heavy work on a location update (such as
@@ -1271,13 +1283,20 @@
      *
      * <p>In case the provider you have selected is disabled, location updates will cease, and a
      * provider availability update will be sent. As soon as the provider is enabled again, another
-     * provider availability update will be sent and location updates will immediately resume.
+     * provider availability update will be sent and location updates will resume.
      *
-     * <p> When location callbacks are invoked, the system will hold a wakelock on your
+     * <p>When location callbacks are invoked, the system will hold a wakelock on your
      * application's behalf for some period of time, but not indefinitely. If your application
      * requires a long running wakelock within the location callback, you should acquire it
      * yourself.
      *
+     * <p>Spamming location requests is a drain on system resources, and the system has preventative
+     * measures in place to ensure that this behavior will never result in more locations than could
+     * be achieved with a single location request with an equivalent interval that is left in place
+     * the whole time. As part of this amelioration, applications that target Android S and above
+     * may receive cached or historical locations through their listener. These locations will never
+     * be older than the interval of the location request.
+     *
      * <p>To unregister for location updates, use {@link #removeUpdates(LocationListener)}.
      *
      * @param provider a provider listed by {@link #getAllProviders()}
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index 0521b10..c57794f 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -27,6 +27,8 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Parcel;
@@ -45,6 +47,17 @@
 public final class LocationRequest implements Parcelable {
 
     /**
+     * For apps targeting Android S and above, all LocationRequest objects marked as low power will
+     * throw exceptions if the caller does not have the LOCATION_HARDWARE permission, instead of
+     * silently dropping the low power part of the request.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
+    public static final long LOW_POWER_EXCEPTIONS = 168936375L;
+
+    /**
      * Represents a passive only request. Such a request will not trigger any active locations or
      * power usage itself, but may receive locations generated in response to other requests.
      *
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index 24dacc4..3af2e17 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -472,7 +472,8 @@
     /**
      * @hide
      */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(publicAlternatives = "Check equality of {@link #getSessionToken() tokens}"
+            + "instead.")
     public boolean controlsSameSession(MediaController other) {
         if (other == null) return false;
         return mSessionBinder.asBinder() == other.getSessionBinder().asBinder();
diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt
index 4d0a01c..3cc1e42 100644
--- a/non-updatable-api/current.txt
+++ b/non-updatable-api/current.txt
@@ -21,6 +21,7 @@
     field public static final String ACTIVITY_RECOGNITION = "android.permission.ACTIVITY_RECOGNITION";
     field public static final String ADD_VOICEMAIL = "com.android.voicemail.permission.ADD_VOICEMAIL";
     field public static final String ANSWER_PHONE_CALLS = "android.permission.ANSWER_PHONE_CALLS";
+    field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA";
     field public static final String BATTERY_STATS = "android.permission.BATTERY_STATS";
     field public static final String BIND_ACCESSIBILITY_SERVICE = "android.permission.BIND_ACCESSIBILITY_SERVICE";
     field public static final String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET";
@@ -129,6 +130,7 @@
     field public static final String RECEIVE_SMS = "android.permission.RECEIVE_SMS";
     field public static final String RECEIVE_WAP_PUSH = "android.permission.RECEIVE_WAP_PUSH";
     field public static final String RECORD_AUDIO = "android.permission.RECORD_AUDIO";
+    field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO";
     field public static final String REORDER_TASKS = "android.permission.REORDER_TASKS";
     field public static final String REQUEST_COMPANION_RUN_IN_BACKGROUND = "android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND";
     field public static final String REQUEST_COMPANION_USE_DATA_IN_BACKGROUND = "android.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND";
@@ -12235,6 +12237,7 @@
     field public static final String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct";
     field public static final String FEATURE_WIFI_PASSPOINT = "android.hardware.wifi.passpoint";
     field public static final String FEATURE_WIFI_RTT = "android.hardware.wifi.rtt";
+    field public static final int FLAG_PERMISSION_ALLOWLIST_ROLE = 8; // 0x8
     field public static final int FLAG_PERMISSION_WHITELIST_INSTALLER = 2; // 0x2
     field public static final int FLAG_PERMISSION_WHITELIST_SYSTEM = 1; // 0x1
     field public static final int FLAG_PERMISSION_WHITELIST_UPGRADE = 4; // 0x4
@@ -12344,6 +12347,7 @@
     field public static final int FLAG_HARD_RESTRICTED = 4; // 0x4
     field public static final int FLAG_IMMUTABLY_RESTRICTED = 16; // 0x10
     field public static final int FLAG_INSTALLED = 1073741824; // 0x40000000
+    field public static final int FLAG_INSTALLER_EXEMPT_IGNORED = 32; // 0x20
     field public static final int FLAG_SOFT_RESTRICTED = 8; // 0x8
     field public static final int PROTECTION_DANGEROUS = 1; // 0x1
     field public static final int PROTECTION_FLAG_APPOP = 64; // 0x40
diff --git a/non-updatable-api/system-current.txt b/non-updatable-api/system-current.txt
index 684968c..7a64d28 100644
--- a/non-updatable-api/system-current.txt
+++ b/non-updatable-api/system-current.txt
@@ -2097,6 +2097,7 @@
     field public static final int FLAG_PERMISSION_ONE_TIME = 65536; // 0x10000
     field public static final int FLAG_PERMISSION_POLICY_FIXED = 4; // 0x4
     field public static final int FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT = 2048; // 0x800
+    field public static final int FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT = 262144; // 0x40000
     field public static final int FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT = 4096; // 0x1000
     field public static final int FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT = 8192; // 0x2000
     field public static final int FLAG_PERMISSION_REVIEW_REQUIRED = 64; // 0x40
@@ -9580,16 +9581,12 @@
     method public int getTimeoutSeconds();
     method public boolean isEnabled();
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallForwardingInfo> CREATOR;
-    field public static final int ERROR_FDN_CHECK_FAILURE = 2; // 0x2
-    field public static final int ERROR_NOT_SUPPORTED = 3; // 0x3
-    field public static final int ERROR_UNKNOWN = 1; // 0x1
     field public static final int REASON_ALL = 4; // 0x4
     field public static final int REASON_ALL_CONDITIONAL = 5; // 0x5
     field public static final int REASON_BUSY = 1; // 0x1
     field public static final int REASON_NOT_REACHABLE = 3; // 0x3
     field public static final int REASON_NO_REPLY = 2; // 0x2
     field public static final int REASON_UNCONDITIONAL = 0; // 0x0
-    field public static final int SUCCESS = 0; // 0x0
   }
 
   public final class CallQuality implements android.os.Parcelable {
@@ -10332,7 +10329,7 @@
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setAllowedNetworkTypes(long);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallForwarding(@NonNull android.telephony.CallForwardingInfo, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallWaitingStatus(boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallWaitingEnabled(boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCarrierDataEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setCarrierRestrictionRules(@NonNull android.telephony.CarrierRestrictionRules);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataActivationState(int);
@@ -10431,6 +10428,10 @@
   public static interface TelephonyManager.CallForwardingInfoCallback {
     method public void onCallForwardingInfoAvailable(@NonNull android.telephony.CallForwardingInfo);
     method public void onError(int);
+    field public static final int RESULT_ERROR_FDN_CHECK_FAILURE = 2; // 0x2
+    field public static final int RESULT_ERROR_NOT_SUPPORTED = 3; // 0x3
+    field public static final int RESULT_ERROR_UNKNOWN = 1; // 0x1
+    field public static final int RESULT_SUCCESS = 0; // 0x0
   }
 
   public final class UiccAccessRule implements android.os.Parcelable {
diff --git a/packages/CarSystemUI/res/drawable/system_bar_background_pill.xml b/packages/CarSystemUI/res/drawable/system_bar_background_pill.xml
index 1b12eb4..51d2b9d 100644
--- a/packages/CarSystemUI/res/drawable/system_bar_background_pill.xml
+++ b/packages/CarSystemUI/res/drawable/system_bar_background_pill.xml
@@ -14,9 +14,20 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-       android:shape="rectangle">
-  <solid android:color="@color/system_bar_background_pill_color"/>
-  <corners android:radius="30dp"/>
-</shape>
\ No newline at end of file
+<layer-list
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <item>
+        <aapt:attr name="android:drawable">
+            <shape android:shape="rectangle">
+                <solid android:color="@color/system_bar_background_pill_color"/>
+                <corners android:radius="30dp"/>
+            </shape>
+        </aapt:attr>
+    </item>
+    <item>
+        <aapt:attr name="android:drawable">
+            <ripple android:color="?android:attr/colorControlHighlight"/>
+        </aapt:attr>
+    </item>
+</layer-list>
diff --git a/packages/CarSystemUI/res/values/styles.xml b/packages/CarSystemUI/res/values/styles.xml
index f242db0..f5de2fd 100644
--- a/packages/CarSystemUI/res/values/styles.xml
+++ b/packages/CarSystemUI/res/values/styles.xml
@@ -49,5 +49,6 @@
         <item name="android:padding">@dimen/system_bar_button_padding</item>
         <item name="android:gravity">center</item>
         <item name="android:background">?android:attr/selectableItemBackground</item>
+        <item name="unselectedAlpha">0.56</item>
     </style>
 </resources>
\ No newline at end of file
diff --git a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
index e05bd3c..d3aa977 100644
--- a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
+++ b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
@@ -21,8 +21,6 @@
 
 import static androidx.test.ext.truth.location.LocationSubject.assertThat;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import android.content.Context;
 import android.location.Criteria;
 import android.location.Location;
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index f5f58ef..a927997 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -326,6 +326,9 @@
     <!-- Permission needed for CTS test - DisplayTest -->
     <uses-permission android:name="android.permission.OVERRIDE_DISPLAY_MODE_REQUESTS" />
 
+    <!-- Permission needed for CTS test - TimeManagerTest -->
+    <uses-permission android:name="android.permission.MANAGE_TIME_AND_ZONE_DETECTION" />
+
     <application android:label="@string/app_label"
                 android:theme="@android:style/Theme.DeviceDefault.DayNight"
                 android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4ba757f..2427d360 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2829,4 +2829,9 @@
     <string name="media_output_dialog_connect_failed">Couldn\'t connect. Try again.</string>
     <!-- Title for pairing item [CHAR LIMIT=60] -->
     <string name="media_output_dialog_pairing_new">Pair new device</string>
+
+    <!-- Label for clip data when copying the build number off QS [CHAR LIMIT=NONE]-->
+    <string name="build_number_clip_data_label">Build number</string>
+    <!-- Text to display when copying the build number off QS [CHAR LIMIT=NONE]-->
+    <string name="build_number_copy_toast">Build number copied to clipboard.</string>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index f4c865e..4657b06 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -75,8 +75,10 @@
             Key.HAS_SEEN_BUBBLES_EDUCATION,
             Key.HAS_SEEN_BUBBLES_MANAGE_EDUCATION,
             Key.HAS_SEEN_REVERSE_BOTTOM_SHEET,
-            Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT
+            Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT,
+            Key.HAS_SEEN_PRIORITY_ONBOARDING
     })
+    // TODO: annotate these with their types so {@link PrefsCommandLine} can know how to set them
     public @interface Key {
         @Deprecated
         String OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME = "OverviewLastStackTaskActiveTime";
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 1ebe648..c4a305e 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -86,6 +86,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.qs.SecureSetting;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
 
@@ -121,6 +122,7 @@
     private final TunerService mTunerService;
     private DisplayManager.DisplayListener mDisplayListener;
     private CameraAvailabilityListener mCameraListener;
+    private final UserTracker mUserTracker;
 
     //TODO: These are piecemeal being updated to Points for now to support non-square rounded
     // corners. for now it is only supposed when reading the intrinsic size from the drawables with
@@ -198,11 +200,13 @@
     public ScreenDecorations(Context context,
             @Main Handler handler,
             BroadcastDispatcher broadcastDispatcher,
-            TunerService tunerService) {
+            TunerService tunerService,
+            UserTracker userTracker) {
         super(context);
         mMainHandler = handler;
         mBroadcastDispatcher = broadcastDispatcher;
         mTunerService = tunerService;
+        mUserTracker = userTracker;
     }
 
     @Override
@@ -306,7 +310,8 @@
             // Watch color inversion and invert the overlay as needed.
             if (mColorInversionSetting == null) {
                 mColorInversionSetting = new SecureSetting(mContext, mHandler,
-                        Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED) {
+                        Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
+                        mUserTracker.getUserId()) {
                     @Override
                     protected void handleValueChanged(int value, boolean observedChange) {
                         updateColorInversion(value);
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
index 658f46e..2f0fd99 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.controls.controller
 
-import android.app.ActivityManager
 import android.content.ComponentName
 import android.content.Context
 import android.os.IBinder
@@ -30,6 +29,7 @@
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.concurrency.DelayableExecutor
 import dagger.Lazy
 import java.util.concurrent.atomic.AtomicBoolean
@@ -40,7 +40,8 @@
 open class ControlsBindingControllerImpl @Inject constructor(
     private val context: Context,
     @Background private val backgroundExecutor: DelayableExecutor,
-    private val lazyController: Lazy<ControlsController>
+    private val lazyController: Lazy<ControlsController>,
+    userTracker: UserTracker
 ) : ControlsBindingController {
 
     companion object {
@@ -56,7 +57,7 @@
         }
     }
 
-    private var currentUser = UserHandle.of(ActivityManager.getCurrentUser())
+    private var currentUser = userTracker.userHandle
 
     override val currentUserId: Int
         get() = currentUser.identifier
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index 495872f..d3d24be 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.controls.controller
 
-import android.app.ActivityManager
 import android.app.PendingIntent
 import android.app.backup.BackupManager
 import android.content.BroadcastReceiver
@@ -46,6 +45,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.globalactions.GlobalActionsDialog
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.concurrency.DelayableExecutor
 import java.io.FileDescriptor
 import java.io.PrintWriter
@@ -56,14 +56,15 @@
 
 @SysUISingleton
 class ControlsControllerImpl @Inject constructor (
-        private val context: Context,
-        @Background private val executor: DelayableExecutor,
-        private val uiController: ControlsUiController,
-        private val bindingController: ControlsBindingController,
-        private val listingController: ControlsListingController,
-        private val broadcastDispatcher: BroadcastDispatcher,
-        optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
-        dumpManager: DumpManager
+    private val context: Context,
+    @Background private val executor: DelayableExecutor,
+    private val uiController: ControlsUiController,
+    private val bindingController: ControlsBindingController,
+    private val listingController: ControlsListingController,
+    private val broadcastDispatcher: BroadcastDispatcher,
+    optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
+    dumpManager: DumpManager,
+    userTracker: UserTracker
 ) : Dumpable, ControlsController {
 
     companion object {
@@ -85,7 +86,7 @@
     private var seedingInProgress = false
     private val seedingCallbacks = mutableListOf<Consumer<Boolean>>()
 
-    private var currentUser = UserHandle.of(ActivityManager.getCurrentUser())
+    private var currentUser = userTracker.userHandle
     override val currentUserId
         get() = currentUser.identifier
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
index 0d4439f..2d76ff2 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.controls.management
 
-import android.app.ActivityManager
 import android.content.ComponentName
 import android.content.Context
 import android.content.pm.ServiceInfo
@@ -29,6 +28,7 @@
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.UserTracker
 import java.util.concurrent.Executor
 import java.util.concurrent.atomic.AtomicInteger
 import javax.inject.Inject
@@ -56,14 +56,16 @@
 class ControlsListingControllerImpl @VisibleForTesting constructor(
     private val context: Context,
     @Background private val backgroundExecutor: Executor,
-    private val serviceListingBuilder: (Context) -> ServiceListing
+    private val serviceListingBuilder: (Context) -> ServiceListing,
+    userTracker: UserTracker
 ) : ControlsListingController {
 
     @Inject
-    constructor(context: Context, executor: Executor): this(
+    constructor(context: Context, executor: Executor, userTracker: UserTracker): this(
             context,
             executor,
-            ::createServiceListing
+            ::createServiceListing,
+            userTracker
     )
 
     private var serviceListing = serviceListingBuilder(context)
@@ -78,7 +80,7 @@
     private var availableServices = emptyList<ServiceInfo>()
     private var userChangeInProgress = AtomicInteger(0)
 
-    override var currentUserId = ActivityManager.getCurrentUser()
+    override var currentUserId = userTracker.userId
         private set
 
     private val serviceListingCallback = ServiceListing.Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index 0fbd73b..f56e6cd 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -16,25 +16,23 @@
 
 package com.android.systemui.privacy
 
-import android.app.ActivityManager
 import android.app.AppOpsManager
-import android.content.BroadcastReceiver
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
+import android.content.pm.UserInfo
 import android.os.UserHandle
-import android.os.UserManager
 import android.provider.DeviceConfig
 import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
 import com.android.systemui.Dumpable
 import com.android.systemui.appops.AppOpItem
 import com.android.systemui.appops.AppOpsController
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.DeviceConfigProxy
 import com.android.systemui.util.concurrency.DelayableExecutor
 import java.io.FileDescriptor
@@ -48,9 +46,8 @@
     private val appOpsController: AppOpsController,
     @Main uiExecutor: DelayableExecutor,
     @Background private val bgExecutor: Executor,
-    private val broadcastDispatcher: BroadcastDispatcher,
     private val deviceConfigProxy: DeviceConfigProxy,
-    private val userManager: UserManager,
+    private val userTracker: UserTracker,
     dumpManager: DumpManager
 ) : Dumpable {
 
@@ -153,13 +150,16 @@
     }
 
     @VisibleForTesting
-    internal var userSwitcherReceiver = Receiver()
-        set(value) {
-            unregisterReceiver()
-            field = value
-            if (listening) registerReceiver()
+    internal var userTrackerCallback = object : UserTracker.Callback {
+        override fun onUserChanged(newUser: Int, userContext: Context) {
+            update(true)
         }
 
+        override fun onProfilesChanged(profiles: List<UserInfo>) {
+            update(true)
+        }
+    }
+
     init {
         deviceConfigProxy.addOnPropertiesChangedListener(
                 DeviceConfig.NAMESPACE_PRIVACY,
@@ -168,20 +168,18 @@
         dumpManager.registerDumpable(TAG, this)
     }
 
-    private fun unregisterReceiver() {
-        broadcastDispatcher.unregisterReceiver(userSwitcherReceiver)
+    private fun unregisterListener() {
+        userTracker.removeCallback(userTrackerCallback)
     }
 
     private fun registerReceiver() {
-        broadcastDispatcher.registerReceiver(userSwitcherReceiver, intentFilter,
-                null /* handler */, UserHandle.ALL)
+        userTracker.addCallback(userTrackerCallback, bgExecutor)
     }
 
     private fun update(updateUsers: Boolean) {
         bgExecutor.execute {
             if (updateUsers) {
-                val currentUser = ActivityManager.getCurrentUser()
-                currentUserIds = userManager.getProfiles(currentUser).map { it.id }
+                currentUserIds = userTracker.userProfiles.map { it.id }
             }
             updateListAndNotifyChanges.run()
         }
@@ -206,7 +204,7 @@
             update(true)
         } else {
             appOpsController.removeCallback(OPS, cb)
-            unregisterReceiver()
+            unregisterListener()
             // Make sure that we remove all indicators and notify listeners if we are not
             // listening anymore due to indicators being disabled
             update(false)
@@ -275,14 +273,6 @@
         fun onFlagMicCameraChanged(flag: Boolean) {}
     }
 
-    internal inner class Receiver : BroadcastReceiver() {
-        override fun onReceive(context: Context, intent: Intent) {
-            if (intentFilter.hasAction(intent.action)) {
-                update(true)
-            }
-        }
-    }
-
     private class NotifyChangesToCallback(
         private val callback: Callback?,
         private val list: List<PrivacyItem>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index 6e4ab9a..84563a0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -20,6 +20,8 @@
 
 import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
 
+import android.content.ClipData;
+import android.content.ClipboardManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Configuration;
@@ -34,6 +36,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -57,6 +60,7 @@
 import com.android.systemui.R.dimen;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.qs.TouchAnimator.Builder;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.MultiUserSwitch;
 import com.android.systemui.statusbar.phone.SettingsButton;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -75,6 +79,7 @@
     private final ActivityStarter mActivityStarter;
     private final UserInfoController mUserInfoController;
     private final DeviceProvisionedController mDeviceProvisionedController;
+    private final UserTracker mUserTracker;
     private SettingsButton mSettingsButton;
     protected View mSettingsContainer;
     private PageIndicator mPageIndicator;
@@ -115,11 +120,12 @@
     @Inject
     public QSFooterImpl(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
             ActivityStarter activityStarter, UserInfoController userInfoController,
-            DeviceProvisionedController deviceProvisionedController) {
+            DeviceProvisionedController deviceProvisionedController, UserTracker userTracker) {
         super(context, attrs);
         mActivityStarter = activityStarter;
         mUserInfoController = userInfoController;
         mDeviceProvisionedController = deviceProvisionedController;
+        mUserTracker = userTracker;
     }
 
     @VisibleForTesting
@@ -127,7 +133,8 @@
         this(context, attrs,
                 Dependency.get(ActivityStarter.class),
                 Dependency.get(UserInfoController.class),
-                Dependency.get(DeviceProvisionedController.class));
+                Dependency.get(DeviceProvisionedController.class),
+                Dependency.get(UserTracker.class));
     }
 
     @Override
@@ -150,6 +157,19 @@
         mActionsContainer = findViewById(R.id.qs_footer_actions_container);
         mEditContainer = findViewById(R.id.qs_footer_actions_edit_container);
         mBuildText = findViewById(R.id.build);
+        mBuildText.setOnLongClickListener(view -> {
+            CharSequence buildText = mBuildText.getText();
+            if (!TextUtils.isEmpty(buildText)) {
+                ClipboardManager service =
+                        mUserTracker.getUserContext().getSystemService(ClipboardManager.class);
+                String label = mContext.getString(R.string.build_number_clip_data_label);
+                service.setPrimaryClip(ClipData.newPlainText(label, buildText));
+                Toast.makeText(mContext, R.string.build_number_copy_toast, Toast.LENGTH_SHORT)
+                        .show();
+                return true;
+            }
+            return false;
+        });
 
         // RenderThread is doing more harm than good when touching the header (to expand quick
         // settings), so disable it for this view
@@ -176,6 +196,7 @@
             mBuildText.setSelected(true);
             mShouldShowBuildText = true;
         } else {
+            mBuildText.setText(null);
             mShouldShowBuildText = false;
             mBuildText.setSelected(false);
         }
@@ -317,12 +338,14 @@
         mMultiUserSwitch.setClickable(mMultiUserSwitch.getVisibility() == View.VISIBLE);
         mEdit.setClickable(mEdit.getVisibility() == View.VISIBLE);
         mSettingsButton.setClickable(mSettingsButton.getVisibility() == View.VISIBLE);
+        mBuildText.setLongClickable(mBuildText.getVisibility() == View.VISIBLE);
     }
 
     private void updateVisibilities() {
         mSettingsContainer.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
         mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
-                TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE);
+                TunerService.isTunerEnabled(mContext, mUserTracker.getUserHandle()) ? View.VISIBLE
+                        : View.INVISIBLE);
         final boolean isDemo = UserManager.isDeviceInDemoMode(mContext);
         mMultiUserSwitch.setVisibility(showUserSwitcher() ? View.VISIBLE : View.INVISIBLE);
         mEditContainer.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
@@ -376,15 +399,16 @@
                             : MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH);
             if (mSettingsButton.isTunerClick()) {
                 mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
-                    if (TunerService.isTunerEnabled(mContext)) {
-                        TunerService.showResetRequest(mContext, () -> {
-                            // Relaunch settings so that the tuner disappears.
-                            startSettingsActivity();
-                        });
+                    if (TunerService.isTunerEnabled(mContext, mUserTracker.getUserHandle())) {
+                        TunerService.showResetRequest(mContext, mUserTracker.getUserHandle(),
+                                () -> {
+                                    // Relaunch settings so that the tuner disappears.
+                                    startSettingsActivity();
+                                });
                     } else {
                         Toast.makeText(getContext(), R.string.tuner_toast,
                                 Toast.LENGTH_LONG).show();
-                        TunerService.setTunerEnabled(mContext, true);
+                        TunerService.setTunerEnabled(mContext, mUserTracker.getUserHandle(), true);
                     }
                     startSettingsActivity();
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index 290ab85..000fd1c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -30,6 +30,7 @@
     void openPanels();
     Context getContext();
     Context getUserContext();
+    int getUserId();
     UiEventLogger getUiEventLogger();
     Collection<QSTile> getTiles();
     void addCallback(Callback callback);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index ae925d1..fdc0a60e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -25,7 +25,6 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.graphics.PointF;
 import android.metrics.LogMaker;
 import android.os.Bundle;
 import android.os.Handler;
@@ -57,6 +56,7 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.settings.BrightnessController;
 import com.android.systemui.settings.ToggleSliderView;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener;
 import com.android.systemui.tuner.TunerService;
@@ -114,6 +114,7 @@
     private final QSLogger mQSLogger;
     protected final UiEventLogger mUiEventLogger;
     protected QSTileHost mHost;
+    private final UserTracker mUserTracker;
 
     @Nullable
     protected QSSecurityFooter mSecurityFooter;
@@ -157,7 +158,8 @@
             BroadcastDispatcher broadcastDispatcher,
             QSLogger qsLogger,
             MediaHost mediaHost,
-            UiEventLogger uiEventLogger
+            UiEventLogger uiEventLogger,
+            UserTracker userTracker
     ) {
         super(context, attrs);
         mUsingMediaPlayer = useQsMediaPlayer(context);
@@ -173,6 +175,7 @@
         mDumpManager = dumpManager;
         mBroadcastDispatcher = broadcastDispatcher;
         mUiEventLogger = uiEventLogger;
+        mUserTracker = userTracker;
 
         setOrientation(VERTICAL);
 
@@ -221,7 +224,7 @@
     }
 
     protected void addSecurityFooter() {
-        mSecurityFooter = new QSSecurityFooter(this, mContext);
+        mSecurityFooter = new QSSecurityFooter(this, mContext, mUserTracker);
     }
 
     protected void addViewsAboveTiles() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index afc5be4e..0891972 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -15,7 +15,6 @@
  */
 package com.android.systemui.qs;
 
-import android.app.ActivityManager;
 import android.app.AlertDialog;
 import android.app.admin.DevicePolicyEventLogger;
 import android.content.Context;
@@ -45,6 +44,7 @@
 import com.android.systemui.FontSizeUtils;
 import com.android.systemui.R;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.SecurityController;
 
@@ -61,8 +61,7 @@
     private final SecurityController mSecurityController;
     private final ActivityStarter mActivityStarter;
     private final Handler mMainHandler;
-
-    private final UserManager mUm;
+    private final UserTracker mUserTracker;
 
     private AlertDialog mDialog;
     private QSTileHost mHost;
@@ -73,7 +72,7 @@
     private int mFooterTextId;
     private int mFooterIconId;
 
-    public QSSecurityFooter(QSPanel qsPanel, Context context) {
+    public QSSecurityFooter(QSPanel qsPanel, Context context, UserTracker userTracker) {
         mRootView = LayoutInflater.from(context)
                 .inflate(R.layout.quick_settings_footer, qsPanel, false);
         mRootView.setOnClickListener(this);
@@ -85,7 +84,7 @@
         mActivityStarter = Dependency.get(ActivityStarter.class);
         mSecurityController = Dependency.get(SecurityController.class);
         mHandler = new H(Dependency.get(Dependency.BG_LOOPER));
-        mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        mUserTracker = userTracker;
     }
 
     public void setHostEnvironment(QSTileHost host) {
@@ -138,7 +137,7 @@
 
     private void handleRefreshState() {
         final boolean isDeviceManaged = mSecurityController.isDeviceManaged();
-        final UserInfo currentUser = mUm.getUserInfo(ActivityManager.getCurrentUser());
+        final UserInfo currentUser = mUserTracker.getUserInfo();
         final boolean isDemoDevice = UserManager.isDeviceInDemoMode(mContext) && currentUser != null
                 && currentUser.isDemo();
         final boolean hasWorkProfile = mSecurityController.hasWorkProfile();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 9a63a56..0d0d012 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -14,7 +14,6 @@
 
 package com.android.systemui.qs;
 
-import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -49,6 +48,7 @@
 import com.android.systemui.qs.external.TileLifecycleManager;
 import com.android.systemui.qs.external.TileServices;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.phone.AutoTileManager;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -99,6 +99,7 @@
     private int mCurrentUser;
     private final Optional<StatusBar> mStatusBarOptional;
     private Context mUserContext;
+    private UserTracker mUserTracker;
 
     @Inject
     public QSTileHost(Context context,
@@ -113,7 +114,8 @@
             BroadcastDispatcher broadcastDispatcher,
             Optional<StatusBar> statusBarOptional,
             QSLogger qsLogger,
-            UiEventLogger uiEventLogger) {
+            UiEventLogger uiEventLogger,
+            UserTracker userTracker) {
         mIconController = iconController;
         mContext = context;
         mUserContext = context;
@@ -125,12 +127,13 @@
         mBroadcastDispatcher = broadcastDispatcher;
 
         mInstanceIdSequence = new InstanceIdSequence(MAX_QS_INSTANCE_ID);
-        mServices = new TileServices(this, bgLooper, mBroadcastDispatcher);
+        mServices = new TileServices(this, bgLooper, mBroadcastDispatcher, userTracker);
         mStatusBarOptional = statusBarOptional;
 
         mQsFactories.add(defaultFactory);
         pluginManager.addPluginListener(this, QSFactory.class, true);
         mDumpManager.registerDumpable(TAG, this);
+        mUserTracker = userTracker;
 
         mainHandler.post(() -> {
             // This is technically a hack to avoid circular dependency of
@@ -230,6 +233,11 @@
     }
 
     @Override
+    public int getUserId() {
+        return mCurrentUser;
+    }
+
+    @Override
     public TileServices getTileServices() {
         return mServices;
     }
@@ -248,9 +256,9 @@
             newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
         }
         final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
-        int currentUser = ActivityManager.getCurrentUser();
+        int currentUser = mUserTracker.getUserId();
         if (currentUser != mCurrentUser) {
-            mUserContext = mContext.createContextAsUser(UserHandle.of(currentUser), 0);
+            mUserContext = mUserTracker.getUserContext();
             if (mAutoTiles != null) {
                 mAutoTiles.changeUser(UserHandle.of(currentUser));
             }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index affb7b9..ea036f6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -38,6 +38,7 @@
 import com.android.systemui.plugins.qs.QSTile.State;
 import com.android.systemui.qs.customize.QSCustomizer;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
 
@@ -70,9 +71,11 @@
             BroadcastDispatcher broadcastDispatcher,
             QSLogger qsLogger,
             MediaHost mediaHost,
-            UiEventLogger uiEventLogger
+            UiEventLogger uiEventLogger,
+            UserTracker userTracker
     ) {
-        super(context, attrs, dumpManager, broadcastDispatcher, qsLogger, mediaHost, uiEventLogger);
+        super(context, attrs, dumpManager, broadcastDispatcher, qsLogger, mediaHost, uiEventLogger,
+                userTracker);
         sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
         applyBottomMargin((View) mRegularTileLayout);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 8fec5a2..544249a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -20,7 +20,6 @@
 import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
 
 import android.annotation.ColorInt;
-import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.content.Context;
 import android.content.Intent;
@@ -76,6 +75,7 @@
 import com.android.systemui.privacy.PrivacyItemController;
 import com.android.systemui.qs.QSDetail.Callback;
 import com.android.systemui.qs.carrier.QSCarrierGroup;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
@@ -156,6 +156,7 @@
     private RingerModeTracker mRingerModeTracker;
     private DemoModeController mDemoModeController;
     private DemoMode mDemoModeReceiver;
+    private UserTracker mUserTracker;
     private boolean mAllIndicatorsEnabled;
     private boolean mMicCameraIndicatorsEnabled;
 
@@ -212,7 +213,8 @@
             StatusBarIconController statusBarIconController,
             ActivityStarter activityStarter, PrivacyItemController privacyItemController,
             CommandQueue commandQueue, RingerModeTracker ringerModeTracker,
-            UiEventLogger uiEventLogger, DemoModeController demoModeController) {
+            UiEventLogger uiEventLogger, DemoModeController demoModeController,
+            UserTracker userTracker) {
         super(context, attrs);
         mAlarmController = nextAlarmController;
         mZenController = zenModeController;
@@ -225,6 +227,7 @@
         mRingerModeTracker = ringerModeTracker;
         mUiEventLogger = uiEventLogger;
         mDemoModeController = demoModeController;
+        mUserTracker = userTracker;
     }
 
     @Override
@@ -723,7 +726,7 @@
             return "";
         }
         String skeleton = android.text.format.DateFormat
-                .is24HourFormat(mContext, ActivityManager.getCurrentUser()) ? "EHm" : "Ehma";
+                .is24HourFormat(mContext, mUserTracker.getUserId()) ? "EHm" : "Ehma";
         String pattern = android.text.format.DateFormat
                 .getBestDateTimePattern(Locale.getDefault(), skeleton);
         return android.text.format.DateFormat.format(pattern, info.getTriggerTime()).toString();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java b/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
index 65d81505..3ee3e11 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.qs;
 
-import android.app.ActivityManager;
 import android.content.Context;
 import android.database.ContentObserver;
 import android.os.Handler;
@@ -37,10 +36,6 @@
 
     protected abstract void handleValueChanged(int value, boolean observedChange);
 
-    protected SecureSetting(Context context, Handler handler, String settingName) {
-        this(context, handler, settingName, ActivityManager.getCurrentUser());
-    }
-
     public SecureSetting(Context context, Handler handler, String settingName, int userId) {
         super(handler);
         mContext = context;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index bffeb3e..e049025 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -607,6 +607,12 @@
             for (int i = 0; i < childCount; i++) {
                 final View child = parent.getChildAt(i);
                 final ViewHolder holder = parent.getChildViewHolder(child);
+                // Do not draw background for the holder that's currently being dragged
+                if (holder == mCurrentDrag) {
+                    continue;
+                }
+                // Do not draw background for holders before the edit index (header and current
+                // tiles)
                 if (holder.getAdapterPosition() == 0 ||
                         holder.getAdapterPosition() < mEditIndex && !(child instanceof TextView)) {
                     continue;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index 73c6504..b795a5f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -17,7 +17,6 @@
 package com.android.systemui.qs.customize;
 
 import android.Manifest.permission;
-import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -40,6 +39,7 @@
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.qs.external.CustomTile;
 import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.leak.GarbageMonitor;
 
 import java.util.ArrayList;
@@ -58,16 +58,18 @@
     private final Executor mMainExecutor;
     private final Executor mBgExecutor;
     private final Context mContext;
+    private final UserTracker mUserTracker;
     private TileStateListener mListener;
 
     private boolean mFinished;
 
     @Inject
-    public TileQueryHelper(Context context,
+    public TileQueryHelper(Context context, UserTracker userTracker,
             @Main Executor mainExecutor, @Background Executor bgExecutor) {
         mContext = context;
         mMainExecutor = mainExecutor;
         mBgExecutor = bgExecutor;
+        mUserTracker = userTracker;
     }
 
     public void setListener(TileStateListener listener) {
@@ -207,7 +209,7 @@
             Collection<QSTile> params = host.getTiles();
             PackageManager pm = mContext.getPackageManager();
             List<ResolveInfo> services = pm.queryIntentServicesAsUser(
-                    new Intent(TileService.ACTION_QS_TILE), 0, ActivityManager.getCurrentUser());
+                    new Intent(TileService.ACTION_QS_TILE), 0, mUserTracker.getUserId());
             String stockTiles = mContext.getString(R.string.quick_settings_tiles_stock);
 
             for (ResolveInfo info : services) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 19c7b6c..6e28cd8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -18,7 +18,6 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
 
-import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -304,8 +303,7 @@
     }
 
     private Intent resolveIntent(Intent i) {
-        ResolveInfo result = mContext.getPackageManager().resolveActivityAsUser(i, 0,
-                ActivityManager.getCurrentUser());
+        ResolveInfo result = mContext.getPackageManager().resolveActivityAsUser(i, 0, mUser);
         return result != null ? new Intent(TileService.ACTION_QS_TILE_PREFERENCES)
                 .setClassName(result.activityInfo.packageName, result.activityInfo.name) : null;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
index cfa8fb6..7e76e57 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
@@ -15,7 +15,6 @@
  */
 package com.android.systemui.qs.external;
 
-import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -26,7 +25,6 @@
 import android.net.Uri;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.UserHandle;
 import android.service.quicksettings.IQSTileService;
 import android.service.quicksettings.Tile;
 import android.service.quicksettings.TileService;
@@ -36,6 +34,7 @@
 
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener;
+import com.android.systemui.settings.UserTracker;
 
 import java.util.List;
 import java.util.Objects;
@@ -60,6 +59,7 @@
     private final TileServices mServices;
     private final TileLifecycleManager mStateManager;
     private final Handler mHandler;
+    private final UserTracker mUserTracker;
     private boolean mBindRequested;
     private boolean mBindAllowed;
     private boolean mBound;
@@ -73,25 +73,26 @@
     private boolean mStarted = false;
 
     TileServiceManager(TileServices tileServices, Handler handler, ComponentName component,
-            Tile tile, BroadcastDispatcher broadcastDispatcher) {
-        this(tileServices, handler, new TileLifecycleManager(handler,
+            Tile tile, BroadcastDispatcher broadcastDispatcher, UserTracker userTracker) {
+        this(tileServices, handler, userTracker, new TileLifecycleManager(handler,
                 tileServices.getContext(), tileServices, tile, new Intent().setComponent(component),
-                new UserHandle(ActivityManager.getCurrentUser()), broadcastDispatcher));
+                userTracker.getUserHandle(), broadcastDispatcher));
     }
 
     @VisibleForTesting
-    TileServiceManager(TileServices tileServices, Handler handler,
+    TileServiceManager(TileServices tileServices, Handler handler, UserTracker userTracker,
             TileLifecycleManager tileLifecycleManager) {
         mServices = tileServices;
         mHandler = handler;
         mStateManager = tileLifecycleManager;
+        mUserTracker = userTracker;
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
         filter.addDataScheme("package");
         Context context = mServices.getContext();
-        context.registerReceiverAsUser(mUninstallReceiver,
-                new UserHandle(ActivityManager.getCurrentUser()), filter, null, mHandler);
+        context.registerReceiverAsUser(mUninstallReceiver, userTracker.getUserHandle(), filter,
+                null, mHandler);
     }
 
     boolean isLifecycleStarted() {
@@ -279,7 +280,7 @@
                 queryIntent.setPackage(pkgName);
                 PackageManager pm = context.getPackageManager();
                 List<ResolveInfo> services = pm.queryIntentServicesAsUser(
-                        queryIntent, 0, ActivityManager.getCurrentUser());
+                        queryIntent, 0, mUserTracker.getUserId());
                 for (ResolveInfo info : services) {
                     if (Objects.equals(info.serviceInfo.packageName, component.getPackageName())
                             && Objects.equals(info.serviceInfo.name, component.getClassName())) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index 2863d08..35cf2a1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -39,6 +39,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -62,15 +63,18 @@
     private final Handler mMainHandler;
     private final QSTileHost mHost;
     private final BroadcastDispatcher mBroadcastDispatcher;
+    private final UserTracker mUserTracker;
 
     private int mMaxBound = DEFAULT_MAX_BOUND;
 
-    public TileServices(QSTileHost host, Looper looper, BroadcastDispatcher broadcastDispatcher) {
+    public TileServices(QSTileHost host, Looper looper, BroadcastDispatcher broadcastDispatcher,
+            UserTracker userTracker) {
         mHost = host;
         mContext = mHost.getContext();
         mBroadcastDispatcher = broadcastDispatcher;
         mHandler = new Handler(looper);
         mMainHandler = new Handler(Looper.getMainLooper());
+        mUserTracker = userTracker;
         mBroadcastDispatcher.registerReceiver(
                 mRequestListeningReceiver,
                 new IntentFilter(TileService.ACTION_REQUEST_LISTENING),
@@ -104,7 +108,7 @@
     protected TileServiceManager onCreateTileService(ComponentName component, Tile tile,
             BroadcastDispatcher broadcastDispatcher) {
         return new TileServiceManager(this, mHandler, component, tile,
-                broadcastDispatcher);
+                broadcastDispatcher, mUserTracker);
     }
 
     public void freeService(CustomTile tile, TileServiceManager service) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index dfd7e2c..5a81676 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -31,7 +31,6 @@
 
 import android.annotation.CallSuper;
 import android.annotation.NonNull;
-import android.app.ActivityManager;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
@@ -521,9 +520,9 @@
 
     protected void checkIfRestrictionEnforcedByAdminOnly(State state, String userRestriction) {
         EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext,
-                userRestriction, ActivityManager.getCurrentUser());
+                userRestriction, mHost.getUserId());
         if (admin != null && !RestrictedLockUtilsInternal.hasBaseUserRestriction(mContext,
-                userRestriction, ActivityManager.getCurrentUser())) {
+                userRestriction, mHost.getUserId())) {
             state.disabledByPolicy = true;
             mEnforcedAdmin = admin;
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index 347ef45..98782f7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -38,6 +38,7 @@
 import com.android.systemui.qs.SecureSetting;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.settings.UserTracker;
 
 import javax.inject.Inject;
 
@@ -61,13 +62,14 @@
             MetricsLogger metricsLogger,
             StatusBarStateController statusBarStateController,
             ActivityStarter activityStarter,
-            QSLogger qsLogger
+            QSLogger qsLogger,
+            UserTracker userTracker
     ) {
         super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController,
                 activityStarter, qsLogger);
 
         mSetting = new SecureSetting(mContext, mainHandler,
-                Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED) {
+                Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, userTracker.getUserId()) {
             @Override
             protected void handleValueChanged(int value, boolean observedChange) {
                 // mHandler is the background handler so calling this is OK
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index ec8b143..2076cbf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -19,7 +19,6 @@
 import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
 import static android.provider.Settings.Global.ZEN_MODE_OFF;
 
-import android.app.ActivityManager;
 import android.app.Dialog;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -203,7 +202,7 @@
                     break;
                 default:
                     Uri conditionId = ZenModeConfig.toTimeCondition(mContext, zenDuration,
-                            ActivityManager.getCurrentUser(), true).id;
+                            mHost.getUserId(), true).id;
                     mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                             conditionId, TAG);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
index 26d408f..c7a8fa2 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
@@ -40,6 +40,11 @@
     val userHandle: UserHandle
 
     /**
+     * [UserInfo] for current user
+     */
+    val userInfo: UserInfo
+
+    /**
      * List of profiles associated with the current user.
      */
     val userProfiles: List<UserInfo>
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index 4cc0eee..049685f 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -82,6 +82,12 @@
     override val userContentResolver: ContentResolver
         get() = userContext.contentResolver
 
+    override val userInfo: UserInfo
+        get() {
+            val user = userId
+            return userProfiles.first { it.id == user }
+        }
+
     /**
      * Returns a [List<UserInfo>] of all profiles associated with the current user.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 37ae791..0184fa7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -58,12 +58,14 @@
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.view.AppearanceRegion;
 import com.android.systemui.statusbar.CommandQueue.Callbacks;
+import com.android.systemui.statusbar.commandline.CommandRegistry;
 import com.android.systemui.statusbar.policy.CallbackController;
 import com.android.systemui.tracing.ProtoTracer;
 
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
 
 /**
  * This class takes the functions from IStatusBar that come in on
@@ -159,6 +161,7 @@
      */
     private int mLastUpdatedImeDisplayId = INVALID_DISPLAY;
     private ProtoTracer mProtoTracer;
+    private final @Nullable CommandRegistry mRegistry;
 
     /**
      * These methods are called back on the main thread.
@@ -368,11 +371,12 @@
     }
 
     public CommandQueue(Context context) {
-        this(context, null);
+        this(context, null, null);
     }
 
-    public CommandQueue(Context context, ProtoTracer protoTracer) {
+    public CommandQueue(Context context, ProtoTracer protoTracer, CommandRegistry registry) {
         mProtoTracer = protoTracer;
+        mRegistry = registry;
         context.getSystemService(DisplayManager.class).registerDisplayListener(this, mHandler);
         // We always have default display.
         setDisabled(DEFAULT_DISPLAY, DISABLE_NONE, DISABLE2_NONE);
@@ -1013,6 +1017,34 @@
         }
     }
 
+    @Override
+    public void passThroughShellCommand(String[] args, ParcelFileDescriptor pfd) {
+        final FileOutputStream fos = new FileOutputStream(pfd.getFileDescriptor());
+        final PrintWriter pw = new PrintWriter(fos);
+        // This is mimicking Binder#dumpAsync, but on this side of the binder. Might be possible
+        // to just throw this work onto the handler just like the other messages
+        Thread thr = new Thread("Sysui.passThroughShellCommand") {
+            public void run() {
+                try {
+                    if (mRegistry == null) {
+                        return;
+                    }
+
+                    // Registry blocks this thread until finished
+                    mRegistry.onShellCommand(pw, args);
+                } finally {
+                    pw.flush();
+                    try {
+                        // Close the file descriptor so the TransferPipe finishes its thread
+                        pfd.close();
+                    } catch (Exception e) {
+                    }
+                }
+            }
+        };
+        thr.start();
+    }
+
     private final class H extends Handler {
         private H(Looper l) {
             super(l);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt
new file mode 100644
index 0000000..ce0a08c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.commandline
+
+import android.content.Context
+
+import com.android.systemui.Prefs
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+
+import java.io.PrintWriter
+import java.lang.IllegalStateException
+import java.util.concurrent.Executor
+import java.util.concurrent.FutureTask
+
+import javax.inject.Inject
+
+/**
+ * Registry / dispatcher for incoming shell commands. See [StatusBarManagerService] and
+ * [StatusBarShellCommand] for how things are set up. Commands come in here by way of the service
+ * like so:
+ *
+ * `adb shell cmd statusbar <command>`
+ *
+ * Where `cmd statusbar` send the shell command through to StatusBarManagerService, and
+ * <command> is either processed in system server, or sent through to IStatusBar (CommandQueue)
+ */
+@SysUISingleton
+class CommandRegistry @Inject constructor(
+    val context: Context,
+    @Main val mainExecutor: Executor
+) {
+    // To keep the command line parser hermetic, create a new one for every shell command
+    private val commandMap = mutableMapOf<String, CommandWrapper>()
+    private var initialized = false
+
+    /**
+     * Register a [Command] for a given name. The name here is the top-level namespace for
+     * the registered command. A command could look like this for instance:
+     *
+     * `adb shell cmd statusbar notifications list`
+     *
+     * Where `notifications` is the command that signifies which receiver to send the remaining args
+     * to.
+     *
+     * @param command String name of the command to register. Currently does not support aliases
+     * @param receiverFactory Creates an instance of the receiver on every command
+     * @param executor Pass an executor to offload your `receive` to another thread
+     */
+    @Synchronized
+    fun registerCommand(
+        name: String,
+        commandFactory: () -> Command,
+        executor: Executor
+    ) {
+        if (commandMap[name] != null) {
+            throw IllegalStateException("A command is already registered for ($name)")
+        }
+        commandMap[name] = CommandWrapper(commandFactory, executor)
+    }
+
+    /**
+     * Register a [Command] for a given name, to be executed on the main thread.
+     */
+    @Synchronized
+    fun registerCommand(name: String, commandFactory: () -> Command) {
+        registerCommand(name, commandFactory, mainExecutor)
+    }
+
+    /** Unregister a receiver */
+    @Synchronized
+    fun unregisterCommand(command: String) {
+        commandMap.remove(command)
+    }
+
+    private fun initializeCommands() {
+        initialized = true
+        // TODO: Might want a dedicated place for commands without a home. Currently
+        // this is here because Prefs.java is just an interface
+        registerCommand("prefs") { PrefsCommand(context) }
+    }
+
+    /**
+     * Receive a shell command and dispatch to the appropriate [Command]. Blocks until finished.
+     */
+    fun onShellCommand(pw: PrintWriter, args: Array<String>) {
+        if (!initialized) initializeCommands()
+
+        if (args.isEmpty()) {
+            help(pw)
+            return
+        }
+
+        val commandName = args[0]
+        val wrapper = commandMap[commandName]
+
+        if (wrapper == null) {
+            help(pw)
+            return
+        }
+
+        // Create a new instance of the command
+        val command = wrapper.commandFactory()
+
+        // Wrap the receive command in a task so that we can wait for its completion
+        val task = FutureTask<Unit> {
+            command.execute(pw, args.drop(1))
+        }
+
+        wrapper.executor.execute {
+            task.run()
+        }
+
+        // Wait for the future to complete
+        task.get()
+    }
+
+    private fun help(pw: PrintWriter) {
+        pw.println("Usage: adb shell cmd statusbar <command>")
+        pw.println("  known commands:")
+        for (k in commandMap.keys) {
+            pw.println("   $k")
+        }
+    }
+}
+
+private const val TAG = "CommandRegistry"
+
+interface Command {
+    fun execute(pw: PrintWriter, args: List<String>)
+    fun help(pw: PrintWriter)
+}
+
+// Wrap commands in an executor package
+private data class CommandWrapper(val commandFactory: () -> Command, val executor: Executor)
+
+// Commands can go here for now, but they should move outside
+
+private class PrefsCommand(val context: Context) : Command {
+    override fun help(pw: PrintWriter) {
+        pw.println("usage: prefs <command> [args]")
+        pw.println("Available commands:")
+        pw.println("  list-prefs")
+        pw.println("  set-pref <pref name> <value>")
+    }
+
+    override fun execute(pw: PrintWriter, args: List<String>) {
+        if (args.isEmpty()) {
+            help(pw)
+            return
+        }
+
+        val topLevel = args[0]
+
+        when (topLevel) {
+            "list-prefs" -> listPrefs(pw)
+            "set-pref" -> setPref(pw, args.drop(1))
+            else -> help(pw)
+        }
+    }
+
+    private fun listPrefs(pw: PrintWriter) {
+        pw.println("Available keys:")
+        for (field in Prefs.Key::class.java.declaredFields) {
+            pw.print("  ")
+            pw.println(field.get(Prefs.Key::class.java))
+        }
+    }
+
+    /**
+     * Sets a preference from [Prefs]
+     */
+    private fun setPref(pw: PrintWriter, args: List<String>) {
+        if (args.isEmpty()) {
+            pw.println("invalid arguments: $args")
+            return
+        }
+        val pref = args[0]
+
+        when (pref) {
+            Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING -> {
+                val value = Integer.parseInt(args[1])
+                Prefs.putBoolean(context, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING, value != 0)
+            }
+            else -> {
+                pw.println("Cannot set pref ($pref)")
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index 969cd90..c4e0429 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -39,6 +39,7 @@
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.commandline.CommandRegistry;
 import com.android.systemui.statusbar.notification.AssistantFeedbackController;
 import com.android.systemui.statusbar.notification.DynamicChildBindController;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
@@ -192,8 +193,11 @@
      */
     @Provides
     @SysUISingleton
-    static CommandQueue provideCommandQueue(Context context, ProtoTracer protoTracer) {
-        return new CommandQueue(context, protoTracer);
+    static CommandQueue provideCommandQueue(
+            Context context,
+            ProtoTracer protoTracer,
+            CommandRegistry registry) {
+        return new CommandQueue(context, protoTracer, registry);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index efd6767..76c5baf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -169,7 +169,8 @@
                 String setting = split[0];
                 String spec = split[1];
                 // Populate all the settings. As they may not have been added in other users
-                AutoAddSetting s = new AutoAddSetting(mContext, mHandler, setting, spec);
+                AutoAddSetting s = new AutoAddSetting(
+                        mContext, mHandler, setting, mCurrentUser.getIdentifier(), spec);
                 mAutoAddSettingList.add(s);
             } else {
                 Log.w(TAG, "Malformed item in array: " + tile);
@@ -319,8 +320,14 @@
     private class AutoAddSetting extends SecureSetting {
         private final String mSpec;
 
-        AutoAddSetting(Context context, Handler handler, String setting, String tileSpec) {
-            super(context, handler, setting);
+        AutoAddSetting(
+                Context context,
+                Handler handler,
+                String setting,
+                int userId,
+                String tileSpec
+        ) {
+            super(context, handler, setting, userId);
             mSpec = tileSpec;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index 0fdc80b..8e8a33f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -20,7 +20,6 @@
 
 import static com.android.settingslib.Utils.updateLocationEnabled;
 
-import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -43,6 +42,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.Utils;
 
 import java.util.ArrayList;
@@ -60,6 +60,7 @@
     private final Context mContext;
     private final AppOpsController mAppOpsController;
     private final BootCompleteCache mBootCompleteCache;
+    private final UserTracker mUserTracker;
     private final H mHandler;
 
 
@@ -68,11 +69,13 @@
     @Inject
     public LocationControllerImpl(Context context, AppOpsController appOpsController,
             @Main Looper mainLooper, @Background Handler backgroundHandler,
-            BroadcastDispatcher broadcastDispatcher, BootCompleteCache bootCompleteCache) {
+            BroadcastDispatcher broadcastDispatcher, BootCompleteCache bootCompleteCache,
+            UserTracker userTracker) {
         mContext = context;
         mAppOpsController = appOpsController;
         mBootCompleteCache = bootCompleteCache;
         mHandler = new H(mainLooper);
+        mUserTracker = userTracker;
 
         // Register to listen for changes in location settings.
         IntentFilter filter = new IntentFilter();
@@ -113,7 +116,7 @@
     public boolean setLocationEnabled(boolean enabled) {
         // QuickSettings always runs as the owner, so specifically set the settings
         // for the current foreground user.
-        int currentUserId = ActivityManager.getCurrentUser();
+        int currentUserId = mUserTracker.getUserId();
         if (isUserLocationRestricted(currentUserId)) {
             return false;
         }
@@ -134,7 +137,7 @@
         LocationManager locationManager =
                 (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
         return mBootCompleteCache.isBootComplete() && locationManager.isLocationEnabledForUser(
-                UserHandle.of(ActivityManager.getCurrentUser()));
+                mUserTracker.getUserHandle());
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
index 0070dcf..4c724ae 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.tuner;
 
+import android.app.ActivityManager;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.DialogFragment;
@@ -22,6 +23,7 @@
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.view.Menu;
 import android.view.MenuInflater;
@@ -122,7 +124,8 @@
                 getActivity().finish();
                 return true;
             case MENU_REMOVE:
-                TunerService.showResetRequest(getContext(), new Runnable() {
+                UserHandle user = new UserHandle(ActivityManager.getCurrentUser());
+                TunerService.showResetRequest(getContext(), user, new Runnable() {
                     @Override
                     public void run() {
                         if (getActivity() != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index 338e178..70bba26 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -14,7 +14,6 @@
 
 package com.android.systemui.tuner;
 
-import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -51,25 +50,24 @@
         void onTuningChanged(String key, String newValue);
     }
 
-    private static Context userContext(Context context) {
+    private static Context userContext(Context context, UserHandle user) {
         try {
-            return context.createPackageContextAsUser(context.getPackageName(), 0,
-                    new UserHandle(ActivityManager.getCurrentUser()));
+            return context.createPackageContextAsUser(context.getPackageName(), 0, user);
         } catch (NameNotFoundException e) {
             return context;
         }
     }
 
-    public static final void setTunerEnabled(Context context, boolean enabled) {
-        userContext(context).getPackageManager().setComponentEnabledSetting(
+    public static final void setTunerEnabled(Context context, UserHandle user, boolean enabled) {
+        userContext(context, user).getPackageManager().setComponentEnabledSetting(
                 new ComponentName(context, TunerActivity.class),
                 enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
                         : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                 PackageManager.DONT_KILL_APP);
     }
 
-    public static final boolean isTunerEnabled(Context context) {
-        return userContext(context).getPackageManager().getComponentEnabledSetting(
+    public static final boolean isTunerEnabled(Context context, UserHandle user) {
+        return userContext(context, user).getPackageManager().getComponentEnabledSetting(
                 new ComponentName(context, TunerActivity.class))
                 == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
     }
@@ -83,7 +81,8 @@
         }
     }
 
-    public static final void showResetRequest(final Context context, final Runnable onDisabled) {
+    public static final void showResetRequest(final Context context, UserHandle user,
+            final Runnable onDisabled) {
         SystemUIDialog dialog = new SystemUIDialog(context);
         dialog.setShowForAllUsers(true);
         dialog.setMessage(R.string.remove_from_settings_prompt);
@@ -91,20 +90,20 @@
                 (OnClickListener) null);
         dialog.setButton(DialogInterface.BUTTON_POSITIVE,
                 context.getString(R.string.guest_exit_guest_dialog_remove), new OnClickListener() {
-            @Override
-            public void onClick(DialogInterface dialog, int which) {
-                // Tell the tuner (in main SysUI process) to clear all its settings.
-                context.sendBroadcast(new Intent(TunerService.ACTION_CLEAR));
-                // Disable access to tuner.
-                TunerService.setTunerEnabled(context, false);
-                // Make them sit through the warning dialog again.
-                Settings.Secure.putInt(context.getContentResolver(),
-                        TunerFragment.SETTING_SEEN_TUNER_WARNING, 0);
-                if (onDisabled != null) {
-                    onDisabled.run();
-                }
-            }
-        });
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        // Tell the tuner (in main SysUI process) to clear all its settings.
+                        context.sendBroadcast(new Intent(TunerService.ACTION_CLEAR));
+                        // Disable access to tuner.
+                        TunerService.setTunerEnabled(context, user, false);
+                        // Make them sit through the warning dialog again.
+                        Settings.Secure.putInt(context.getContentResolver(),
+                                TunerFragment.SETTING_SEEN_TUNER_WARNING, 0);
+                        if (onDisabled != null) {
+                            onDisabled.run();
+                        }
+                    }
+                });
         dialog.show();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index d9727a7..22f03e07 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -15,13 +15,13 @@
  */
 package com.android.systemui.tuner;
 
-import android.app.ActivityManager;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.Looper;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -37,7 +37,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.qs.QSTileHost;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.util.leak.LeakDetector;
 
@@ -81,7 +81,8 @@
 
     private ContentResolver mContentResolver;
     private int mCurrentUser;
-    private CurrentUserTracker mUserTracker;
+    private UserTracker.Callback mCurrentUserTracker;
+    private UserTracker mUserTracker;
 
     /**
      */
@@ -91,11 +92,13 @@
             @Main Handler mainHandler,
             LeakDetector leakDetector,
             DemoModeController demoModeController,
-            BroadcastDispatcher broadcastDispatcher) {
+            BroadcastDispatcher broadcastDispatcher,
+            UserTracker userTracker) {
         mContext = context;
         mContentResolver = mContext.getContentResolver();
         mLeakDetector = leakDetector;
         mDemoModeController = demoModeController;
+        mUserTracker = userTracker;
 
         for (UserInfo user : UserManager.get(mContext).getUsers()) {
             mCurrentUser = user.getUserHandle().getIdentifier();
@@ -104,21 +107,22 @@
             }
         }
 
-        mCurrentUser = ActivityManager.getCurrentUser();
-        mUserTracker = new CurrentUserTracker(broadcastDispatcher) {
+        mCurrentUser = mUserTracker.getUserId();
+        mCurrentUserTracker = new UserTracker.Callback() {
             @Override
-            public void onUserSwitched(int newUserId) {
-                mCurrentUser = newUserId;
+            public void onUserChanged(int newUser, Context userContext) {
+                mCurrentUser = newUser;
                 reloadAll();
                 reregisterAll();
             }
         };
-        mUserTracker.startTracking();
+        mUserTracker.addCallback(mCurrentUserTracker,
+                new HandlerExecutor(mainHandler));
     }
 
     @Override
     public void destroy() {
-        mUserTracker.stopTracking();
+        mUserTracker.removeCallback(mCurrentUserTracker);
     }
 
     private void upgradeTuner(int oldVersion, int newVersion, Handler mainHandler) {
@@ -137,7 +141,7 @@
             }
         }
         if (oldVersion < 2) {
-            setTunerEnabled(mContext, false);
+            setTunerEnabled(mContext, mUserTracker.getUserHandle(), false);
         }
         // 3 Removed because of a revert.
         if (oldVersion < 4) {
@@ -272,7 +276,7 @@
         @Override
         public void onChange(boolean selfChange, java.util.Collection<Uri> uris,
                 int flags, int userId) {
-            if (userId == ActivityManager.getCurrentUser()) {
+            if (userId == mUserTracker.getUserId()) {
                 for (Uri u : uris) {
                     reloadSetting(u);
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index bb3b1b4..970d500 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -32,8 +32,10 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.animation.FlingAnimationUtils;
+import com.android.wm.shell.common.AnimationThread;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.HandlerExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SystemWindows;
 import com.android.wm.shell.common.TransactionPool;
@@ -132,8 +134,10 @@
 
     @SysUISingleton
     @Provides
-    static ShellTaskOrganizer provideShellTaskOrganizer(SyncTransactionQueue syncQueue) {
-        ShellTaskOrganizer organizer = new ShellTaskOrganizer(syncQueue);
+    static ShellTaskOrganizer provideShellTaskOrganizer(SyncTransactionQueue syncQueue,
+            @Main Handler handler, TransactionPool transactionPool) {
+        ShellTaskOrganizer organizer = new ShellTaskOrganizer(syncQueue, transactionPool,
+                new HandlerExecutor(handler), AnimationThread.instance().getExecutor());
         organizer.registerOrganizer();
         return organizer;
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index d107f64..ab805f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -62,6 +62,7 @@
 
 import com.android.systemui.R.dimen;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.tuner.TunerService;
 
 import org.junit.Before;
@@ -88,6 +89,8 @@
     private TunerService mTunerService;
     @Mock
     private BroadcastDispatcher mBroadcastDispatcher;
+    @Mock
+    private UserTracker mUserTracker;
 
     @Before
     public void setup() {
@@ -109,7 +112,7 @@
         mContext.addMockSystemService(DisplayManager.class, mDisplayManager);
 
         mScreenDecorations = spy(new ScreenDecorations(mContext, mMainHandler,
-                mBroadcastDispatcher, mTunerService) {
+                mBroadcastDispatcher, mTunerService, mUserTracker) {
             @Override
             public void start() {
                 super.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
index bb003ee..0a81c38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
@@ -27,6 +27,7 @@
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
@@ -61,6 +62,8 @@
 
     @Mock
     private lateinit var mockControlsController: ControlsController
+    @Mock(stubOnly = true)
+    private lateinit var mockUserTracker: UserTracker
 
     @Captor
     private lateinit var subscriberCaptor: ArgumentCaptor<IControlsSubscriber.Stub>
@@ -82,9 +85,10 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         providers.clear()
+        `when`(mockUserTracker.userHandle).thenReturn(user)
 
         controller = TestableControlsBindingControllerImpl(
-                mContext, executor, Lazy { mockControlsController })
+                mContext, executor, Lazy { mockControlsController }, mockUserTracker)
     }
 
     @After
@@ -364,8 +368,9 @@
 class TestableControlsBindingControllerImpl(
     context: Context,
     executor: DelayableExecutor,
-    lazyController: Lazy<ControlsController>
-) : ControlsBindingControllerImpl(context, executor, lazyController) {
+    lazyController: Lazy<ControlsController>,
+    userTracker: UserTracker
+) : ControlsBindingControllerImpl(context, executor, lazyController, userTracker) {
 
     companion object {
         val providers = mutableListOf<ControlsProviderLifecycleManager>()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index 45262c7..f6c836a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
 import org.junit.After
@@ -82,6 +83,8 @@
     private lateinit var broadcastDispatcher: BroadcastDispatcher
     @Mock
     private lateinit var listingController: ControlsListingController
+    @Mock(stubOnly = true)
+    private lateinit var userTracker: UserTracker
 
     @Captor
     private lateinit var structureInfoCaptor: ArgumentCaptor<StructureInfo>
@@ -143,6 +146,8 @@
         Settings.Secure.putIntForUser(mContext.contentResolver,
                 ControlsControllerImpl.CONTROLS_AVAILABLE, 1, otherUser)
 
+        `when`(userTracker.userHandle).thenReturn(UserHandle.of(user))
+
         delayableExecutor = FakeExecutor(FakeSystemClock())
 
         val wrapper = object : ContextWrapper(mContext) {
@@ -162,7 +167,8 @@
                 listingController,
                 broadcastDispatcher,
                 Optional.of(persistenceWrapper),
-                mock(DumpManager::class.java)
+                mock(DumpManager::class.java),
+                userTracker
         )
         controller.auxiliaryPersistenceWrapper = auxiliaryPersistenceWrapper
 
@@ -217,7 +223,8 @@
                 listingController,
                 broadcastDispatcher,
                 Optional.of(persistenceWrapper),
-                mock(DumpManager::class.java)
+                mock(DumpManager::class.java),
+                userTracker
         )
         assertEquals(listOf(TEST_STRUCTURE_INFO), controller_other.getFavorites())
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
index 841b255..db41d8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -26,6 +26,7 @@
 import com.android.settingslib.applications.ServiceListing
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
 import org.junit.After
@@ -66,6 +67,8 @@
     private lateinit var serviceInfo: ServiceInfo
     @Mock
     private lateinit var serviceInfo2: ServiceInfo
+    @Mock(stubOnly = true)
+    private lateinit var userTracker: UserTracker
 
     private var componentName = ComponentName("pkg1", "class1")
     private var componentName2 = ComponentName("pkg2", "class2")
@@ -86,6 +89,7 @@
 
         `when`(serviceInfo.componentName).thenReturn(componentName)
         `when`(serviceInfo2.componentName).thenReturn(componentName2)
+        `when`(userTracker.userId).thenReturn(user)
 
         val wrapper = object : ContextWrapper(mContext) {
             override fun createContextAsUser(user: UserHandle, flags: Int): Context {
@@ -93,7 +97,7 @@
             }
         }
 
-        controller = ControlsListingControllerImpl(wrapper, executor, { mockSL })
+        controller = ControlsListingControllerImpl(wrapper, executor, { mockSL }, userTracker)
         verify(mockSL).addCallback(capture(serviceListingCallbackCaptor))
     }
 
@@ -121,7 +125,7 @@
         `when`(mockServiceListing.reload()).then {
             callback?.onServicesReloaded(listOf(serviceInfo))
         }
-        ControlsListingControllerImpl(mContext, exec, { mockServiceListing })
+        ControlsListingControllerImpl(mContext, exec, { mockServiceListing }, userTracker)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt
index 1f10d01..cb17829 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt
@@ -16,15 +16,14 @@
 
 package com.android.systemui.privacy
 
-import android.os.UserManager
 import android.provider.DeviceConfig
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.appops.AppOpsController
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.DeviceConfigProxy
 import com.android.systemui.util.DeviceConfigProxyFake
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -62,11 +61,9 @@
     @Mock
     private lateinit var callback: PrivacyItemController.Callback
     @Mock
-    private lateinit var userManager: UserManager
-    @Mock
-    private lateinit var broadcastDispatcher: BroadcastDispatcher
-    @Mock
     private lateinit var dumpManager: DumpManager
+    @Mock
+    private lateinit var userTracker: UserTracker
 
     private lateinit var privacyItemController: PrivacyItemController
     private lateinit var executor: FakeExecutor
@@ -77,9 +74,8 @@
                 appOpsController,
                 executor,
                 executor,
-                broadcastDispatcher,
                 deviceConfigProxy,
-                userManager,
+                userTracker,
                 dumpManager
         )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
index 0a079b1..16a1105 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
@@ -18,10 +18,8 @@
 
 import android.app.ActivityManager
 import android.app.AppOpsManager
-import android.content.Intent
 import android.content.pm.UserInfo
 import android.os.UserHandle
-import android.os.UserManager
 import android.provider.DeviceConfig
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
@@ -30,8 +28,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.appops.AppOpItem
 import com.android.systemui.appops.AppOpsController
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.DeviceConfigProxy
 import com.android.systemui.util.DeviceConfigProxyFake
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -52,6 +50,7 @@
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito
+import org.mockito.Mockito.`when`
 import org.mockito.Mockito.atLeastOnce
 import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.mock
@@ -84,9 +83,7 @@
     @Mock
     private lateinit var callback: PrivacyItemController.Callback
     @Mock
-    private lateinit var userManager: UserManager
-    @Mock
-    private lateinit var broadcastDispatcher: BroadcastDispatcher
+    private lateinit var userTracker: UserTracker
     @Mock
     private lateinit var dumpManager: DumpManager
     @Captor
@@ -103,9 +100,8 @@
                 appOpsController,
                 executor,
                 executor,
-                broadcastDispatcher,
                 deviceConfigProxy,
-                userManager,
+                userTracker,
                 dumpManager
         )
     }
@@ -119,11 +115,7 @@
         // Listen to everything by default
         changeAll(true)
 
-        doReturn(listOf(object : UserInfo() {
-            init {
-                id = CURRENT_USER_ID
-            }
-        })).`when`(userManager).getProfiles(anyInt())
+        `when`(userTracker.userProfiles).thenReturn(listOf(UserInfo(CURRENT_USER_ID, "", 0)))
 
         privacyItemController = PrivacyItemController()
     }
@@ -163,37 +155,26 @@
     }
 
     @Test
-    fun testRegisterReceiver_allUsers() {
+    fun testRegisterCallback() {
         privacyItemController.addCallback(callback)
         executor.runAllReady()
-        verify(broadcastDispatcher, atLeastOnce()).registerReceiver(
-                eq(privacyItemController.userSwitcherReceiver), any(), eq(null), eq(UserHandle.ALL))
-        verify(broadcastDispatcher, never())
-                .unregisterReceiver(eq(privacyItemController.userSwitcherReceiver))
+        verify(userTracker, atLeastOnce()).addCallback(
+                eq(privacyItemController.userTrackerCallback), any())
+        verify(userTracker, never()).removeCallback(eq(privacyItemController.userTrackerCallback))
     }
 
     @Test
-    fun testReceiver_ACTION_USER_FOREGROUND() {
-        privacyItemController.userSwitcherReceiver.onReceive(context,
-                Intent(Intent.ACTION_USER_SWITCHED))
+    fun testCallback_userChanged() {
+        privacyItemController.userTrackerCallback.onUserChanged(0, mContext)
         executor.runAllReady()
-        verify(userManager).getProfiles(anyInt())
+        verify(userTracker).userProfiles
     }
 
     @Test
-    fun testReceiver_ACTION_MANAGED_PROFILE_ADDED() {
-        privacyItemController.userSwitcherReceiver.onReceive(context,
-                Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE))
+    fun testReceiver_profilesChanged() {
+        privacyItemController.userTrackerCallback.onProfilesChanged(emptyList())
         executor.runAllReady()
-        verify(userManager).getProfiles(anyInt())
-    }
-
-    @Test
-    fun testReceiver_ACTION_MANAGED_PROFILE_REMOVED() {
-        privacyItemController.userSwitcherReceiver.onReceive(context,
-                Intent(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE))
-        executor.runAllReady()
-        verify(userManager).getProfiles(anyInt())
+        verify(userTracker).userProfiles
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterImplTest.java
index d4688d7..99f2d80 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterImplTest.java
@@ -14,30 +14,40 @@
 
 package com.android.systemui.qs;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.ClipData;
+import android.content.ClipboardManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.widget.TextView;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.R;
 import com.android.systemui.R.id;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
 
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
@@ -47,19 +57,46 @@
     private QSFooterImpl mFooter;
     private ActivityStarter mActivityStarter;
     private DeviceProvisionedController mDeviceProvisionedController;
+    private UserInfoController mUserInfoController;
+    private UserTracker mUserTracker;
+    @Mock
+    private ClipboardManager mClipboardManager;
 
     @Before
     public void setup() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
         injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
         mActivityStarter = mDependency.injectMockDependency(ActivityStarter.class);
         mDeviceProvisionedController = mDependency.injectMockDependency(
                 DeviceProvisionedController.class);
+        mUserInfoController = mDependency.injectMockDependency(UserInfoController.class);
+        mUserTracker = mDependency.injectMockDependency(UserTracker.class);
+
+        mContext.addMockSystemService(ClipboardManager.class, mClipboardManager);
+
+        when(mUserTracker.getUserContext()).thenReturn(mContext);
+
         TestableLooper.get(this).runWithLooper(
                 () -> mFooter = (QSFooterImpl) LayoutInflater.from(mContext).inflate(
                         R.layout.qs_footer_impl, null));
     }
 
     @Test
+    public void testBuildTextCopy() {
+        TextView buildTextView = mFooter.requireViewById(R.id.build);
+        CharSequence buildText = "TEST";
+        buildTextView.setText(buildText);
+        buildTextView.setLongClickable(true);
+
+        buildTextView.performLongClick();
+
+        ArgumentCaptor<ClipData> captor = ArgumentCaptor.forClass(ClipData.class);
+        verify(mClipboardManager).setPrimaryClip(captor.capture());
+        assertThat(captor.getValue().getItemAt(0).getText()).isEqualTo(buildText);
+    }
+
+    @Test
     @Ignore("failing")
     public void testSettings_UserNotSetup() {
         View settingsButton = mFooter.findViewById(id.settings_button);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index b46c6ef..1c2d0840 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -45,6 +45,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSFactoryImpl;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.AutoTileManager;
@@ -107,7 +108,7 @@
                 mock(PluginManager.class), mock(TunerService.class),
                 () -> mock(AutoTileManager.class), mock(DumpManager.class),
                 mock(BroadcastDispatcher.class), Optional.of(mock(StatusBar.class)),
-                mock(QSLogger.class), mock(UiEventLogger.class));
+                mock(QSLogger.class), mock(UiEventLogger.class), mock(UserTracker.class));
         qs.setHost(host);
 
         qs.setListening(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
index cb3a048..4b7a268 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
@@ -38,7 +38,6 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.logging.testing.UiEventLoggerFake;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.systemui.Dependency;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -49,7 +48,7 @@
 import com.android.systemui.qs.customize.QSCustomizer;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.SecurityController;
 import com.android.systemui.util.animation.DisappearParameters;
 import com.android.systemui.util.animation.UniqueObjectHostView;
@@ -93,11 +92,9 @@
     @Mock
     private MediaHost mMediaHost;
     @Mock
-    private LocalBluetoothManager mLocalBluetoothManager;
-    @Mock
     private ActivityStarter mActivityStarter;
-    @Mock
-    private NotificationEntryManager mEntryManager;
+    @Mock(stubOnly = true)
+    private UserTracker mUserTracker;
     private UiEventLoggerFake mUiEventLogger;
 
     @Before
@@ -117,7 +114,7 @@
         mTestableLooper.runWithLooper(() -> {
             mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
             mQsPanel = new QSPanel(mContext, null, mDumpManager, mBroadcastDispatcher,
-                    mQSLogger, mMediaHost, mUiEventLogger);
+                    mQSLogger, mMediaHost, mUiEventLogger, mUserTracker);
             mQsPanel.onFinishInflate();
             // Provides a parent with non-zero size for QSPanel
             mParentView = new FrameLayout(mContext);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index 417b19f..fd1866b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -17,7 +17,6 @@
 import static junit.framework.Assert.assertEquals;
 
 import static org.junit.Assert.assertFalse;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -25,7 +24,6 @@
 
 import android.content.Context;
 import android.content.pm.UserInfo;
-import android.os.UserManager;
 import android.provider.Settings;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
@@ -42,6 +40,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.SecurityController;
 
 import org.junit.Before;
@@ -73,20 +72,20 @@
     private TestableImageView mFooterIcon;
     private QSSecurityFooter mFooter;
     private SecurityController mSecurityController = mock(SecurityController.class);
-    private UserManager mUserManager;
+    private UserTracker mUserTracker;
 
     @Before
     public void setUp() {
         mDependency.injectTestDependency(SecurityController.class, mSecurityController);
         mDependency.injectTestDependency(Dependency.BG_LOOPER,
                 TestableLooper.get(this).getLooper());
+        mUserTracker = mock(UserTracker.class);
+        when(mUserTracker.getUserInfo()).thenReturn(mock(UserInfo.class));
         mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE,
                 new LayoutInflaterBuilder(mContext)
                         .replace("ImageView", TestableImageView.class)
                         .build());
-        mUserManager = Mockito.mock(UserManager.class);
-        mContext.addMockSystemService(Context.USER_SERVICE, mUserManager);
-        mFooter = new QSSecurityFooter(null, mContext);
+        mFooter = new QSSecurityFooter(null, mContext, mUserTracker);
         mRootView = (ViewGroup) mFooter.getView();
         mFooterText = mRootView.findViewById(R.id.footer_text);
         mFooterIcon = mRootView.findViewById(R.id.footer_icon);
@@ -141,7 +140,7 @@
         when(mSecurityController.getDeviceOwnerOrganizationName()).thenReturn(null);
         final UserInfo mockUserInfo = Mockito.mock(UserInfo.class);
         when(mockUserInfo.isDemo()).thenReturn(true);
-        when(mUserManager.getUserInfo(anyInt())).thenReturn(mockUserInfo);
+        when(mUserTracker.getUserInfo()).thenReturn(mockUserInfo);
         Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_DEMO_MODE, 1);
 
         mFooter.refreshState();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index c8e1a74..452ff4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -55,6 +55,7 @@
 import com.android.systemui.qs.external.CustomTile;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.phone.AutoTileManager;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -110,6 +111,8 @@
     private CustomTile mCustomTile;
     @Mock
     private UiEventLogger mUiEventLogger;
+    @Mock
+    private UserTracker mUserTracker;
 
     private Handler mHandler;
     private TestableLooper mLooper;
@@ -122,7 +125,7 @@
         mHandler = new Handler(mLooper.getLooper());
         mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mHandler,
                 mLooper.getLooper(), mPluginManager, mTunerService, mAutoTiles, mDumpManager,
-                mBroadcastDispatcher, mStatusBar, mQSLogger, mUiEventLogger);
+                mBroadcastDispatcher, mStatusBar, mQSLogger, mUiEventLogger, mUserTracker);
         setUpTileFactory();
 
         Settings.Secure.putStringForUser(mContext.getContentResolver(), QSTileHost.TILES_SETTING,
@@ -301,10 +304,10 @@
                 PluginManager pluginManager, TunerService tunerService,
                 Provider<AutoTileManager> autoTiles, DumpManager dumpManager,
                 BroadcastDispatcher broadcastDispatcher, StatusBar statusBar, QSLogger qsLogger,
-                UiEventLogger uiEventLogger) {
+                UiEventLogger uiEventLogger, UserTracker userTracker) {
             super(context, iconController, defaultFactory, mainHandler, bgLooper, pluginManager,
                     tunerService, autoTiles, dumpManager, broadcastDispatcher,
-                    Optional.of(statusBar), qsLogger, uiEventLogger);
+                    Optional.of(statusBar), qsLogger, uiEventLogger, userTracker);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
index 2ef7c65..a2ffb84 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
@@ -55,6 +55,7 @@
 import com.android.systemui.plugins.qs.QSIconView;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -103,6 +104,8 @@
     private QSTileHost mQSTileHost;
     @Mock
     private PackageManager mPackageManager;
+    @Mock
+    private UserTracker mUserTracker;
     @Captor
     private ArgumentCaptor<List<TileQueryHelper.TileInfo>> mCaptor;
 
@@ -133,7 +136,7 @@
         FakeSystemClock clock = new FakeSystemClock();
         mMainExecutor = new FakeExecutor(clock);
         mBgExecutor = new FakeExecutor(clock);
-        mTileQueryHelper = new TileQueryHelper(mContext, mMainExecutor, mBgExecutor);
+        mTileQueryHelper = new TileQueryHelper(mContext, mUserTracker, mMainExecutor, mBgExecutor);
         mTileQueryHelper.setListener(mListener);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
index 683e8f4..6a9d9fa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
@@ -22,11 +22,13 @@
 import android.content.ComponentName;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.UserHandle;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.settings.UserTracker;
 
 import org.junit.After;
 import org.junit.Before;
@@ -44,6 +46,7 @@
     private HandlerThread mThread;
     private Handler mHandler;
     private TileServiceManager mTileServiceManager;
+    private UserTracker mUserTracker;
 
     @Before
     public void setUp() throws Exception {
@@ -51,13 +54,18 @@
         mThread.start();
         mHandler = Handler.createAsync(mThread.getLooper());
         mTileServices = Mockito.mock(TileServices.class);
+        mUserTracker = Mockito.mock(UserTracker.class);
+        Mockito.when(mUserTracker.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
+        Mockito.when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM);
+
         Mockito.when(mTileServices.getContext()).thenReturn(mContext);
         mTileLifecycle = Mockito.mock(TileLifecycleManager.class);
         Mockito.when(mTileLifecycle.isActiveTile()).thenReturn(false);
         ComponentName componentName = new ComponentName(mContext,
                 TileServiceManagerTest.class);
         Mockito.when(mTileLifecycle.getComponent()).thenReturn(componentName);
-        mTileServiceManager = new TileServiceManager(mTileServices, mHandler, mTileLifecycle);
+        mTileServiceManager = new TileServiceManager(mTileServices, mHandler, mUserTracker,
+                mTileLifecycle);
     }
 
     @After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 53ed4cf..2a3bc31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -46,6 +46,7 @@
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSFactoryImpl;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.phone.AutoTileManager;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -92,6 +93,8 @@
     private QSLogger mQSLogger;
     @Mock
     private UiEventLogger mUiEventLogger;
+    @Mock
+    private UserTracker mUserTracker;
 
     @Before
     public void setUp() throws Exception {
@@ -110,8 +113,10 @@
                 mock(BroadcastDispatcher.class),
                 Optional.of(mStatusBar),
                 mQSLogger,
-                mUiEventLogger);
-        mTileService = new TestTileServices(host, Looper.getMainLooper(), mBroadcastDispatcher);
+                mUiEventLogger,
+                mUserTracker);
+        mTileService = new TestTileServices(host, Looper.getMainLooper(), mBroadcastDispatcher,
+                mUserTracker);
     }
 
     @After
@@ -186,8 +191,8 @@
 
     private class TestTileServices extends TileServices {
         TestTileServices(QSTileHost host, Looper looper,
-                BroadcastDispatcher broadcastDispatcher) {
-            super(host, looper, broadcastDispatcher);
+                BroadcastDispatcher broadcastDispatcher, UserTracker userTracker) {
+            super(host, looper, broadcastDispatcher, userTracker);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
new file mode 100644
index 0000000..16eb1d9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.commandline
+
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+
+import com.android.systemui.SysuiTestCase
+
+import org.mockito.ArgumentMatchers.anyList
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import java.io.PrintWriter
+import java.io.StringWriter
+import java.util.concurrent.Executor
+
+private fun <T> anyObject(): T {
+    return Mockito.anyObject<T>()
+}
+
+private fun <T : Any> safeEq(value: T): T = eq(value) ?: value
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@SmallTest
+class CommandRegistryTest : SysuiTestCase() {
+    lateinit var registry: CommandRegistry
+    val inLineExecutor = object : Executor {
+        override fun execute(command: Runnable) {
+            command.run()
+        }
+    }
+
+    val writer: PrintWriter = PrintWriter(StringWriter())
+
+    @Before
+    fun setup() {
+        registry = CommandRegistry(context, inLineExecutor)
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun testRegisterCommand_throwsWhenAlreadyRegistered() {
+        registry.registerCommand(COMMAND) { FakeCommand() }
+        // Should throw when registering the same command twice
+        registry.registerCommand(COMMAND) { FakeCommand() }
+    }
+
+    @Test
+    fun testOnShellCommand() {
+        var fakeCommand = mock(Command::class.java)
+        registry.registerCommand(COMMAND) { fakeCommand }
+        registry.onShellCommand(writer, arrayOf(COMMAND))
+        verify(fakeCommand).execute(anyObject(), anyList())
+    }
+
+    @Test
+    fun testArgsPassedToShellCommand() {
+        var fakeCommand = mock(Command::class.java)
+        registry.registerCommand(COMMAND) { fakeCommand }
+        registry.onShellCommand(writer, arrayOf(COMMAND, "arg1", "arg2", "arg3"))
+        verify(fakeCommand).execute(anyObject(), safeEq(listOf("arg1", "arg2", "arg3")))
+    }
+
+    class FakeCommand() : Command {
+        override fun execute(pw: PrintWriter, args: List<String>) {
+        }
+
+        override fun help(pw: PrintWriter) {
+        }
+    }
+}
+
+private const val COMMAND = "test_command"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
index 0c2361a..be836d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
@@ -20,11 +20,13 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.AppOpsManager;
 import android.content.Intent;
 import android.location.LocationManager;
 import android.os.Handler;
+import android.os.UserHandle;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
@@ -35,6 +37,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.appops.AppOpsController;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.LocationController.LocationChangeCallback;
 
 import org.junit.Before;
@@ -52,10 +55,13 @@
     private TestableLooper mTestableLooper;
 
     @Mock private AppOpsController mAppOpsController;
+    @Mock private UserTracker mUserTracker;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+        when(mUserTracker.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
+        when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM);
 
         mTestableLooper = TestableLooper.get(this);
         mLocationController = spy(new LocationControllerImpl(mContext,
@@ -63,7 +69,8 @@
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 mock(BroadcastDispatcher.class),
-                mock(BootCompleteCache.class)));
+                mock(BootCompleteCache.class),
+                mUserTracker));
 
         mTestableLooper.processAllMessages();
     }
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java b/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
index 33b9d00..da5f25b 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
@@ -28,6 +28,7 @@
 import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
 import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent;
 import android.net.netlink.NetlinkSocket;
+import android.net.netlink.StructNfGenMsg;
 import android.net.netlink.StructNlMsgHdr;
 import android.net.util.SharedLog;
 import android.net.util.SocketUtils;
@@ -41,11 +42,12 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.FileDescriptor;
-import java.io.InterruptedIOException;
 import java.io.IOException;
+import java.io.InterruptedIOException;
 import java.net.SocketAddress;
 import java.net.SocketException;
 import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import java.util.ArrayList;
 import java.util.NoSuchElementException;
 
@@ -66,11 +68,12 @@
     private static final String NO_IPV4_ADDRESS = "";
     private static final String NO_IPV4_GATEWAY = "";
     // Reference kernel/uapi/linux/netfilter/nfnetlink_compat.h
-    private static final int NF_NETLINK_CONNTRACK_NEW = 1;
-    private static final int NF_NETLINK_CONNTRACK_UPDATE = 2;
-    private static final int NF_NETLINK_CONNTRACK_DESTROY = 4;
+    public static final int NF_NETLINK_CONNTRACK_NEW = 1;
+    public static final int NF_NETLINK_CONNTRACK_UPDATE = 2;
+    public static final int NF_NETLINK_CONNTRACK_DESTROY = 4;
     // Reference libnetfilter_conntrack/linux_nfnetlink_conntrack.h
     public static final short NFNL_SUBSYS_CTNETLINK = 1;
+    public static final short IPCTNL_MSG_CT_NEW = 0;
     public static final short IPCTNL_MSG_CT_GET = 1;
 
     private final long NETLINK_MESSAGE_TIMEOUT_MS = 500;
@@ -237,7 +240,7 @@
                 NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY);
         if (h1 == null) return false;
 
-        sendNetlinkMessage(h1, (short) ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET),
+        sendIpv4NfGenMsg(h1, (short) ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET),
                            (short) (NLM_F_REQUEST | NLM_F_DUMP));
 
         final NativeHandle h2 = mDeps.createConntrackSocket(
@@ -267,16 +270,23 @@
     }
 
     @VisibleForTesting
-    public void sendNetlinkMessage(@NonNull NativeHandle handle, short type, short flags) {
-        final int length = StructNlMsgHdr.STRUCT_SIZE;
+    public void sendIpv4NfGenMsg(@NonNull NativeHandle handle, short type, short flags) {
+        final int length = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE;
         final byte[] msg = new byte[length];
-        final StructNlMsgHdr nlh = new StructNlMsgHdr();
         final ByteBuffer byteBuffer = ByteBuffer.wrap(msg);
+        byteBuffer.order(ByteOrder.nativeOrder());
+
+        final StructNlMsgHdr nlh = new StructNlMsgHdr();
         nlh.nlmsg_len = length;
         nlh.nlmsg_type = type;
         nlh.nlmsg_flags = flags;
-        nlh.nlmsg_seq = 1;
+        nlh.nlmsg_seq = 0;
         nlh.pack(byteBuffer);
+
+        // Header needs to be added to buffer since a generic netlink request is being sent.
+        final StructNfGenMsg nfh = new StructNfGenMsg((byte) OsConstants.AF_INET);
+        nfh.pack(byteBuffer);
+
         try {
             NetlinkSocket.sendMessage(handle.getFileDescriptor(), msg, 0 /* offset */, length,
                                       NETLINK_MESSAGE_TIMEOUT_MS);
diff --git a/packages/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java b/packages/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
new file mode 100644
index 0000000..57c28fc
--- /dev/null
+++ b/packages/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2020 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.networkstack.tethering;
+
+import static android.net.netlink.NetlinkSocket.DEFAULT_RECV_BUFSIZE;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
+
+import static com.android.networkstack.tethering.OffloadHardwareInterface.IPCTNL_MSG_CT_GET;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.IPCTNL_MSG_CT_NEW;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.NFNL_SUBSYS_CTNETLINK;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.NF_NETLINK_CONNTRACK_DESTROY;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.NF_NETLINK_CONNTRACK_NEW;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.netlink.StructNlMsgHdr;
+import android.net.util.SharedLog;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.NativeHandle;
+import android.system.Os;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ConntrackSocketTest {
+    private static final long TIMEOUT = 500;
+
+    private HandlerThread mHandlerThread;
+    private Handler mHandler;
+    private final SharedLog mLog = new SharedLog("privileged-test");
+
+    private OffloadHardwareInterface mOffloadHw;
+    private OffloadHardwareInterface.Dependencies mDeps;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mHandlerThread = new HandlerThread(getClass().getSimpleName());
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+
+        // Looper must be prepared here since AndroidJUnitRunner runs tests on separate threads.
+        if (Looper.myLooper() == null) Looper.prepare();
+
+        mDeps = new OffloadHardwareInterface.Dependencies(mLog);
+        mOffloadHw = new OffloadHardwareInterface(mHandler, mLog, mDeps);
+    }
+
+    @Test
+    public void testIpv4ConntrackSocket() throws Exception {
+        // Set up server and connect.
+        final InetSocketAddress anyAddress = new InetSocketAddress(
+                InetAddress.getByName("127.0.0.1"), 0);
+        final ServerSocket serverSocket = new ServerSocket();
+        serverSocket.bind(anyAddress);
+        final SocketAddress theAddress = serverSocket.getLocalSocketAddress();
+
+        // Make a connection to the server.
+        final Socket socket = new Socket();
+        socket.connect(theAddress);
+        final Socket acceptedSocket = serverSocket.accept();
+
+        final NativeHandle handle = mDeps.createConntrackSocket(
+                NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY);
+        mOffloadHw.sendIpv4NfGenMsg(handle,
+                (short) ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET),
+                (short) (NLM_F_REQUEST | NLM_F_DUMP));
+
+        boolean foundConntrackEntry = false;
+        ByteBuffer buffer = ByteBuffer.allocate(DEFAULT_RECV_BUFSIZE);
+        buffer.order(ByteOrder.nativeOrder());
+
+        try {
+            while (Os.read(handle.getFileDescriptor(), buffer) > 0) {
+                buffer.flip();
+
+                // TODO: ConntrackMessage should get a parse API like StructNlMsgHdr
+                // so we can confirm that the conntrack added is for the TCP connection above.
+                final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(buffer);
+                assertNotNull(nlmsghdr);
+
+                // As long as 1 conntrack entry is found test case will pass, even if it's not
+                // the from the TCP connection above.
+                if (nlmsghdr.nlmsg_type == ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_NEW)) {
+                    foundConntrackEntry = true;
+                    break;
+                }
+            }
+        } finally {
+            socket.close();
+            serverSocket.close();
+        }
+        assertTrue("Did not receive any NFNL_SUBSYS_CTNETLINK/IPCTNL_MSG_CT_NEW message",
+                foundConntrackEntry);
+    }
+}
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
index c543fad..38b19dd 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
@@ -17,8 +17,9 @@
 package com.android.networkstack.tethering;
 
 import static android.net.util.TetheringUtils.uint16;
-import static android.system.OsConstants.SOCK_STREAM;
+import static android.system.OsConstants.AF_INET;
 import static android.system.OsConstants.AF_UNIX;
+import static android.system.OsConstants.SOCK_STREAM;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -35,14 +36,15 @@
 import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate;
 import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
 import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent;
+import android.net.netlink.StructNfGenMsg;
 import android.net.netlink.StructNlMsgHdr;
 import android.net.util.SharedLog;
 import android.os.Handler;
 import android.os.NativeHandle;
 import android.os.test.TestLooper;
 import android.system.ErrnoException;
-import android.system.OsConstants;
 import android.system.Os;
+import android.system.OsConstants;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -55,8 +57,8 @@
 import org.mockito.MockitoAnnotations;
 
 import java.io.FileDescriptor;
-import java.io.OutputStream;
 import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import java.util.ArrayList;
 
 @RunWith(AndroidJUnit4.class)
@@ -218,7 +220,7 @@
     }
 
     @Test
-    public void testNetlinkMessage() throws Exception {
+    public void testSendIpv4NfGenMsg() throws Exception {
         FileDescriptor writeSocket = new FileDescriptor();
         FileDescriptor readSocket = new FileDescriptor();
         try {
@@ -229,17 +231,25 @@
         }
         when(mNativeHandle.getFileDescriptor()).thenReturn(writeSocket);
 
-        mOffloadHw.sendNetlinkMessage(mNativeHandle, TEST_TYPE, TEST_FLAGS);
+        mOffloadHw.sendIpv4NfGenMsg(mNativeHandle, TEST_TYPE, TEST_FLAGS);
 
-        ByteBuffer buffer = ByteBuffer.allocate(StructNlMsgHdr.STRUCT_SIZE);
+        ByteBuffer buffer = ByteBuffer.allocate(9823);  // Arbitrary value > expectedLen.
+        buffer.order(ByteOrder.nativeOrder());
+
         int read = Os.read(readSocket, buffer);
+        final int expectedLen = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE;
+        assertEquals(expectedLen, read);
 
         buffer.flip();
-        assertEquals(StructNlMsgHdr.STRUCT_SIZE, buffer.getInt());
+        assertEquals(expectedLen, buffer.getInt());
         assertEquals(TEST_TYPE, buffer.getShort());
         assertEquals(TEST_FLAGS, buffer.getShort());
-        assertEquals(1 /* seq */, buffer.getInt());
+        assertEquals(0 /* seq */, buffer.getInt());
         assertEquals(0 /* pid */, buffer.getInt());
+        assertEquals(AF_INET, buffer.get());             // nfgen_family
+        assertEquals(0 /* error */, buffer.get());       // version
+        assertEquals(0 /* error */, buffer.getShort());  // res_id
+        assertEquals(expectedLen, buffer.position());
     }
 
     private NatTimeoutUpdate buildNatTimeoutUpdate(final int proto) {
diff --git a/packages/overlays/IconShapePebbleOverlay/AndroidManifest.xml b/packages/overlays/IconShapePebbleOverlay/AndroidManifest.xml
index 6842dde..d719a97 100644
--- a/packages/overlays/IconShapePebbleOverlay/AndroidManifest.xml
+++ b/packages/overlays/IconShapePebbleOverlay/AndroidManifest.xml
@@ -21,7 +21,6 @@
     android:versionName="1.0">
 <overlay
         android:targetPackage="android"
-        android:targetName="IconShapeCustomization"
         android:category="android.theme.customization.adaptive_icon_shape"
         android:priority="1"/>
 
diff --git a/services/backup/java/com/android/server/backup/DataChangedJournal.java b/services/backup/java/com/android/server/backup/DataChangedJournal.java
index 0e7fc93..4eb1d9a 100644
--- a/services/backup/java/com/android/server/backup/DataChangedJournal.java
+++ b/services/backup/java/com/android/server/backup/DataChangedJournal.java
@@ -40,7 +40,7 @@
  * <p>This information is persisted to the filesystem so that it is not lost in the event of a
  * reboot.
  */
-public final class DataChangedJournal {
+public class DataChangedJournal {
     private static final String TAG = "DataChangedJournal";
     private static final String FILE_NAME_PREFIX = "journal";
 
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index eb60573..6c08826 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -712,8 +712,6 @@
             Slog.i(TAG, "Stopping pre-created user " + userInfo.toFullString());
             // Pre-created user was started right after creation so services could properly
             // intialize it; it should be stopped right away as it's not really a "real" user.
-            // TODO(b/143092698): in the long-term, it might be better to add a onCreateUser()
-            // callback on SystemService instead.
             stopUser(userInfo.id, /* force= */ true, /* allowDelayedLocking= */ false,
                     /* stopUserCallback= */ null, /* keyEvictedCallback= */ null);
             return;
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 668713f..c1777b8 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -584,29 +584,6 @@
                     // process is not in foreground.
                     return MODE_IGNORED;
                 }
-            } else if (mode == MODE_ALLOWED) {
-                switch (op) {
-                    case OP_CAMERA:
-                        if (mActivityManagerInternal != null
-                                && mActivityManagerInternal.isPendingTopUid(uid)) {
-                            return MODE_ALLOWED;
-                        } else if ((capability & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) {
-                            return MODE_ALLOWED;
-                        } else {
-                            return MODE_IGNORED;
-                        }
-                    case OP_RECORD_AUDIO:
-                        if (mActivityManagerInternal != null
-                                && mActivityManagerInternal.isPendingTopUid(uid)) {
-                            return MODE_ALLOWED;
-                        } else if ((capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0) {
-                            return MODE_ALLOWED;
-                        } else {
-                            return MODE_IGNORED;
-                        }
-                    default:
-                        return MODE_ALLOWED;
-                }
             }
             return mode;
         }
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 72734c4..0329c3c 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -23,10 +23,10 @@
 import static android.location.LocationManager.FUSED_PROVIDER;
 import static android.location.LocationManager.GPS_PROVIDER;
 import static android.location.LocationManager.NETWORK_PROVIDER;
+import static android.location.LocationRequest.LOW_POWER_EXCEPTIONS;
 
 import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
 import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
-import static com.android.server.location.LocationProviderManager.FASTEST_COARSE_INTERVAL_MS;
 
 import static java.util.concurrent.TimeUnit.NANOSECONDS;
 
@@ -36,6 +36,7 @@
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
+import android.app.compat.CompatChanges;
 import android.content.Context;
 import android.content.Intent;
 import android.location.Criteria;
@@ -82,12 +83,12 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
-import com.android.server.location.LocationPermissions.PermissionLevel;
 import com.android.server.location.LocationRequestStatistics.PackageProviderKey;
 import com.android.server.location.LocationRequestStatistics.PackageStatistics;
 import com.android.server.location.geofence.GeofenceManager;
 import com.android.server.location.geofence.GeofenceProxy;
 import com.android.server.location.gnss.GnssManagerService;
+import com.android.server.location.util.AlarmHelper;
 import com.android.server.location.util.AppForegroundHelper;
 import com.android.server.location.util.AppOpsHelper;
 import com.android.server.location.util.Injector;
@@ -97,6 +98,7 @@
 import com.android.server.location.util.LocationUsageLogger;
 import com.android.server.location.util.ScreenInteractiveHelper;
 import com.android.server.location.util.SettingsHelper;
+import com.android.server.location.util.SystemAlarmHelper;
 import com.android.server.location.util.SystemAppForegroundHelper;
 import com.android.server.location.util.SystemAppOpsHelper;
 import com.android.server.location.util.SystemLocationPermissionsHelper;
@@ -569,7 +571,7 @@
                     new IllegalArgumentException());
         }
 
-        request = validateAndSanitizeLocationRequest(request, permissionLevel);
+        request = validateLocationRequest(request);
 
         LocationProviderManager manager = getLocationProviderManager(provider);
         Preconditions.checkArgument(manager != null,
@@ -591,7 +593,7 @@
         // clients in the system process must have an attribution tag set
         Preconditions.checkArgument(identity.getPid() != Process.myPid() || attributionTag != null);
 
-        request = validateAndSanitizeLocationRequest(request, permissionLevel);
+        request = validateLocationRequest(request);
 
         LocationProviderManager manager = getLocationProviderManager(provider);
         Preconditions.checkArgument(manager != null,
@@ -600,8 +602,7 @@
         manager.registerLocationRequest(request, identity, permissionLevel, pendingIntent);
     }
 
-    private LocationRequest validateAndSanitizeLocationRequest(LocationRequest request,
-            @PermissionLevel int permissionLevel) {
+    private LocationRequest validateLocationRequest(LocationRequest request) {
         WorkSource workSource = request.getWorkSource();
         if (workSource != null && !workSource.isEmpty()) {
             mContext.enforceCallingOrSelfPermission(
@@ -620,26 +621,20 @@
         }
 
         LocationRequest.Builder sanitized = new LocationRequest.Builder(request);
-        if (mContext.checkCallingPermission(permission.LOCATION_HARDWARE) != PERMISSION_GRANTED) {
-            sanitized.setLowPower(false);
-        }
-        if (permissionLevel < PERMISSION_FINE) {
-            switch (request.getQuality()) {
-                case LocationRequest.ACCURACY_FINE:
-                    sanitized.setQuality(LocationRequest.ACCURACY_BLOCK);
-                    break;
-                case LocationRequest.POWER_HIGH:
-                    sanitized.setQuality(LocationRequest.POWER_LOW);
-                    break;
-            }
 
-            if (request.getIntervalMillis() < FASTEST_COARSE_INTERVAL_MS) {
-                sanitized.setIntervalMillis(FASTEST_COARSE_INTERVAL_MS);
+        if (CompatChanges.isChangeEnabled(LOW_POWER_EXCEPTIONS, Binder.getCallingUid())) {
+            if (request.isLowPower()) {
+                mContext.enforceCallingOrSelfPermission(
+                        permission.LOCATION_HARDWARE,
+                        "low power request requires " + permission.LOCATION_HARDWARE);
             }
-            if (request.getMinUpdateIntervalMillis() < FASTEST_COARSE_INTERVAL_MS) {
-                sanitized.clearMinUpdateIntervalMillis();
+        } else {
+            if (mContext.checkCallingPermission(permission.LOCATION_HARDWARE)
+                    != PERMISSION_GRANTED) {
+                sanitized.setLowPower(false);
             }
         }
+
         if (request.getWorkSource() != null) {
             if (request.getWorkSource().isEmpty()) {
                 sanitized.setWorkSource(null);
@@ -716,7 +711,7 @@
         // clients in the system process must have an attribution tag set
         Preconditions.checkState(identity.getPid() != Process.myPid() || attributionTag != null);
 
-        request = validateAndSanitizeLocationRequest(request, permissionLevel);
+        request = validateLocationRequest(request);
 
         LocationProviderManager manager = getLocationProviderManager(provider);
         Preconditions.checkArgument(manager != null,
@@ -735,7 +730,7 @@
 
             // use fine permission level to avoid creating unnecessary coarse locations
             Location location = gpsManager.getLastLocationUnsafe(UserHandle.USER_ALL,
-                    PERMISSION_FINE, false);
+                    PERMISSION_FINE, false, Long.MAX_VALUE);
             if (location == null) {
                 return null;
             }
@@ -1237,6 +1232,7 @@
     private static class SystemInjector implements Injector {
 
         private final UserInfoHelper mUserInfoHelper;
+        private final AlarmHelper mAlarmHelper;
         private final SystemAppOpsHelper mAppOpsHelper;
         private final SystemLocationPermissionsHelper mLocationPermissionsHelper;
         private final SystemSettingsHelper mSettingsHelper;
@@ -1249,6 +1245,7 @@
 
         SystemInjector(Context context, UserInfoHelper userInfoHelper) {
             mUserInfoHelper = userInfoHelper;
+            mAlarmHelper = new SystemAlarmHelper(context);
             mAppOpsHelper = new SystemAppOpsHelper(context);
             mLocationPermissionsHelper = new SystemLocationPermissionsHelper(context,
                     mAppOpsHelper);
@@ -1276,6 +1273,11 @@
         }
 
         @Override
+        public AlarmHelper getAlarmHelper() {
+            return mAlarmHelper;
+        }
+
+        @Override
         public AppOpsHelper getAppOpsHelper() {
             return mAppOpsHelper;
         }
diff --git a/services/core/java/com/android/server/location/LocationProviderManager.java b/services/core/java/com/android/server/location/LocationProviderManager.java
index 138301a..cd8bf4a 100644
--- a/services/core/java/com/android/server/location/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/LocationProviderManager.java
@@ -16,13 +16,14 @@
 
 package com.android.server.location;
 
-import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
-import static android.app.AlarmManager.WINDOW_EXACT;
+import static android.app.compat.CompatChanges.isChangeEnabled;
+import static android.location.LocationManager.DELIVER_HISTORICAL_LOCATIONS;
 import static android.location.LocationManager.FUSED_PROVIDER;
 import static android.location.LocationManager.GPS_PROVIDER;
 import static android.location.LocationManager.KEY_LOCATION_CHANGED;
 import static android.location.LocationManager.KEY_PROVIDER_ENABLED;
 import static android.location.LocationManager.PASSIVE_PROVIDER;
+import static android.location.LocationRequest.PASSIVE_INTERVAL;
 import static android.os.IPowerManager.LOCATION_MODE_NO_CHANGE;
 import static android.os.PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF;
 import static android.os.PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
@@ -36,11 +37,11 @@
 import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
 import static com.android.server.location.LocationPermissions.PERMISSION_NONE;
 
+import static java.lang.Math.max;
 import static java.lang.Math.min;
-import static java.util.concurrent.TimeUnit.NANOSECONDS;
 
 import android.annotation.Nullable;
-import android.app.AlarmManager;
+import android.app.AlarmManager.OnAlarmListener;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
@@ -88,6 +89,7 @@
 import com.android.server.location.LocationPermissions.PermissionLevel;
 import com.android.server.location.listeners.ListenerMultiplexer;
 import com.android.server.location.listeners.RemoteListenerRegistration;
+import com.android.server.location.util.AlarmHelper;
 import com.android.server.location.util.AppForegroundHelper;
 import com.android.server.location.util.AppForegroundHelper.AppForegroundListener;
 import com.android.server.location.util.AppOpsHelper;
@@ -118,12 +120,12 @@
                 LocationProviderManager.Registration, ProviderRequest> implements
         AbstractLocationProvider.Listener {
 
-    // fastest interval at which clients may receive coarse locations
-    public static final long FASTEST_COARSE_INTERVAL_MS = 10 * 60 * 1000;
-
     private static final String WAKELOCK_TAG = "*location*";
     private static final long WAKELOCK_TIMEOUT_MS = 30 * 1000;
 
+    // fastest interval at which clients may receive coarse locations
+    private static final long MIN_COARSE_INTERVAL_MS = 10 * 60 * 1000;
+
     // max interval to be considered "high power" request
     private static final long MAX_HIGH_POWER_INTERVAL_MS = 5 * 60 * 1000;
 
@@ -133,8 +135,15 @@
     // max timeout allowed for getting the current location
     private static final long GET_CURRENT_LOCATION_MAX_TIMEOUT_MS = 30 * 1000;
 
-    // max jitter allowed for fastest interval evaluation
-    private static final int MAX_FASTEST_INTERVAL_JITTER_MS = 100;
+    // max jitter allowed for min update interval as a percentage of the interval
+    private static final float FASTEST_INTERVAL_JITTER_PERCENTAGE = .10f;
+
+    // max absolute jitter allowed for min update interval evaluation
+    private static final int MAX_FASTEST_INTERVAL_JITTER_MS = 5 * 1000;
+
+    // minimum amount of request delay in order to respect the delay, below this value the request
+    // will just be scheduled immediately
+    private static final long MIN_REQUEST_DELAY_MS = 30 * 1000;
 
     protected interface LocationTransport {
 
@@ -221,6 +230,7 @@
         /**
          * Must be implemented to return the location this operation intends to deliver.
          */
+        @Nullable
         Location getLocation();
     }
 
@@ -312,7 +322,7 @@
                 mLocationAttributionHelper.reportLocationStart(getIdentity(), getName(), getKey());
             }
             onHighPowerUsageChanged();
-            return null;
+            return onProviderListenerActive();
         }
 
         @Override
@@ -325,6 +335,22 @@
             if (!getRequest().isHiddenFromAppOps()) {
                 mLocationAttributionHelper.reportLocationStop(getIdentity(), getName(), getKey());
             }
+            return onProviderListenerInactive();
+        }
+
+        /**
+         * Subclasses may override this instead of {@link #onActive()}.
+         */
+        @GuardedBy("mLock")
+        protected LocationListenerOperation onProviderListenerActive() {
+            return null;
+        }
+
+        /**
+         * Subclasses may override this instead of {@link #onInactive()} ()}.
+         */
+        @GuardedBy("mLock")
+        protected LocationListenerOperation onProviderListenerInactive() {
             return null;
         }
 
@@ -333,6 +359,14 @@
             return mProviderLocationRequest;
         }
 
+        @GuardedBy("mLock")
+        final void initializeLastLocation(@Nullable Location location) {
+            if (mLastLocation == null) {
+                mLastLocation = location;
+            }
+        }
+
+        @GuardedBy("mLock")
         public final Location getLastDeliveredLocation() {
             return mLastLocation;
         }
@@ -465,9 +499,27 @@
         }
 
         private LocationRequest calculateProviderLocationRequest() {
-            LocationRequest.Builder builder = new LocationRequest.Builder(super.getRequest());
+            LocationRequest baseRequest = super.getRequest();
+            LocationRequest.Builder builder = new LocationRequest.Builder(baseRequest);
 
-            if (super.getRequest().isLocationSettingsIgnored()) {
+            if (mPermissionLevel < PERMISSION_FINE) {
+                switch (baseRequest.getQuality()) {
+                    case LocationRequest.ACCURACY_FINE:
+                        builder.setQuality(LocationRequest.ACCURACY_BLOCK);
+                        break;
+                    case LocationRequest.POWER_HIGH:
+                        builder.setQuality(LocationRequest.POWER_LOW);
+                        break;
+                }
+                if (baseRequest.getIntervalMillis() < MIN_COARSE_INTERVAL_MS) {
+                    builder.setIntervalMillis(MIN_COARSE_INTERVAL_MS);
+                }
+                if (baseRequest.getMinUpdateIntervalMillis() < MIN_COARSE_INTERVAL_MS) {
+                    builder.clearMinUpdateIntervalMillis();
+                }
+            }
+
+            if (baseRequest.isLocationSettingsIgnored()) {
                 // if we are not currently allowed use location settings ignored, disable it
                 if (!mSettingsHelper.getIgnoreSettingsPackageWhitelist().contains(
                         getIdentity().getPackageName()) && !mLocationManagerInternal.isProvider(
@@ -476,10 +528,10 @@
                 }
             }
 
-            if (!super.getRequest().isLocationSettingsIgnored() && !isThrottlingExempt()) {
+            if (!baseRequest.isLocationSettingsIgnored() && !isThrottlingExempt()) {
                 // throttle in the background
                 if (!mForeground) {
-                    builder.setIntervalMillis(Math.max(super.getRequest().getIntervalMillis(),
+                    builder.setIntervalMillis(max(baseRequest.getIntervalMillis(),
                             mSettingsHelper.getBackgroundThrottleIntervalMs()));
                 }
             }
@@ -534,7 +586,7 @@
     }
 
     protected abstract class LocationRegistration extends Registration implements
-            AlarmManager.OnAlarmListener, ProviderEnabledListener {
+            OnAlarmListener, ProviderEnabledListener {
 
         private final PowerManager.WakeLock mWakeLock;
 
@@ -561,17 +613,15 @@
         @GuardedBy("mLock")
         @Override
         protected final void onProviderListenerRegister() {
-            mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs(
-                    SystemClock.elapsedRealtime());
+            long registerTimeMs = SystemClock.elapsedRealtime();
+            mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs(registerTimeMs);
 
             // add alarm for expiration
-            if (mExpirationRealtimeMs < SystemClock.elapsedRealtime()) {
-                remove();
+            if (mExpirationRealtimeMs <= registerTimeMs) {
+                onAlarm();
             } else if (mExpirationRealtimeMs < Long.MAX_VALUE) {
-                AlarmManager alarmManager = Objects.requireNonNull(
-                        mContext.getSystemService(AlarmManager.class));
-                alarmManager.set(ELAPSED_REALTIME_WAKEUP, mExpirationRealtimeMs, WINDOW_EXACT,
-                        0, this, FgThread.getHandler(), getWorkSource());
+                mAlarmHelper.setDelayedAlarm(mExpirationRealtimeMs - registerTimeMs, this,
+                        getWorkSource());
             }
 
             // start listening for provider enabled/disabled events
@@ -594,9 +644,7 @@
 
             // remove alarm for expiration
             if (mExpirationRealtimeMs < Long.MAX_VALUE) {
-                AlarmManager alarmManager = Objects.requireNonNull(
-                        mContext.getSystemService(AlarmManager.class));
-                alarmManager.cancel(this);
+                mAlarmHelper.cancel(this);
             }
 
             onLocationListenerUnregister();
@@ -614,6 +662,39 @@
         @GuardedBy("mLock")
         protected void onLocationListenerUnregister() {}
 
+        @GuardedBy("mLock")
+        @Override
+        protected final LocationListenerOperation onProviderListenerActive() {
+            // a new registration may not get a location immediately, the provider request may be
+            // delayed. therefore we deliver a historical location if available. since delivering an
+            // older location could be considered a breaking change for some applications, we only
+            // do so for apps targeting S+.
+            if (isChangeEnabled(DELIVER_HISTORICAL_LOCATIONS, getIdentity().getUid())) {
+                long maxLocationAgeMs = getRequest().getIntervalMillis();
+                Location lastDeliveredLocation = getLastDeliveredLocation();
+                if (lastDeliveredLocation != null) {
+                    // ensure that location is fresher than the last delivered location
+                    maxLocationAgeMs = min(maxLocationAgeMs,
+                            lastDeliveredLocation.getElapsedRealtimeAgeMillis() - 1);
+                }
+
+                // requests are never delayed less than MIN_REQUEST_DELAY_MS, so it only makes sense
+                // to deliver historical locations to clients with a last location older than that
+                if (maxLocationAgeMs > MIN_REQUEST_DELAY_MS) {
+                    Location lastLocation = getLastLocationUnsafe(
+                            getIdentity().getUserId(),
+                            PERMISSION_FINE, // acceptLocationChange() handles coarsening this
+                            getRequest().isLocationSettingsIgnored(),
+                            maxLocationAgeMs);
+                    if (lastLocation != null) {
+                        return acceptLocationChange(lastLocation);
+                    }
+                }
+            }
+
+            return null;
+        }
+
         @Override
         public void onAlarm() {
             if (D) {
@@ -624,6 +705,8 @@
 
             synchronized (mLock) {
                 remove();
+                // no need to remove alarm after it's fired
+                mExpirationRealtimeMs = Long.MAX_VALUE;
             }
         }
 
@@ -658,11 +741,11 @@
             Location lastDeliveredLocation = getLastDeliveredLocation();
             if (lastDeliveredLocation != null) {
                 // check fastest interval
-                long deltaMs = NANOSECONDS.toMillis(
-                        location.getElapsedRealtimeNanos()
-                                - lastDeliveredLocation.getElapsedRealtimeNanos());
-                if (deltaMs < getRequest().getMinUpdateIntervalMillis()
-                        - MAX_FASTEST_INTERVAL_JITTER_MS) {
+                long deltaMs = location.getElapsedRealtimeMillis()
+                        - lastDeliveredLocation.getElapsedRealtimeMillis();
+                long maxJitterMs = min((long) (FASTEST_INTERVAL_JITTER_PERCENTAGE
+                        * getRequest().getIntervalMillis()), MAX_FASTEST_INTERVAL_JITTER_MS);
+                if (deltaMs < getRequest().getMinUpdateIntervalMillis() - maxJitterMs) {
                     return null;
                 }
 
@@ -871,7 +954,7 @@
     }
 
     protected final class GetCurrentLocationListenerRegistration extends Registration implements
-            IBinder.DeathRecipient, ProviderEnabledListener, AlarmManager.OnAlarmListener {
+            IBinder.DeathRecipient, ProviderEnabledListener, OnAlarmListener {
 
         private volatile LocationTransport mTransport;
 
@@ -902,15 +985,15 @@
                 remove();
             }
 
-            mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs(
-                    SystemClock.elapsedRealtime());
+            long registerTimeMs = SystemClock.elapsedRealtime();
+            mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs(registerTimeMs);
 
             // add alarm for expiration
-            if (mExpirationRealtimeMs < Long.MAX_VALUE) {
-                AlarmManager alarmManager = Objects.requireNonNull(
-                        mContext.getSystemService(AlarmManager.class));
-                alarmManager.set(ELAPSED_REALTIME_WAKEUP, mExpirationRealtimeMs, WINDOW_EXACT,
-                        0, this, FgThread.getHandler(), getWorkSource());
+            if (mExpirationRealtimeMs <= registerTimeMs) {
+                onAlarm();
+            } else if (mExpirationRealtimeMs < Long.MAX_VALUE) {
+                mAlarmHelper.setDelayedAlarm(mExpirationRealtimeMs - registerTimeMs, this,
+                        getWorkSource());
             }
 
             // if this request is ignoring location settings, then we don't want to immediately fail
@@ -935,9 +1018,7 @@
 
             // remove alarm for expiration
             if (mExpirationRealtimeMs < Long.MAX_VALUE) {
-                AlarmManager alarmManager = Objects.requireNonNull(
-                        mContext.getSystemService(AlarmManager.class));
-                alarmManager.cancel(this);
+                mAlarmHelper.cancel(this);
             }
 
             ((IBinder) getKey()).unlinkToDeath(this, 0);
@@ -953,6 +1034,8 @@
 
             synchronized (mLock) {
                 deliverLocation(null);
+                // no need to remove alarm after it's fired
+                mExpirationRealtimeMs = Long.MAX_VALUE;
             }
         }
 
@@ -964,6 +1047,12 @@
                 Preconditions.checkState(Thread.holdsLock(mLock));
             }
 
+            // check expiration time - alarm is not guaranteed to go off at the right time,
+            // especially for short intervals
+            if (SystemClock.elapsedRealtime() >= mExpirationRealtimeMs) {
+                fineLocation = null;
+            }
+
             // lastly - note app ops
             Location location;
             if (fineLocation == null) {
@@ -1077,6 +1166,7 @@
     protected final LocationManagerInternal mLocationManagerInternal;
     protected final SettingsHelper mSettingsHelper;
     protected final UserInfoHelper mUserInfoHelper;
+    protected final AlarmHelper mAlarmHelper;
     protected final AppOpsHelper mAppOpsHelper;
     protected final LocationPermissionsHelper mLocationPermissionsHelper;
     protected final AppForegroundHelper mAppForegroundHelper;
@@ -1120,6 +1210,9 @@
     // acquiring mLock makes operations on mProvider atomic, but is otherwise unnecessary
     protected final MockableLocationProvider mProvider;
 
+    @GuardedBy("mLock")
+    @Nullable private OnAlarmListener mDelayedRegister;
+
     LocationProviderManager(Context context, Injector injector, String name,
             @Nullable PassiveLocationProviderManager passiveManager) {
         mContext = context;
@@ -1135,6 +1228,7 @@
                 LocalServices.getService(LocationManagerInternal.class));
         mSettingsHelper = injector.getSettingsHelper();
         mUserInfoHelper = injector.getUserInfoHelper();
+        mAlarmHelper = injector.getAlarmHelper();
         mAppOpsHelper = injector.getAppOpsHelper();
         mLocationPermissionsHelper = injector.getLocationPermissionsHelper();
         mAppForegroundHelper = injector.getAppForegroundHelper();
@@ -1344,7 +1438,7 @@
         }
 
         Location location = getLastLocationUnsafe(identity.getUserId(), permissionLevel,
-                ignoreLocationSettings);
+                ignoreLocationSettings, Long.MAX_VALUE);
 
         // we don't note op here because we don't know what the client intends to do with the
         // location, the client is responsible for noting if necessary
@@ -1364,13 +1458,14 @@
      */
     @Nullable
     public Location getLastLocationUnsafe(int userId, @PermissionLevel int permissionLevel,
-            boolean ignoreLocationSettings) {
+            boolean ignoreLocationSettings, long maximumAgeMs) {
         if (userId == UserHandle.USER_ALL) {
+            // find the most recent location across all users
             Location lastLocation = null;
             final int[] runningUserIds = mUserInfoHelper.getRunningUserIds();
             for (int i = 0; i < runningUserIds.length; i++) {
                 Location next = getLastLocationUnsafe(runningUserIds[i], permissionLevel,
-                        ignoreLocationSettings);
+                        ignoreLocationSettings, maximumAgeMs);
                 if (lastLocation == null || (next != null && next.getElapsedRealtimeNanos()
                         > lastLocation.getElapsedRealtimeNanos())) {
                     lastLocation = next;
@@ -1381,18 +1476,30 @@
 
         Preconditions.checkArgument(userId >= 0);
 
+        Location location;
         synchronized (mLock) {
             LastLocation lastLocation = mLastLocations.get(userId);
             if (lastLocation == null) {
-                return null;
+                location = null;
+            } else {
+                location = lastLocation.get(permissionLevel, ignoreLocationSettings);
             }
-            return lastLocation.get(permissionLevel, ignoreLocationSettings);
         }
+
+        if (location == null) {
+            return null;
+        }
+
+        if (location.getElapsedRealtimeAgeMillis() > maximumAgeMs) {
+            return null;
+        }
+
+        return location;
     }
 
     public void injectLastLocation(Location location, int userId) {
         synchronized (mLock) {
-            if (getLastLocationUnsafe(userId, PERMISSION_FINE, false) == null) {
+            if (getLastLocationUnsafe(userId, PERMISSION_FINE, false, Long.MAX_VALUE) == null) {
                 setLastLocation(location, userId);
             }
         }
@@ -1455,22 +1562,14 @@
                 return null;
             }
 
-            Location lastLocation = getLastLocationUnsafe(callerIdentity.getUserId(),
-                    permissionLevel, request.isLocationSettingsIgnored());
+            Location lastLocation = getLastLocationUnsafe(
+                    callerIdentity.getUserId(),
+                    permissionLevel,
+                    request.isLocationSettingsIgnored(),
+                    MAX_CURRENT_LOCATION_AGE_MS);
             if (lastLocation != null) {
-                long locationAgeMs = NANOSECONDS.toMillis(
-                        SystemClock.elapsedRealtimeNanos()
-                                - lastLocation.getElapsedRealtimeNanos());
-                if (locationAgeMs < MAX_CURRENT_LOCATION_AGE_MS) {
-                    registration.deliverLocation(lastLocation);
-                    return null;
-                }
-
-                if (!mAppForegroundHelper.isAppForeground(Binder.getCallingUid())
-                        && locationAgeMs < mSettingsHelper.getBackgroundThrottleIntervalMs()) {
-                    registration.deliverLocation(null);
-                    return null;
-                }
+                registration.deliverLocation(lastLocation);
+                return null;
             }
 
             // if last location isn't good enough then we add a location request
@@ -1629,6 +1728,16 @@
 
     @GuardedBy("mLock")
     @Override
+    protected void onRegistrationReplaced(Object key, Registration oldRegistration,
+            Registration newRegistration) {
+        // by saving the last delivered location state we are able to potentially delay the
+        // resulting provider request longer and save additional power
+        newRegistration.initializeLastLocation(oldRegistration.getLastDeliveredLocation());
+        super.onRegistrationReplaced(key, oldRegistration, newRegistration);
+    }
+
+    @GuardedBy("mLock")
+    @Override
     protected void onRegistrationRemoved(Object key, Registration registration) {
         if (Build.IS_DEBUGGABLE) {
             Preconditions.checkState(Thread.holdsLock(mLock));
@@ -1652,21 +1761,61 @@
 
     @GuardedBy("mLock")
     @Override
-    protected boolean registerWithService(ProviderRequest mergedRequest,
+    protected boolean registerWithService(ProviderRequest request,
             Collection<Registration> registrations) {
-        if (Build.IS_DEBUGGABLE) {
-            Preconditions.checkState(Thread.holdsLock(mLock));
-        }
-
-        mProvider.setRequest(mergedRequest);
-        return true;
+        return reregisterWithService(EMPTY_REQUEST, request, registrations);
     }
 
     @GuardedBy("mLock")
     @Override
     protected boolean reregisterWithService(ProviderRequest oldRequest,
             ProviderRequest newRequest, Collection<Registration> registrations) {
-        return registerWithService(newRequest, registrations);
+        if (Build.IS_DEBUGGABLE) {
+            Preconditions.checkState(Thread.holdsLock(mLock));
+        }
+
+        if (mDelayedRegister != null) {
+            mAlarmHelper.cancel(mDelayedRegister);
+            mDelayedRegister = null;
+        }
+
+        // calculate how long the new request should be delayed before sending it off to the
+        // provider, under the assumption that once we send the request off, the provider will
+        // immediately attempt to deliver a new location satisfying that request.
+        long delayMs;
+        if (!oldRequest.isLocationSettingsIgnored() && newRequest.isLocationSettingsIgnored()) {
+            delayMs = 0;
+        } else if (newRequest.getIntervalMillis() > oldRequest.getIntervalMillis()) {
+            // if the interval has increased, tell the provider immediately, so it can save power
+            // (even though technically this could burn extra power in the short term by producing
+            // an extra location - the provider itself is free to detect an increasing interval and
+            // delay its own location)
+            delayMs = 0;
+        } else {
+            delayMs = calculateRequestDelayMillis(newRequest.getIntervalMillis(), registrations);
+        }
+
+        // the delay should never exceed the new interval
+        Preconditions.checkState(delayMs >= 0 && delayMs <= newRequest.getIntervalMillis());
+
+        if (delayMs < MIN_REQUEST_DELAY_MS) {
+            mProvider.setRequest(newRequest);
+        } else {
+            mDelayedRegister = new OnAlarmListener() {
+                @Override
+                public void onAlarm() {
+                    synchronized (mLock) {
+                        if (mDelayedRegister == this) {
+                            mProvider.setRequest(newRequest);
+                            mDelayedRegister = null;
+                        }
+                    }
+                }
+            };
+            mAlarmHelper.setDelayedAlarm(delayMs, mDelayedRegister, newRequest.getWorkSource());
+        }
+
+        return true;
     }
 
     @GuardedBy("mLock")
@@ -1733,42 +1882,40 @@
             Preconditions.checkState(Thread.holdsLock(mLock));
         }
 
-        ArrayList<Registration> providerRegistrations = new ArrayList<>(registrations.size());
-
         long intervalMs = Long.MAX_VALUE;
         boolean locationSettingsIgnored = false;
         boolean lowPower = true;
         ArrayList<LocationRequest> locationRequests = new ArrayList<>(registrations.size());
-        for (Registration registration : registrations) {
-            LocationRequest locationRequest = registration.getRequest();
 
-            // passive requests do not contribute to the provider
-            if (locationRequest.getIntervalMillis() == LocationRequest.PASSIVE_INTERVAL) {
+        for (Registration registration : registrations) {
+            LocationRequest request = registration.getRequest();
+
+            // passive requests do not contribute to the provider request
+            if (request.getIntervalMillis() == PASSIVE_INTERVAL) {
                 continue;
             }
 
-            providerRegistrations.add(registration);
-            intervalMs = min(locationRequest.getIntervalMillis(), intervalMs);
-            locationSettingsIgnored |= locationRequest.isLocationSettingsIgnored();
-            lowPower &= locationRequest.isLowPower();
-            locationRequests.add(locationRequest);
+            intervalMs = min(request.getIntervalMillis(), intervalMs);
+            locationSettingsIgnored |= request.isLocationSettingsIgnored();
+            lowPower &= request.isLowPower();
+            locationRequests.add(request);
         }
 
         // calculate who to blame for power in a somewhat arbitrary fashion. we pick a threshold
         // interval slightly higher that the minimum interval, and spread the blame across all
         // contributing registrations under that threshold (since worksource does not allow us to
         // represent differing power blame ratios).
-        WorkSource workSource = new WorkSource();
         long thresholdIntervalMs = (intervalMs + 1000) * 3 / 2;
-        if (thresholdIntervalMs < 0) {
-            // handle overflow by setting to one below the passive interval
-            thresholdIntervalMs = Long.MAX_VALUE - 1;
+        if (thresholdIntervalMs < 0 || thresholdIntervalMs >= PASSIVE_INTERVAL) {
+            // check for and handle overflow by setting to one below the passive interval so passive
+            // requests are automatically skipped
+            thresholdIntervalMs = PASSIVE_INTERVAL - 1;
         }
-        final int providerRegistrationsSize = providerRegistrations.size();
-        for (int i = 0; i < providerRegistrationsSize; i++) {
-            Registration registration = providerRegistrations.get(i);
+
+        WorkSource workSource = new WorkSource();
+        for (Registration registration : registrations) {
             if (registration.getRequest().getIntervalMillis() <= thresholdIntervalMs) {
-                workSource.add(providerRegistrations.get(i).getWorkSource());
+                workSource.add(registration.getWorkSource());
             }
         }
 
@@ -1781,6 +1928,47 @@
                 .build();
     }
 
+    @GuardedBy("mLock")
+    protected long calculateRequestDelayMillis(long newIntervalMs,
+            Collection<Registration> registrations) {
+        // calculate the minimum delay across all registrations, ensuring that it is not more than
+        // the requested interval
+        long delayMs = newIntervalMs;
+        for (Registration registration : registrations) {
+            if (delayMs == 0) {
+                break;
+            }
+
+            LocationRequest locationRequest = registration.getRequest();
+            Location last = registration.getLastDeliveredLocation();
+
+            if (last == null && !locationRequest.isLocationSettingsIgnored()) {
+                // if this request has never gotten any location and it's not ignoring location
+                // settings, then we pretend that this request has gotten the last applicable cached
+                // location for our calculations instead. this prevents spammy add/remove behavior
+                last = getLastLocationUnsafe(
+                        registration.getIdentity().getUserId(),
+                        PERMISSION_FINE,
+                        false,
+                        locationRequest.getIntervalMillis());
+            }
+
+            long registrationDelayMs;
+            if (last == null) {
+                // if this request has never gotten any location then there's no delay
+                registrationDelayMs = 0;
+            } else {
+                // otherwise the delay is the amount of time until the next location is expected
+                registrationDelayMs = max(0,
+                        locationRequest.getIntervalMillis() - last.getElapsedRealtimeAgeMillis());
+            }
+
+            delayMs = min(delayMs, registrationDelayMs);
+        }
+
+        return delayMs;
+    }
+
     private void onUserChanged(int userId, int change) {
         synchronized (mLock) {
             switch (change) {
@@ -2068,7 +2256,7 @@
                     ipw.increaseIndent();
                 }
                 ipw.print("last location=");
-                ipw.println(getLastLocationUnsafe(userId, PERMISSION_FINE, false));
+                ipw.println(getLastLocationUnsafe(userId, PERMISSION_FINE, false, Long.MAX_VALUE));
                 ipw.print("enabled=");
                 ipw.println(isEnabled(userId));
                 if (userIds.length != 1) {
@@ -2126,24 +2314,37 @@
             }
         }
 
-        public void set(Location location, Location coarseLocation) {
-            mFineLocation = location;
+        public void set(Location fineLocation, Location coarseLocation) {
+            mFineLocation = calculateNextFine(mFineLocation, fineLocation);
             mCoarseLocation = calculateNextCoarse(mCoarseLocation, coarseLocation);
         }
 
-        public void setBypass(Location location, Location coarseLocation) {
-            mFineBypassLocation = location;
+        public void setBypass(Location fineLocation, Location coarseLocation) {
+            mFineBypassLocation = calculateNextFine(mFineBypassLocation, fineLocation);
             mCoarseBypassLocation = calculateNextCoarse(mCoarseBypassLocation, coarseLocation);
         }
 
+        private Location calculateNextFine(@Nullable Location oldFine, Location newFine) {
+            if (oldFine == null) {
+                return newFine;
+            }
+
+            // update last fine interval only if more recent
+            if (newFine.getElapsedRealtimeNanos() > oldFine.getElapsedRealtimeNanos()) {
+                return newFine;
+            } else {
+                return oldFine;
+            }
+        }
+
         private Location calculateNextCoarse(@Nullable Location oldCoarse, Location newCoarse) {
             if (oldCoarse == null) {
                 return newCoarse;
             }
+
             // update last coarse interval only if enough time has passed
-            long timeDeltaMs = NANOSECONDS.toMillis(newCoarse.getElapsedRealtimeNanos())
-                    - NANOSECONDS.toMillis(oldCoarse.getElapsedRealtimeNanos());
-            if (timeDeltaMs > FASTEST_COARSE_INTERVAL_MS) {
+            if (newCoarse.getElapsedRealtimeNanos() - MIN_COARSE_INTERVAL_MS
+                    > oldCoarse.getElapsedRealtimeNanos()) {
                 return newCoarse;
             } else {
                 return oldCoarse;
diff --git a/services/core/java/com/android/server/location/PassiveLocationProviderManager.java b/services/core/java/com/android/server/location/PassiveLocationProviderManager.java
index afeb644..2870d41 100644
--- a/services/core/java/com/android/server/location/PassiveLocationProviderManager.java
+++ b/services/core/java/com/android/server/location/PassiveLocationProviderManager.java
@@ -20,14 +20,12 @@
 import android.content.Context;
 import android.location.Location;
 import android.location.LocationManager;
-import android.location.LocationRequest;
 import android.os.Binder;
 
 import com.android.internal.location.ProviderRequest;
 import com.android.internal.util.Preconditions;
 import com.android.server.location.util.Injector;
 
-import java.util.ArrayList;
 import java.util.Collection;
 
 class PassiveLocationProviderManager extends LocationProviderManager {
@@ -65,17 +63,20 @@
 
     @Override
     protected ProviderRequest mergeRegistrations(Collection<Registration> registrations) {
-        ProviderRequest.Builder providerRequest = new ProviderRequest.Builder()
-                .setIntervalMillis(0);
-
-        ArrayList<LocationRequest> requests = new ArrayList<>(registrations.size());
+        boolean locationSettingsIgnored = false;
         for (Registration registration : registrations) {
-            requests.add(registration.getRequest());
-            if (registration.getRequest().isLocationSettingsIgnored()) {
-                providerRequest.setLocationSettingsIgnored(true);
-            }
+            locationSettingsIgnored |= registration.getRequest().isLocationSettingsIgnored();
         }
 
-        return providerRequest.setLocationRequests(requests).build();
+        return new ProviderRequest.Builder()
+                .setIntervalMillis(0)
+                .setLocationSettingsIgnored(locationSettingsIgnored)
+                .build();
+    }
+
+    @Override
+    protected long calculateRequestDelayMillis(long newIntervalMs,
+            Collection<Registration> registrations) {
+        return 0;
     }
 }
diff --git a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
index 87d668a..0cda57c 100644
--- a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
@@ -57,6 +57,8 @@
  * <li>{@link #onRegister()}</li>
  * <li>{@link ListenerRegistration#onRegister(Object)}</li>
  * <li>{@link #onRegistrationAdded(Object, ListenerRegistration)}</li>
+ * <li>{@link #onRegistrationReplaced(Object, ListenerRegistration, ListenerRegistration)} (only
+ * invoked if this registration is replacing a prior registration)</li>
  * <li>{@link #onActive()}</li>
  * <li>{@link ListenerRegistration#onActive()}</li>
  * <li>{@link ListenerRegistration#onInactive()}</li>
@@ -183,6 +185,17 @@
     protected void onRegistrationAdded(@NonNull TKey key, @NonNull TRegistration registration) {}
 
     /**
+     * Invoked instead of {@link #onRegistrationAdded(Object, ListenerRegistration)} if a
+     * registration is replacing an old registration. The old registration will have already been
+     * unregistered. Invoked while holding the multiplexer's internal lock. The default behavior is
+     * simply to call into {@link #onRegistrationAdded(Object, ListenerRegistration)}.
+     */
+    protected void onRegistrationReplaced(@NonNull TKey key, @NonNull TRegistration oldRegistration,
+            @NonNull TRegistration newRegistration) {
+        onRegistrationAdded(key, newRegistration);
+    }
+
+    /**
      * Invoked when a registration is removed. Invoked while holding the multiplexer's internal
      * lock.
      */
@@ -227,9 +240,10 @@
 
                 boolean wasEmpty = mRegistrations.isEmpty();
 
+                TRegistration oldRegistration = null;
                 int index = mRegistrations.indexOfKey(key);
                 if (index >= 0) {
-                    removeRegistration(index, false);
+                    oldRegistration = removeRegistration(index, false);
                     mRegistrations.setValueAt(index, registration);
                 } else {
                     mRegistrations.put(key, registration);
@@ -239,7 +253,11 @@
                     onRegister();
                 }
                 registration.onRegister(key);
-                onRegistrationAdded(key, registration);
+                if (oldRegistration == null) {
+                    onRegistrationAdded(key, registration);
+                } else {
+                    onRegistrationReplaced(key, oldRegistration, registration);
+                }
                 onRegistrationActiveChanged(registration);
             }
         }
@@ -320,7 +338,7 @@
     }
 
     @GuardedBy("mRegistrations")
-    private void removeRegistration(int index, boolean removeEntry) {
+    private TRegistration removeRegistration(int index, boolean removeEntry) {
         if (Build.IS_DEBUGGABLE) {
             Preconditions.checkState(Thread.holdsLock(mRegistrations));
         }
@@ -347,6 +365,8 @@
                 }
             }
         }
+
+        return registration;
     }
 
     /**
diff --git a/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java
index e28d73e..5dc4318 100644
--- a/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java
@@ -18,7 +18,9 @@
 
 import static com.android.server.location.timezone.LocationTimeZoneManagerService.debugLog;
 import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_CERTAIN;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_INITIALIZING;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_UNCERTAIN;
 import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
 
 import android.annotation.NonNull;
@@ -78,16 +80,19 @@
         synchronized (mSharedLock) {
             ProviderState currentState = mCurrentState.get();
             switch (currentState.stateEnum) {
-                case PROVIDER_STATE_ENABLED: {
+                case PROVIDER_STATE_ENABLED_INITIALIZING:
+                case PROVIDER_STATE_ENABLED_UNCERTAIN:
+                case PROVIDER_STATE_ENABLED_CERTAIN: {
                     // Losing a remote provider is treated as becoming uncertain.
                     String msg = "handleProviderLost reason=" + reason
                             + ", mProviderName=" + mProviderName
                             + ", currentState=" + currentState;
                     debugLog(msg);
-                    // This is an unusual PROVIDER_STATE_ENABLED state because event == null
+                    // This is an unusual PROVIDER_STATE_ENABLED_UNCERTAIN state because
+                    // event == null
                     ProviderState newState = currentState.newState(
-                            PROVIDER_STATE_ENABLED, null, currentState.currentUserConfiguration,
-                            msg);
+                            PROVIDER_STATE_ENABLED_UNCERTAIN, null,
+                            currentState.currentUserConfiguration, msg);
                     setCurrentState(newState, true);
                     break;
                 }
@@ -118,7 +123,9 @@
         synchronized (mSharedLock) {
             ProviderState currentState = mCurrentState.get();
             switch (currentState.stateEnum) {
-                case PROVIDER_STATE_ENABLED: {
+                case PROVIDER_STATE_ENABLED_INITIALIZING:
+                case PROVIDER_STATE_ENABLED_CERTAIN:
+                case PROVIDER_STATE_ENABLED_UNCERTAIN: {
                     debugLog("handleOnProviderBound mProviderName=" + mProviderName
                             + ", currentState=" + currentState + ": Provider is enabled.");
                     break;
diff --git a/services/core/java/com/android/server/location/timezone/ControllerImpl.java b/services/core/java/com/android/server/location/timezone/ControllerImpl.java
index bedaeda..179ce75 100644
--- a/services/core/java/com/android/server/location/timezone/ControllerImpl.java
+++ b/services/core/java/com/android/server/location/timezone/ControllerImpl.java
@@ -24,7 +24,9 @@
 import static com.android.server.location.timezone.LocationTimeZoneManagerService.warnLog;
 import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState;
 import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_CERTAIN;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_INITIALIZING;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_UNCERTAIN;
 import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
 
 import android.annotation.NonNull;
@@ -38,6 +40,7 @@
 import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
 
 import java.time.Duration;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -49,8 +52,7 @@
  */
 class ControllerImpl extends LocationTimeZoneProviderController {
 
-    @NonNull private final LocationTimeZoneProvider mProvider;
-    @NonNull private final SingleRunnableQueue mDelayedSuggestionQueue;
+    @NonNull private final LocationTimeZoneProvider mPrimaryProvider;
 
     @GuardedBy("mSharedLock")
     // Non-null after initialize()
@@ -65,12 +67,9 @@
     private Callback mCallback;
 
     /**
-     * Contains any currently pending suggestion on {@link #mDelayedSuggestionQueue}, if there is
-     * one.
+     * Used for scheduling uncertainty timeouts, i.e after the provider has reported uncertainty.
      */
-    @GuardedBy("mSharedLock")
-    @Nullable
-    private GeolocationTimeZoneSuggestion mPendingSuggestion;
+    @NonNull private final SingleRunnableQueue mUncertaintyTimeoutQueue;
 
     /** Contains the last suggestion actually made, if there is one. */
     @GuardedBy("mSharedLock")
@@ -78,10 +77,10 @@
     private GeolocationTimeZoneSuggestion mLastSuggestion;
 
     ControllerImpl(@NonNull ThreadingDomain threadingDomain,
-            @NonNull LocationTimeZoneProvider provider) {
+            @NonNull LocationTimeZoneProvider primaryProvider) {
         super(threadingDomain);
-        mDelayedSuggestionQueue = threadingDomain.createSingleRunnableQueue();
-        mProvider = Objects.requireNonNull(provider);
+        mUncertaintyTimeoutQueue = threadingDomain.createSingleRunnableQueue();
+        mPrimaryProvider = Objects.requireNonNull(primaryProvider);
     }
 
     @Override
@@ -94,8 +93,12 @@
             mCallback = Objects.requireNonNull(callback);
             mCurrentUserConfiguration = environment.getCurrentUserConfigurationInternal();
 
-            mProvider.initialize(ControllerImpl.this::onProviderStateChange);
-            enableOrDisableProvider(mCurrentUserConfiguration);
+            LocationTimeZoneProvider.ProviderListener providerListener =
+                    ControllerImpl.this::onProviderStateChange;
+            mPrimaryProvider.initialize(providerListener);
+
+            alterProviderEnabledStateIfRequired(
+                    null /* oldConfiguration */, mCurrentUserConfiguration);
         }
     }
 
@@ -115,92 +118,148 @@
                     // If the user changed, disable the provider if needed. It may be re-enabled for
                     // the new user below if their settings allow.
                     debugLog("User changed. old=" + oldConfig.getUserId()
-                            + ", new=" + newConfig.getUserId());
-                    debugLog("Disabling LocationTimeZoneProviders as needed");
-                    if (mProvider.getCurrentState().stateEnum == PROVIDER_STATE_ENABLED) {
-                        mProvider.disable();
-                    }
-                }
+                            + ", new=" + newConfig.getUserId() + ": Disabling provider");
+                    disableProvider();
 
-                enableOrDisableProvider(newConfig);
+                    alterProviderEnabledStateIfRequired(null /* oldConfiguration */, newConfig);
+                } else {
+                    alterProviderEnabledStateIfRequired(oldConfig, newConfig);
+                }
             }
         }
     }
 
+    @Override
+    boolean isUncertaintyTimeoutSet() {
+        return mUncertaintyTimeoutQueue.hasQueued();
+    }
+
+    @Override
+    long getUncertaintyTimeoutDelayMillis() {
+        return mUncertaintyTimeoutQueue.getQueuedDelayMillis();
+    }
+
     @GuardedBy("mSharedLock")
-    private void enableOrDisableProvider(@NonNull ConfigurationInternal configuration) {
-        ProviderState providerState = mProvider.getCurrentState();
-        boolean geoDetectionEnabled = configuration.getGeoDetectionEnabledBehavior();
-        boolean providerWasEnabled = providerState.stateEnum == PROVIDER_STATE_ENABLED;
-        if (geoDetectionEnabled) {
-            switch (providerState.stateEnum) {
-                case PROVIDER_STATE_DISABLED: {
-                    debugLog("Enabling " + mProvider);
-                    mProvider.enable(
-                            configuration, mEnvironment.getProviderInitializationTimeout());
-                    break;
-                }
-                case PROVIDER_STATE_ENABLED: {
-                    debugLog("No need to enable " + mProvider + ": already enabled");
-                    break;
-                }
-                case PROVIDER_STATE_PERM_FAILED: {
-                    debugLog("Unable to enable " + mProvider + ": it is perm failed");
-                    break;
-                }
-                default:
-                    warnLog("Unknown provider state: " + mProvider);
-                    break;
+    private void disableProvider() {
+        disableProviderIfEnabled(mPrimaryProvider);
+
+        // By definition, if the provider is disabled, the controller is uncertain.
+        cancelUncertaintyTimeout();
+    }
+
+    @GuardedBy("mSharedLock")
+    private void disableProviderIfEnabled(LocationTimeZoneProvider provider) {
+        if (provider.getCurrentState().isEnabled()) {
+            disableProvider(provider);
+        }
+    }
+
+    @GuardedBy("mSharedLock")
+    private void disableProvider(LocationTimeZoneProvider provider) {
+        ProviderState providerState = provider.getCurrentState();
+        switch (providerState.stateEnum) {
+            case PROVIDER_STATE_DISABLED: {
+                debugLog("No need to disable " + provider + ": already disabled");
+                break;
             }
-        } else {
-            switch (providerState.stateEnum) {
-                case PROVIDER_STATE_DISABLED: {
-                    debugLog("No need to disable " + mProvider + ": already enabled");
-                    break;
-                }
-                case PROVIDER_STATE_ENABLED: {
-                    debugLog("Disabling " + mProvider);
-                    mProvider.disable();
-                    break;
-                }
-                case PROVIDER_STATE_PERM_FAILED: {
-                    debugLog("Unable to disable " + mProvider + ": it is perm failed");
-                    break;
-                }
-                default: {
-                    warnLog("Unknown provider state: " + mProvider);
-                    break;
-                }
+            case PROVIDER_STATE_ENABLED_INITIALIZING:
+            case PROVIDER_STATE_ENABLED_CERTAIN:
+            case PROVIDER_STATE_ENABLED_UNCERTAIN: {
+                debugLog("Disabling " + provider);
+                provider.disable();
+                break;
+            }
+            case PROVIDER_STATE_PERM_FAILED: {
+                debugLog("Unable to disable " + provider + ": it is perm failed");
+                break;
+            }
+            default: {
+                warnLog("Unknown provider state: " + provider);
+                break;
             }
         }
+    }
 
-        boolean isProviderEnabled =
-                mProvider.getCurrentState().stateEnum == PROVIDER_STATE_ENABLED;
+    /**
+     * Sets the provider into the correct enabled/disabled state for the {@code newConfiguration}
+     * and, if there is a provider state change, makes any suggestions required to inform the
+     * downstream time zone detection code.
+     *
+     * <p>This is a utility method that exists to avoid duplicated logic for the various cases when
+     * provider enabled / disabled state may need to be set or changed, e.g. during initialization
+     * or when a new configuration has been received.
+     */
+    @GuardedBy("mSharedLock")
+    private void alterProviderEnabledStateIfRequired(
+            @Nullable ConfigurationInternal oldConfiguration,
+            @NonNull ConfigurationInternal newConfiguration) {
 
-        if (isProviderEnabled) {
-            if (!providerWasEnabled) {
-                // When a provider has first been enabled, we allow it some time for it to
-                // initialize before sending its first event.
-                Duration initializationTimeout = mEnvironment.getProviderInitializationTimeout()
-                        .plus(mEnvironment.getProviderInitializationTimeoutFuzz());
-                // This sets up an empty suggestion to trigger if no explicit "certain" or
-                // "uncertain" suggestion preempts it within initializationTimeout. If, for some
-                // reason, the provider does not produce any events then this scheduled suggestion
-                // will ensure the controller makes at least an "uncertain" suggestion.
-                suggestDelayed(createEmptySuggestion("No event received from provider in"
-                                + " initializationTimeout=" + initializationTimeout),
-                        initializationTimeout);
+        // Provider enabled / disabled states only need to be changed if geoDetectionEnabled has
+        // changed.
+        boolean oldGeoDetectionEnabled = oldConfiguration != null
+                && oldConfiguration.getGeoDetectionEnabledBehavior();
+        boolean newGeoDetectionEnabled = newConfiguration.getGeoDetectionEnabledBehavior();
+        if (oldGeoDetectionEnabled == newGeoDetectionEnabled) {
+            return;
+        }
+
+        if (newGeoDetectionEnabled) {
+            // Try to enable the primary provider.
+            tryEnableProvider(mPrimaryProvider, newConfiguration);
+
+            ProviderState newPrimaryState = mPrimaryProvider.getCurrentState();
+            if (!newPrimaryState.isEnabled()) {
+                // If the provider is perm failed then the controller is immediately considered
+                // uncertain.
+                GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
+                        "Provider is failed:"
+                                + " primary=" + mPrimaryProvider.getCurrentState());
+                makeSuggestion(suggestion);
             }
         } else {
-            // Clear any queued suggestions.
-            clearDelayedSuggestion();
+            disableProvider();
 
-            // If the provider is now not enabled, and a previous "certain" suggestion has been
-            // made, then a new "uncertain" suggestion must be made to indicate the provider no
-            // longer has an opinion and will not be sending updates.
+            // There can be an uncertainty timeout set if the controller most recently received
+            // an uncertain event. This is a no-op if there isn't a timeout set.
+            cancelUncertaintyTimeout();
+
+            // If a previous "certain" suggestion has been made, then a new "uncertain"
+            // suggestion must now be made to indicate the controller {does not / no longer has}
+            // an opinion and will not be sending further updates (until at least the config
+            // changes again and providers are re-enabled).
             if (mLastSuggestion != null && mLastSuggestion.getZoneIds() != null) {
-                suggestImmediate(createEmptySuggestion(
-                        "Provider disabled, clearing previous suggestion"));
+                GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
+                        "Provider is disabled:"
+                                + " primary=" + mPrimaryProvider.getCurrentState());
+                makeSuggestion(suggestion);
+            }
+        }
+    }
+
+    private void tryEnableProvider(@NonNull LocationTimeZoneProvider provider,
+            @NonNull ConfigurationInternal configuration) {
+        ProviderState providerState = provider.getCurrentState();
+        switch (providerState.stateEnum) {
+            case PROVIDER_STATE_DISABLED: {
+                debugLog("Enabling " + provider);
+                provider.enable(configuration, mEnvironment.getProviderInitializationTimeout(),
+                        mEnvironment.getProviderInitializationTimeoutFuzz());
+                break;
+            }
+            case PROVIDER_STATE_ENABLED_INITIALIZING:
+            case PROVIDER_STATE_ENABLED_CERTAIN:
+            case PROVIDER_STATE_ENABLED_UNCERTAIN: {
+                debugLog("No need to enable " + provider + ": already enabled");
+                break;
+            }
+            case PROVIDER_STATE_PERM_FAILED: {
+                debugLog("Unable to enable " + provider + ": it is perm failed");
+                break;
+            }
+            default: {
+                throw new IllegalStateException("Unknown provider state:"
+                        + " provider=" + provider
+                        + ", state=" + providerState.stateEnum);
             }
         }
     }
@@ -217,7 +276,9 @@
                             + " providerState=" + providerState);
                     break;
                 }
-                case PROVIDER_STATE_ENABLED: {
+                case PROVIDER_STATE_ENABLED_INITIALIZING:
+                case PROVIDER_STATE_ENABLED_CERTAIN:
+                case PROVIDER_STATE_ENABLED_UNCERTAIN: {
                     // Entering enabled does not trigger an event, so this only happens if an event
                     // is received while the provider is enabled.
                     debugLog("onProviderStateChange: Received notification of an event while"
@@ -228,10 +289,7 @@
                 case PROVIDER_STATE_PERM_FAILED: {
                     debugLog("Received notification of permanent failure for"
                             + " provider=" + providerState);
-                    GeolocationTimeZoneSuggestion suggestion = createEmptySuggestion(
-                            "provider=" + providerState.provider
-                                    + " permanently failed: " + providerState);
-                    suggestImmediate(suggestion);
+                    providerFailedProcessEvent();
                     break;
                 }
                 default: {
@@ -242,24 +300,46 @@
     }
 
     private void assertProviderKnown(LocationTimeZoneProvider provider) {
-        if (provider != mProvider) {
+        if (provider != mPrimaryProvider) {
             throw new IllegalArgumentException("Unknown provider: " + provider);
         }
     }
 
     /**
-     * Called when a provider has changed state but just moved from a PROVIDER_STATE_ENABLED state
-     * to another PROVIDER_STATE_ENABLED state, usually as a result of a new {@link
-     * LocationTimeZoneEvent} being received. There are some cases where event can be null.
+     * Called when the provider has reported that it has failed permanently.
      */
+    @GuardedBy("mSharedLock")
+    private void providerFailedProcessEvent() {
+        // If the provider is newly perm failed then the controller is uncertain by
+        // definition.
+        cancelUncertaintyTimeout();
+
+        // If the provider is now failed, then we must send a suggestion informing the time
+        // zone detector that there are no further updates coming in future.
+
+        GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
+                "The provider is permanently failed:"
+                        + " primary=" + mPrimaryProvider.getCurrentState());
+        makeSuggestion(suggestion);
+    }
+
+    /**
+     * Called when a provider has changed state but just moved from one enabled state to another
+     * enabled state, usually as a result of a new {@link LocationTimeZoneEvent} being received.
+     * However, there are rare cases where the event can be null.
+     */
+    @GuardedBy("mSharedLock")
     private void providerEnabledProcessEvent(@NonNull ProviderState providerState) {
+        LocationTimeZoneProvider provider = providerState.provider;
         LocationTimeZoneEvent event = providerState.event;
         if (event == null) {
             // Implicit uncertainty, i.e. where the provider is enabled, but a problem has been
             // detected without having received an event. For example, if the process has detected
-            // the loss of a binder-based provider. This is treated like explicit uncertainty, i.e.
-            // where the provider has explicitly told this process it is uncertain.
-            scheduleUncertainSuggestionIfNeeded(null);
+            // the loss of a binder-based provider, or initialization took too long. This is treated
+            // the same as explicit uncertainty, i.e. where the provider has explicitly told this
+            // process it is uncertain.
+            handleProviderUncertainty(provider, "provider=" + provider
+                    + ", implicit uncertainty, event=null");
             return;
         }
 
@@ -279,22 +359,19 @@
 
         switch (event.getEventType()) {
             case EVENT_TYPE_PERMANENT_FAILURE: {
-                // This shouldn't happen. Providers cannot be enabled and have this event.
+                // This shouldn't happen. A provider cannot be enabled and have this event.
                 warnLog("Provider=" + providerState
                         + " is enabled, but event suggests it shouldn't be");
                 break;
             }
             case EVENT_TYPE_UNCERTAIN: {
-                scheduleUncertainSuggestionIfNeeded(event);
+                handleProviderUncertainty(provider, "provider=" + provider
+                        + ", explicit uncertainty. event=" + event);
                 break;
             }
             case EVENT_TYPE_SUCCESS: {
-                GeolocationTimeZoneSuggestion suggestion =
-                        new GeolocationTimeZoneSuggestion(event.getTimeZoneIds());
-                suggestion.addDebugInfo("Event received provider=" + mProvider.getName()
-                        + ", event=" + event);
-                // Rely on the receiver to dedupe events. It is better to over-communicate.
-                suggestImmediate(suggestion);
+                handleProviderCertainty(provider, event.getTimeZoneIds(),
+                        "Event received provider=" + provider.getName() + ", event=" + event);
                 break;
             }
             default: {
@@ -304,30 +381,19 @@
         }
     }
 
-    /**
-     * Indicates a provider has become uncertain with the event (if any) received that indicates
-     * that.
-     *
-     * <p>Providers are expected to report their uncertainty as soon as they become uncertain, as
-     * this enables the most flexibility for the controller to enable other providers when there are
-     * multiple ones available. The controller is therefore responsible for deciding when to make a
-     * "uncertain" suggestion.
-     *
-     * <p>This method schedules an "uncertain" suggestion (if one isn't already scheduled) to be
-     * made later if nothing else preempts it. It can be preempted if the provider becomes certain
-     * (or does anything else that calls {@link #suggestImmediate(GeolocationTimeZoneSuggestion)})
-     * within {@link Environment#getUncertaintyDelay()}. Preemption causes the scheduled
-     * "uncertain" event to be cancelled. If the provider repeatedly sends uncertainty events within
-     * the uncertainty delay period, those events are effectively ignored (i.e. the timer is not
-     * reset each time).
-     */
-    private void scheduleUncertainSuggestionIfNeeded(@Nullable LocationTimeZoneEvent event) {
-        if (mPendingSuggestion == null || mPendingSuggestion.getZoneIds() != null) {
-            GeolocationTimeZoneSuggestion suggestion = createEmptySuggestion(
-                    "provider=" + mProvider + " became uncertain, event=" + event);
-            // Only send the empty suggestion after the uncertainty delay.
-            suggestDelayed(suggestion, mEnvironment.getUncertaintyDelay());
-        }
+    @GuardedBy("mSharedLock")
+    private void handleProviderCertainty(
+            @NonNull LocationTimeZoneProvider provider,
+            @Nullable List<String> timeZoneIds,
+            @NonNull String reason) {
+        // By definition, the controller is now certain.
+        cancelUncertaintyTimeout();
+
+        GeolocationTimeZoneSuggestion suggestion =
+                new GeolocationTimeZoneSuggestion(timeZoneIds);
+        suggestion.addDebugInfo(reason);
+        // Rely on the receiver to dedupe events. It is better to over-communicate.
+        makeSuggestion(suggestion);
     }
 
     @Override
@@ -342,66 +408,74 @@
             ipw.println("providerInitializationTimeoutFuzz="
                     + mEnvironment.getProviderInitializationTimeoutFuzz());
             ipw.println("uncertaintyDelay=" + mEnvironment.getUncertaintyDelay());
-            ipw.println("mPendingSuggestion=" + mPendingSuggestion);
             ipw.println("mLastSuggestion=" + mLastSuggestion);
 
-            ipw.println("Provider:");
+            ipw.println("Primary Provider:");
             ipw.increaseIndent(); // level 2
-            mProvider.dump(ipw, args);
+            mPrimaryProvider.dump(ipw, args);
             ipw.decreaseIndent(); // level 2
 
             ipw.decreaseIndent(); // level 1
         }
     }
 
-    /** Sends an immediate suggestion, cancelling any pending suggestion. */
+    /** Sends an immediate suggestion, updating mLastSuggestion. */
     @GuardedBy("mSharedLock")
-    private void suggestImmediate(@NonNull GeolocationTimeZoneSuggestion suggestion) {
-        debugLog("suggestImmediate: Executing suggestion=" + suggestion);
-        mDelayedSuggestionQueue.runSynchronously(() -> mCallback.suggest(suggestion));
-        mPendingSuggestion = null;
+    private void makeSuggestion(@NonNull GeolocationTimeZoneSuggestion suggestion) {
+        debugLog("makeSuggestion: suggestion=" + suggestion);
+        mCallback.suggest(suggestion);
         mLastSuggestion = suggestion;
     }
 
-    /** Clears any pending suggestion. */
+    /** Clears the uncertainty timeout. */
     @GuardedBy("mSharedLock")
-    private void clearDelayedSuggestion() {
-        mDelayedSuggestionQueue.cancel();
-        mPendingSuggestion = null;
+    private void cancelUncertaintyTimeout() {
+        mUncertaintyTimeoutQueue.cancel();
     }
 
-
     /**
-     * Schedules a delayed suggestion. There can only be one delayed suggestion at a time.
-     * If there is a pending scheduled suggestion equal to the one passed, it will not be replaced.
-     * Replacing a previous delayed suggestion has the effect of cancelling the timeout associated
-     * with that previous suggestion.
+     * Indicates a provider has become uncertain with the event (if any) received that indicates
+     * that.
+     *
+     * <p>A provider is expected to report its uncertainty as soon as it becomes uncertain, as
+     * this enables the most flexibility for the controller to enable other providers when there are
+     * multiple ones available. The controller is therefore responsible for deciding when to make a
+     * "uncertain" suggestion.
+     *
+     * <p>This method schedules an "uncertain" suggestion (if one isn't already scheduled) to be
+     * made later if nothing else preempts it. It can be preempted if the provider becomes certain
+     * (or does anything else that calls {@link #makeSuggestion(GeolocationTimeZoneSuggestion)})
+     * within {@link Environment#getUncertaintyDelay()}. Preemption causes the scheduled
+     * "uncertain" event to be cancelled. If the provider repeatedly sends uncertainty events within
+     * the uncertainty delay period, those events are effectively ignored (i.e. the timer is not
+     * reset each time).
      */
     @GuardedBy("mSharedLock")
-    private void suggestDelayed(@NonNull GeolocationTimeZoneSuggestion suggestion,
-            @NonNull Duration delay) {
-        Objects.requireNonNull(suggestion);
-        Objects.requireNonNull(delay);
+    void handleProviderUncertainty(@NonNull LocationTimeZoneProvider provider, String reason) {
+        Objects.requireNonNull(provider);
 
-        if (Objects.equals(mPendingSuggestion, suggestion)) {
-            // Do not reset the timer.
-            debugLog("suggestDelayed: Suggestion=" + suggestion + " is equal to existing."
-                    + " Not scheduled.");
-            return;
+        // Start the uncertainty timeout if needed.
+        if (!mUncertaintyTimeoutQueue.hasQueued()) {
+            debugLog("Starting uncertainty timeout: reason=" + reason);
+
+            Duration delay = mEnvironment.getUncertaintyDelay();
+            mUncertaintyTimeoutQueue.runDelayed(
+                    this::onProviderUncertaintyTimeout, delay.toMillis());
         }
-
-        debugLog("suggestDelayed: Scheduling suggestion=" + suggestion);
-        mPendingSuggestion = suggestion;
-
-        mDelayedSuggestionQueue.runDelayed(() -> {
-            debugLog("suggestDelayed: Executing suggestion=" + suggestion);
-            mCallback.suggest(suggestion);
-            mPendingSuggestion = null;
-            mLastSuggestion = suggestion;
-        }, delay.toMillis());
     }
 
-    private static GeolocationTimeZoneSuggestion createEmptySuggestion(String reason) {
+    private void onProviderUncertaintyTimeout() {
+        mThreadingDomain.assertCurrentThread();
+
+        synchronized (mSharedLock) {
+            GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
+                    "Uncertainty timeout triggered:"
+                            + " primary=" + mPrimaryProvider.getCurrentState());
+            makeSuggestion(suggestion);
+        }
+    }
+
+    private static GeolocationTimeZoneSuggestion createUncertainSuggestion(String reason) {
         GeolocationTimeZoneSuggestion suggestion = new GeolocationTimeZoneSuggestion(null);
         suggestion.addDebugInfo(reason);
         return suggestion;
@@ -412,17 +486,22 @@
      * If the provider name does not match a known provider, then the event is logged and discarded.
      */
     void simulateBinderProviderEvent(SimulatedBinderProviderEvent event) {
-        if (!Objects.equals(mProvider.getName(), event.getProviderName())) {
+        String targetProviderName = event.getProviderName();
+        LocationTimeZoneProvider targetProvider;
+        if (Objects.equals(mPrimaryProvider.getName(), targetProviderName)) {
+            targetProvider = mPrimaryProvider;
+        } else {
             warnLog("Unable to process simulated binder provider event,"
                     + " unknown providerName in event=" + event);
             return;
         }
-        if (!(mProvider instanceof BinderLocationTimeZoneProvider)) {
+        if (!(targetProvider instanceof BinderLocationTimeZoneProvider)) {
             warnLog("Unable to process simulated binder provider event,"
-                    + " provider is not a " + BinderLocationTimeZoneProvider.class
+                    + " provider=" + targetProvider
+                    + " is not a " + BinderLocationTimeZoneProvider.class
                     + ", event=" + event);
             return;
         }
-        ((BinderLocationTimeZoneProvider) mProvider).simulateBinderProviderEvent(event);
+        ((BinderLocationTimeZoneProvider) targetProvider).simulateBinderProviderEvent(event);
     }
 }
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
index 238f999..c9a211d 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
@@ -160,7 +160,8 @@
         // Called on an arbitrary thread during initialization.
         synchronized (mSharedLock) {
             LocationTimeZoneProvider primary = createPrimaryProvider();
-            mLocationTimeZoneDetectorController = new ControllerImpl(mThreadingDomain, primary);
+            mLocationTimeZoneDetectorController =
+                    new ControllerImpl(mThreadingDomain, primary);
             ControllerCallbackImpl callback = new ControllerCallbackImpl(mThreadingDomain);
             ControllerEnvironmentImpl environment = new ControllerEnvironmentImpl(
                     mThreadingDomain, mLocationTimeZoneDetectorController);
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
index abfa580..4b0b5a2 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
@@ -22,7 +22,9 @@
 
 import static com.android.server.location.timezone.LocationTimeZoneManagerService.debugLog;
 import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_CERTAIN;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_INITIALIZING;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_UNCERTAIN;
 import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
 
 import android.annotation.IntDef;
@@ -33,6 +35,9 @@
 import android.os.SystemClock;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.ProviderStateEnum;
+import com.android.server.location.timezone.ThreadingDomain.SingleRunnableQueue;
 import com.android.server.timezonedetector.ConfigurationInternal;
 import com.android.server.timezonedetector.Dumpable;
 import com.android.server.timezonedetector.ReferenceWithHistory;
@@ -73,8 +78,9 @@
      */
     static class ProviderState {
 
-        @IntDef({ PROVIDER_STATE_UNKNOWN, PROVIDER_STATE_ENABLED, PROVIDER_STATE_DISABLED,
-                PROVIDER_STATE_PERM_FAILED })
+        @IntDef({ PROVIDER_STATE_UNKNOWN, PROVIDER_STATE_ENABLED_INITIALIZING,
+                PROVIDER_STATE_ENABLED_CERTAIN, PROVIDER_STATE_ENABLED_UNCERTAIN,
+                PROVIDER_STATE_DISABLED, PROVIDER_STATE_PERM_FAILED })
         @interface ProviderStateEnum {}
 
         /**
@@ -83,22 +89,33 @@
         static final int PROVIDER_STATE_UNKNOWN = 0;
 
         /**
-         * The provider is currently enabled.
+         * The provider is enabled and has not reported its first event.
          */
-        static final int PROVIDER_STATE_ENABLED = 1;
+        static final int PROVIDER_STATE_ENABLED_INITIALIZING = 1;
 
         /**
-         * The provider is currently disabled.
+         * The provider is enabled and most recently reported a "success" event.
+         */
+        static final int PROVIDER_STATE_ENABLED_CERTAIN = 2;
+
+        /**
+         * The provider is enabled and most recently reported an "uncertain" event.
+         */
+        static final int PROVIDER_STATE_ENABLED_UNCERTAIN = 3;
+
+        /**
+         * The provider is disabled.
+         *
          * This is the state after {@link #initialize} is called.
          */
-        static final int PROVIDER_STATE_DISABLED = 2;
+        static final int PROVIDER_STATE_DISABLED = 4;
 
         /**
          * The provider has failed and cannot be re-enabled.
          *
          * Providers may enter this state after a provider is enabled.
          */
-        static final int PROVIDER_STATE_PERM_FAILED = 3;
+        static final int PROVIDER_STATE_PERM_FAILED = 5;
 
         /** The {@link LocationTimeZoneProvider} the state is for. */
         public final @NonNull LocationTimeZoneProvider provider;
@@ -108,14 +125,15 @@
 
         /**
          * The last {@link LocationTimeZoneEvent} received. Only populated when {@link #stateEnum}
-         * is {@link #PROVIDER_STATE_ENABLED}, but it can be {@code null} then too if no event has
+         * is either {@link #PROVIDER_STATE_ENABLED_CERTAIN} or {@link
+         * #PROVIDER_STATE_ENABLED_UNCERTAIN}, but it can be {@code null} then too if no event has
          * yet been received.
          */
         @Nullable public final LocationTimeZoneEvent event;
 
         /**
          * The user configuration associated with the current state. Only and always present when
-         * {@link #stateEnum} is {@link #PROVIDER_STATE_ENABLED}.
+         * {@link #stateEnum} is one of the enabled states.
          */
         @Nullable public final ConfigurationInternal currentUserConfiguration;
 
@@ -133,7 +151,8 @@
 
 
         private ProviderState(@NonNull LocationTimeZoneProvider provider,
-                @ProviderStateEnum int stateEnum, @Nullable LocationTimeZoneEvent event,
+                @ProviderStateEnum int stateEnum,
+                @Nullable LocationTimeZoneEvent event,
                 @Nullable ConfigurationInternal currentUserConfiguration,
                 @Nullable String debugInfo) {
             this.provider = Objects.requireNonNull(provider);
@@ -172,7 +191,9 @@
                     break;
                 }
                 case PROVIDER_STATE_DISABLED:
-                case PROVIDER_STATE_ENABLED: {
+                case PROVIDER_STATE_ENABLED_INITIALIZING:
+                case PROVIDER_STATE_ENABLED_CERTAIN:
+                case PROVIDER_STATE_ENABLED_UNCERTAIN: {
                     // These can go to each other or PROVIDER_STATE_PERM_FAILED.
                     break;
                 }
@@ -200,7 +221,9 @@
                     }
                     break;
                 }
-                case PROVIDER_STATE_ENABLED: {
+                case PROVIDER_STATE_ENABLED_INITIALIZING:
+                case PROVIDER_STATE_ENABLED_CERTAIN:
+                case PROVIDER_STATE_ENABLED_UNCERTAIN: {
                     if (currentUserConfig == null) {
                         throw new IllegalArgumentException(
                                 "Enabled state: currentUserConfig must not be null");
@@ -223,6 +246,13 @@
             return new ProviderState(provider, newStateEnum, event, currentUserConfig, debugInfo);
         }
 
+        /** Returns {@code true} if {@link #stateEnum} is one of the enabled states. */
+        boolean isEnabled() {
+            return stateEnum == PROVIDER_STATE_ENABLED_INITIALIZING
+                    || stateEnum == PROVIDER_STATE_ENABLED_CERTAIN
+                    || stateEnum == PROVIDER_STATE_ENABLED_UNCERTAIN;
+        }
+
         @Override
         public String toString() {
             return "State{"
@@ -257,8 +287,12 @@
             switch (state) {
                 case PROVIDER_STATE_DISABLED:
                     return "Disabled (" + PROVIDER_STATE_DISABLED + ")";
-                case PROVIDER_STATE_ENABLED:
-                    return "Enabled (" + PROVIDER_STATE_ENABLED + ")";
+                case PROVIDER_STATE_ENABLED_INITIALIZING:
+                    return "Enabled initializing (" + PROVIDER_STATE_ENABLED_INITIALIZING + ")";
+                case PROVIDER_STATE_ENABLED_CERTAIN:
+                    return "Enabled certain (" + PROVIDER_STATE_ENABLED_CERTAIN + ")";
+                case PROVIDER_STATE_ENABLED_UNCERTAIN:
+                    return "Enabled uncertain (" + PROVIDER_STATE_ENABLED_UNCERTAIN + ")";
                 case PROVIDER_STATE_PERM_FAILED:
                     return "Perm failure (" + PROVIDER_STATE_PERM_FAILED + ")";
                 case PROVIDER_STATE_UNKNOWN:
@@ -279,6 +313,11 @@
     final ReferenceWithHistory<ProviderState> mCurrentState =
             new ReferenceWithHistory<>(10);
 
+    /**
+     * Used for scheduling initialization timeouts, i.e. for providers that have just been enabled.
+     */
+    @NonNull private final SingleRunnableQueue mInitializationTimeoutQueue;
+
     // Non-null and effectively final after initialize() is called.
     ProviderListener mProviderListener;
 
@@ -286,6 +325,7 @@
     LocationTimeZoneProvider(@NonNull ThreadingDomain threadingDomain,
             @NonNull String providerName) {
         mThreadingDomain = Objects.requireNonNull(threadingDomain);
+        mInitializationTimeoutQueue = threadingDomain.createSingleRunnableQueue();
         mSharedLock = threadingDomain.getLockObject();
         mProviderName = Objects.requireNonNull(providerName);
     }
@@ -303,7 +343,8 @@
             mProviderListener = Objects.requireNonNull(providerListener);
             ProviderState currentState = ProviderState.createStartingState(this);
             ProviderState newState = currentState.newState(
-                    PROVIDER_STATE_DISABLED, null, null, "initialize() called");
+                    PROVIDER_STATE_DISABLED, null, null,
+                    "initialize() called");
             setCurrentState(newState, false);
 
             onInitialize();
@@ -370,20 +411,41 @@
      * called using the handler thread from the {@link ThreadingDomain}.
      */
     final void enable(@NonNull ConfigurationInternal currentUserConfiguration,
-            @NonNull Duration initializationTimeout) {
+            @NonNull Duration initializationTimeout, @NonNull Duration initializationTimeoutFuzz) {
         mThreadingDomain.assertCurrentThread();
 
         synchronized (mSharedLock) {
             assertCurrentState(PROVIDER_STATE_DISABLED);
 
-            ProviderState currentState = getCurrentState();
+            ProviderState currentState = mCurrentState.get();
             ProviderState newState = currentState.newState(
-                    PROVIDER_STATE_ENABLED, null, currentUserConfiguration, "enable() called");
+                    PROVIDER_STATE_ENABLED_INITIALIZING, null /* event */,
+                    currentUserConfiguration, "enable() called");
             setCurrentState(newState, false);
+
+            Duration delay = initializationTimeout.plus(initializationTimeoutFuzz);
+            mInitializationTimeoutQueue.runDelayed(
+                    this::handleInitializationTimeout, delay.toMillis());
+
             onEnable(initializationTimeout);
         }
     }
 
+    private void handleInitializationTimeout() {
+        mThreadingDomain.assertCurrentThread();
+
+        synchronized (mSharedLock) {
+            ProviderState currentState = mCurrentState.get();
+            if (currentState.stateEnum == PROVIDER_STATE_ENABLED_INITIALIZING) {
+                // On initialization timeout the provider becomes uncertain.
+                ProviderState newState = currentState.newState(
+                        PROVIDER_STATE_ENABLED_UNCERTAIN, null /* event */,
+                        currentState.currentUserConfiguration, "initialization timeout");
+                setCurrentState(newState, true);
+            }
+        }
+    }
+
     /**
      * Implemented by subclasses to do work during {@link #enable}.
      */
@@ -391,20 +453,24 @@
 
     /**
      * Disables the provider. It is an error* to call this method except when the {@link
-     * #getCurrentState()} is at {@link ProviderState#PROVIDER_STATE_ENABLED}. This method must be
+     * #getCurrentState()} is one of the enabled states. This method must be
      * called using the handler thread from the {@link ThreadingDomain}.
      */
     final void disable() {
         mThreadingDomain.assertCurrentThread();
 
         synchronized (mSharedLock) {
-            assertCurrentState(PROVIDER_STATE_ENABLED);
+            assertIsEnabled();
 
-            ProviderState currentState = getCurrentState();
-            ProviderState newState =
-                    currentState.newState(PROVIDER_STATE_DISABLED, null, null, "disable() called");
+            ProviderState currentState = mCurrentState.get();
+            ProviderState newState = currentState.newState(
+                    PROVIDER_STATE_DISABLED, null, null, "disable() called");
             setCurrentState(newState, false);
 
+            if (mInitializationTimeoutQueue.hasQueued()) {
+                mInitializationTimeoutQueue.cancel();
+            }
+
             onDisable();
         }
     }
@@ -424,7 +490,7 @@
             debugLog("handleLocationTimeZoneEvent: mProviderName=" + mProviderName
                     + ", locationTimeZoneEvent=" + locationTimeZoneEvent);
 
-            ProviderState currentState = getCurrentState();
+            ProviderState currentState = mCurrentState.get();
             int eventType = locationTimeZoneEvent.getEventType();
             switch (currentState.stateEnum) {
                 case PROVIDER_STATE_PERM_FAILED: {
@@ -445,6 +511,9 @@
                             ProviderState newState = currentState.newState(
                                     PROVIDER_STATE_PERM_FAILED, null, null, msg);
                             setCurrentState(newState, true);
+                            if (mInitializationTimeoutQueue.hasQueued()) {
+                                mInitializationTimeoutQueue.cancel();
+                            }
                             return;
                         }
                         case EVENT_TYPE_SUCCESS:
@@ -464,7 +533,9 @@
                         }
                     }
                 }
-                case PROVIDER_STATE_ENABLED: {
+                case PROVIDER_STATE_ENABLED_INITIALIZING:
+                case PROVIDER_STATE_ENABLED_CERTAIN:
+                case PROVIDER_STATE_ENABLED_UNCERTAIN: {
                     switch (eventType) {
                         case EVENT_TYPE_PERMANENT_FAILURE: {
                             String msg = "handleLocationTimeZoneEvent:"
@@ -475,14 +546,27 @@
                             ProviderState newState = currentState.newState(
                                     PROVIDER_STATE_PERM_FAILED, null, null, msg);
                             setCurrentState(newState, true);
+                            if (mInitializationTimeoutQueue.hasQueued()) {
+                                mInitializationTimeoutQueue.cancel();
+                            }
+
                             return;
                         }
                         case EVENT_TYPE_UNCERTAIN:
                         case EVENT_TYPE_SUCCESS: {
-                            ProviderState newState = currentState.newState(PROVIDER_STATE_ENABLED,
+                            @ProviderStateEnum int providerStateEnum;
+                            if (eventType == EVENT_TYPE_UNCERTAIN) {
+                                providerStateEnum = PROVIDER_STATE_ENABLED_UNCERTAIN;
+                            } else {
+                                providerStateEnum = PROVIDER_STATE_ENABLED_CERTAIN;
+                            }
+                            ProviderState newState = currentState.newState(providerStateEnum,
                                     locationTimeZoneEvent, currentState.currentUserConfiguration,
                                     "handleLocationTimeZoneEvent() when enabled");
                             setCurrentState(newState, true);
+                            if (mInitializationTimeoutQueue.hasQueued()) {
+                                mInitializationTimeoutQueue.cancel();
+                            }
                             return;
                         }
                         default: {
@@ -503,11 +587,34 @@
      */
     abstract void logWarn(String msg);
 
-    private void assertCurrentState(@ProviderState.ProviderStateEnum int requiredState) {
-        ProviderState currentState = getCurrentState();
+    @GuardedBy("mSharedLock")
+    private void assertIsEnabled() {
+        ProviderState currentState = mCurrentState.get();
+        if (!currentState.isEnabled()) {
+            throw new IllegalStateException("Required an enabled state, but was " + currentState);
+        }
+    }
+
+    @GuardedBy("mSharedLock")
+    private void assertCurrentState(@ProviderStateEnum int requiredState) {
+        ProviderState currentState = mCurrentState.get();
         if (currentState.stateEnum != requiredState) {
             throw new IllegalStateException(
                     "Required stateEnum=" + requiredState + ", but was " + currentState);
         }
     }
+
+    @VisibleForTesting
+    boolean isInitializationTimeoutSet() {
+        synchronized (mSharedLock) {
+            return mInitializationTimeoutQueue.hasQueued();
+        }
+    }
+
+    @VisibleForTesting
+    Duration getInitializationTimeoutDelay() {
+        synchronized (mSharedLock) {
+            return Duration.ofMillis(mInitializationTimeoutQueue.getQueuedDelayMillis());
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java
index 88f0f00..ace066e 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.os.Handler;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState;
 import com.android.server.timezonedetector.ConfigurationInternal;
 import com.android.server.timezonedetector.Dumpable;
@@ -85,6 +86,12 @@
      */
     abstract void onConfigChanged();
 
+    @VisibleForTesting
+    abstract boolean isUncertaintyTimeoutSet();
+
+    @VisibleForTesting
+    abstract long getUncertaintyTimeoutDelayMillis();
+
     /**
      * Used by {@link LocationTimeZoneProviderController} to obtain information from the surrounding
      * service. It can easily be faked for tests.
diff --git a/services/core/java/com/android/server/location/timezone/ThreadingDomain.java b/services/core/java/com/android/server/location/timezone/ThreadingDomain.java
index 9b9c823..d55d3ed 100644
--- a/services/core/java/com/android/server/location/timezone/ThreadingDomain.java
+++ b/services/core/java/com/android/server/location/timezone/ThreadingDomain.java
@@ -83,21 +83,14 @@
     }
 
     /**
-     * A class that allows up to one {@link Runnable} to be queued on the handler, i.e. calling any
-     * of the methods will cancel the execution of any previously queued / delayed runnable. All
+     * A class that allows up to one {@link Runnable} to be queued, i.e. calling {@link
+     * #runDelayed(Runnable, long)} will cancel the execution of any previously queued runnable. All
      * methods must be called from the {@link ThreadingDomain}'s thread.
      */
     final class SingleRunnableQueue {
 
-        /**
-         * Runs the supplied {@link Runnable} synchronously on the threading domain's thread,
-         * cancelling any queued but not-yet-executed {@link Runnable} previously added by this.
-         * This method must be called from the threading domain's thread.
-         */
-        void runSynchronously(Runnable r) {
-            cancel();
-            r.run();
-        }
+        private boolean mIsQueued;
+        private long mDelayMillis;
 
         /**
          * Posts the supplied {@link Runnable} asynchronously and delayed on the threading domain
@@ -106,15 +99,48 @@
          */
         void runDelayed(Runnable r, long delayMillis) {
             cancel();
-            ThreadingDomain.this.postDelayed(r, this, delayMillis);
+            mIsQueued = true;
+            mDelayMillis = delayMillis;
+            ThreadingDomain.this.postDelayed(() -> {
+                mIsQueued = false;
+                mDelayMillis = -2;
+                r.run();
+            }, this, delayMillis);
+        }
+
+        /**
+         * Returns {@code true} if there is an item current queued. This method must be called from
+         * the threading domain's thread.
+         */
+        boolean hasQueued() {
+            assertCurrentThread();
+            return mIsQueued;
+        }
+
+        /**
+         * Returns the delay in milliseconds for the currently queued item. Throws {@link
+         * IllegalStateException} if nothing is currently queued, see {@link #hasQueued()}.
+         * This method must be called from the threading domain's thread.
+         */
+        long getQueuedDelayMillis() {
+            assertCurrentThread();
+            if (!mIsQueued) {
+                throw new IllegalStateException("No item queued");
+            }
+            return mDelayMillis;
         }
 
         /**
          * Cancels any queued but not-yet-executed {@link Runnable} previously added by this.
+         * This method must be called from the threading domain's thread.
          */
         public void cancel() {
             assertCurrentThread();
-            removeQueuedRunnables(this);
+            if (mIsQueued) {
+                removeQueuedRunnables(this);
+            }
+            mIsQueued = false;
+            mDelayMillis = -1;
         }
     }
 }
diff --git a/services/core/java/com/android/server/location/util/AlarmHelper.java b/services/core/java/com/android/server/location/util/AlarmHelper.java
new file mode 100644
index 0000000..46dbdbf
--- /dev/null
+++ b/services/core/java/com/android/server/location/util/AlarmHelper.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 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.location.util;
+
+import android.app.AlarmManager.OnAlarmListener;
+import android.os.WorkSource;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Helps manage alarms.
+ */
+public abstract class AlarmHelper {
+
+    /**
+     * Sets a wakeup alarm that will fire after the given delay.
+     */
+    public final void setDelayedAlarm(long delayMs, OnAlarmListener listener,
+            WorkSource workSource) {
+        // helps ensure that we're not wasting system resources by setting alarms in the past/now
+        Preconditions.checkArgument(delayMs > 0);
+        Preconditions.checkArgument(workSource != null);
+        setDelayedAlarmInternal(delayMs, listener, workSource);
+    }
+
+    protected abstract void setDelayedAlarmInternal(long delayMs, OnAlarmListener listener,
+            WorkSource workSource);
+
+    /**
+     * Cancels an alarm.
+     */
+    public abstract void cancel(OnAlarmListener listener);
+}
diff --git a/services/core/java/com/android/server/location/util/Injector.java b/services/core/java/com/android/server/location/util/Injector.java
index 379b303..d9c73ba 100644
--- a/services/core/java/com/android/server/location/util/Injector.java
+++ b/services/core/java/com/android/server/location/util/Injector.java
@@ -28,6 +28,9 @@
     /** Returns a UserInfoHelper. */
     UserInfoHelper getUserInfoHelper();
 
+    /** Returns an AlarmHelper. */
+    AlarmHelper getAlarmHelper();
+
     /** Returns an AppOpsHelper. */
     AppOpsHelper getAppOpsHelper();
 
diff --git a/services/core/java/com/android/server/location/util/SystemAlarmHelper.java b/services/core/java/com/android/server/location/util/SystemAlarmHelper.java
new file mode 100644
index 0000000..81849794
--- /dev/null
+++ b/services/core/java/com/android/server/location/util/SystemAlarmHelper.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2020 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.location.util;
+
+import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
+import static android.app.AlarmManager.WINDOW_EXACT;
+
+import android.app.AlarmManager;
+import android.content.Context;
+import android.os.SystemClock;
+import android.os.WorkSource;
+
+import com.android.server.FgThread;
+
+import java.util.Objects;
+
+/**
+ * Provides helpers for alarms.
+ */
+public class SystemAlarmHelper extends AlarmHelper {
+
+    private final Context mContext;
+
+    public SystemAlarmHelper(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    public void setDelayedAlarmInternal(long delayMs, AlarmManager.OnAlarmListener listener,
+            WorkSource workSource) {
+        AlarmManager alarmManager = Objects.requireNonNull(
+                mContext.getSystemService(AlarmManager.class));
+        alarmManager.set(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + delayMs,
+                WINDOW_EXACT, 0, listener, FgThread.getHandler(), workSource);
+    }
+
+    @Override
+    public void cancel(AlarmManager.OnAlarmListener listener) {
+        AlarmManager alarmManager = Objects.requireNonNull(
+                mContext.getSystemService(AlarmManager.class));
+        alarmManager.cancel(listener);
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 1ae1681..67f218e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -25740,7 +25740,7 @@
         // This API is exposed temporarily to only the contacts provider. (b/158688602)
         final int callingUid = Binder.getCallingUid();
         ProviderInfo contactsProvider = resolveContentProviderInternal(
-                        ContactsContract.AUTHORITY, 0, UserHandle.USER_SYSTEM);
+                        ContactsContract.AUTHORITY, 0, UserHandle.getUserId(callingUid));
         if (contactsProvider == null || contactsProvider.applicationInfo == null
                 || !UserHandle.isSameApp(contactsProvider.applicationInfo.uid, callingUid)) {
             throw new SecurityException(callingUid + " is not allow to call grantImplicitAccess");
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 6b29129..0a8c8f6 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1729,6 +1729,7 @@
     }
 
     public void makeInitialized(@UserIdInt int userId) {
+        if (DBG) Slog.d(LOG_TAG, "makeInitialized(" + userId + ")");
         checkManageUsersPermission("makeInitialized");
         boolean scheduleWriteUser = false;
         UserData userData;
@@ -3553,8 +3554,7 @@
                 // Must start user (which will be stopped right away, through
                 // UserController.finishUserUnlockedCompleted) so services can properly
                 // intialize it.
-                // TODO(b/143092698): in the long-term, it might be better to add a onCreateUser()
-                // callback on SystemService instead.
+                // NOTE: user will be stopped on UserController.finishUserUnlockedCompleted().
                 Slog.i(LOG_TAG, "starting pre-created user " + userInfo.toFullString());
                 final IActivityManager am = ActivityManager.getService();
                 try {
@@ -4965,6 +4965,9 @@
                         UserData userData = getUserDataNoChecks(userId);
                         if (userData != null) {
                             writeUserLP(userData);
+                        } else {
+                            Slog.i(LOG_TAG, "handle(WRITE_USER_MSG): no data for user " + userId
+                                    + ", it was probably removed before handler could handle it");
                         }
                     }
             }
diff --git a/services/core/java/com/android/server/pm/permission/BasePermission.java b/services/core/java/com/android/server/pm/permission/BasePermission.java
index 524f9ac..eb06bf9 100644
--- a/services/core/java/com/android/server/pm/permission/BasePermission.java
+++ b/services/core/java/com/android/server/pm/permission/BasePermission.java
@@ -36,13 +36,14 @@
 import android.util.Log;
 import android.util.Slog;
 
-import com.android.internal.util.ArrayUtils;
 import com.android.server.pm.DumpState;
 import com.android.server.pm.PackageManagerService;
 import com.android.server.pm.PackageSettingBase;
 import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 
+import libcore.util.EmptyArray;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlSerializer;
 
@@ -95,7 +96,8 @@
     int uid;
 
     /** Additional GIDs given to apps granted this permission */
-    private int[] gids;
+    @NonNull
+    private int[] gids = EmptyArray.INT;
 
     /**
      * Flag indicating that {@link #gids} should be adjusted based on the
@@ -132,7 +134,7 @@
     public int getUid() {
         return uid;
     }
-    public void setGids(int[] gids, boolean perUser) {
+    public void setGids(@NonNull int[] gids, boolean perUser) {
         this.gids = gids;
         this.perUser = perUser;
     }
@@ -141,18 +143,20 @@
     }
 
     public boolean hasGids() {
-        return !ArrayUtils.isEmpty(gids);
+        return gids.length != 0;
     }
 
+    @NonNull
     public int[] computeGids(int userId) {
         if (perUser) {
             final int[] userGids = new int[gids.length];
             for (int i = 0; i < gids.length; i++) {
-                userGids[i] = UserHandle.getUid(userId, gids[i]);
+                final int gid = gids[i];
+                userGids[i] = UserHandle.getUid(userId, gid);
             }
             return userGids;
         } else {
-            return gids;
+            return gids.length != 0 ? gids.clone() : gids;
         }
     }
 
@@ -206,6 +210,11 @@
         return perm != null && (perm.getFlags() & PermissionInfo.FLAG_IMMUTABLY_RESTRICTED) != 0;
     }
 
+    public boolean isInstallerExemptIgnored() {
+        return perm != null
+                && (perm.getFlags() & PermissionInfo.FLAG_INSTALLER_EXEMPT_IGNORED) != 0;
+    }
+
     public boolean isSignature() {
         return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) ==
                 PermissionInfo.PROTECTION_SIGNATURE;
@@ -286,7 +295,8 @@
             pendingPermissionInfo.packageName = newPackageName;
         }
         uid = 0;
-        setGids(null, false);
+        gids = EmptyArray.INT;
+        perUser = false;
     }
 
     public boolean addToTree(@ProtectionLevel int protectionLevel,
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 474ce7c..b293ba6 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -24,12 +24,14 @@
 import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISALLOWED;
 import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISCOURAGED;
 import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
@@ -53,7 +55,6 @@
 import static com.android.server.pm.PackageManagerService.DEBUG_PERMISSIONS;
 import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
-import static com.android.server.pm.permission.UidPermissionState.PERMISSION_OPERATION_FAILURE;
 
 import static java.util.concurrent.TimeUnit.SECONDS;
 
@@ -152,6 +153,8 @@
 import com.android.server.policy.PermissionPolicyInternal;
 import com.android.server.policy.SoftRestrictedPermissionPolicy;
 
+import libcore.util.EmptyArray;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -245,6 +248,7 @@
     private final SparseArray<ArraySet<String>> mSystemPermissions;
 
     /** Built-in group IDs given to all packages. Read from system configuration files. */
+    @NonNull
     private final int[] mGlobalGids;
 
     private final HandlerThread mHandlerThread;
@@ -785,6 +789,10 @@
             throw new IllegalArgumentException("Unknown permission: " + permName);
         }
 
+        if (bp.isInstallerExemptIgnored()) {
+            flagValues &= ~FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
+        }
+
         final UidPermissionState uidState = getUidState(pkg, userId);
         if (uidState == null) {
             Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId);
@@ -1096,7 +1104,8 @@
         Preconditions.checkFlagsArgument(flags,
                 PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
                         | PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
-                        | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER);
+                        | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER
+                        | PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE);
         Preconditions.checkArgumentNonNegative(userId, null);
 
         if (UserHandle.getCallingUserId() != userId) {
@@ -1120,16 +1129,16 @@
         final boolean isCallerInstallerOnRecord =
                 mPackageManagerInt.isCallerInstallerOfRecord(pkg, callingUid);
 
-        if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0
-                && !isCallerPrivileged) {
-            throw new SecurityException("Querying system whitelist requires "
+        if ((flags & (PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
+                | PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE)) != 0 && !isCallerPrivileged) {
+            throw new SecurityException("Querying system or role allowlist requires "
                     + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
         }
 
         if ((flags & (PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
                 | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)) != 0) {
             if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
-                throw new SecurityException("Querying upgrade or installer whitelist"
+                throw new SecurityException("Querying upgrade or installer allowlist"
                         + " requires being installer on record or "
                         + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
             }
@@ -1153,6 +1162,9 @@
             if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER) != 0) {
                 queryFlags |=  FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
             }
+            if ((flags & PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE) != 0) {
+                queryFlags |=  FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT;
+            }
 
             ArrayList<String> whitelistedPermissions = null;
 
@@ -1245,7 +1257,8 @@
         Preconditions.checkFlagsArgument(flags,
                 PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
                         | PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
-                        | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER);
+                        | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER
+                        | PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE);
         Preconditions.checkArgument(Integer.bitCount(flags) == 1);
         Preconditions.checkArgumentNonNegative(userId, null);
 
@@ -1271,15 +1284,16 @@
         final boolean isCallerInstallerOnRecord =
                 mPackageManagerInt.isCallerInstallerOfRecord(pkg, callingUid);
 
-        if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0
+        if ((flags & (PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
+                | PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE)) != 0
                 && !isCallerPrivileged) {
-            throw new SecurityException("Modifying system whitelist requires "
+            throw new SecurityException("Modifying system or role allowlist requires "
                     + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
         }
 
         if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE) != 0) {
             if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
-                throw new SecurityException("Modifying upgrade whitelist requires"
+                throw new SecurityException("Modifying upgrade allowlist requires"
                         + " being installer on record or "
                         + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
             }
@@ -1501,7 +1515,7 @@
             // normal runtime permissions.  For now they apply to all users.
             // TODO(zhanghai): We are breaking the behavior above by making all permission state
             //  per-user. It isn't documented behavior and relatively rarely used anyway.
-            if (uidState.grantPermission(bp) != PERMISSION_OPERATION_FAILURE) {
+            if (uidState.grantPermission(bp)) {
                 if (callback != null) {
                     callback.onInstallPermissionGranted();
                 }
@@ -1519,18 +1533,14 @@
             return;
         }
 
-        final int result = uidState.grantPermission(bp);
-        switch (result) {
-            case PERMISSION_OPERATION_FAILURE: {
-                return;
-            }
+        if (!uidState.grantPermission(bp)) {
+            return;
+        }
 
-            case UidPermissionState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {
-                if (callback != null) {
-                    callback.onGidsChanged(UserHandle.getAppId(pkg.getUid()), userId);
-                }
+        if (bp.hasGids()) {
+            if (callback != null) {
+                callback.onGidsChanged(UserHandle.getAppId(pkg.getUid()), userId);
             }
-            break;
         }
 
         if (bp.isRuntime()) {
@@ -1650,7 +1660,7 @@
             // normal runtime permissions.  For now they apply to all users.
             // TODO(zhanghai): We are breaking the behavior above by making all permission state
             //  per-user. It isn't documented behavior and relatively rarely used anyway.
-            if (uidState.revokePermission(bp) != PERMISSION_OPERATION_FAILURE) {
+            if (uidState.revokePermission(bp)) {
                 if (callback != null) {
                     mDefaultPermissionCallback.onInstallPermissionRevoked();
                 }
@@ -1658,12 +1668,7 @@
             return;
         }
 
-        // Permission is already revoked, no need to do anything.
-        if (!uidState.isPermissionGranted(permName)) {
-            return;
-        }
-
-        if (uidState.revokePermission(bp) == PERMISSION_OPERATION_FAILURE) {
+        if (!uidState.revokePermission(bp)) {
             return;
         }
 
@@ -2077,6 +2082,15 @@
             return false;
         }
 
+        BasePermission permission = getPermission(permName);
+        if (permission == null) {
+            return false;
+        }
+        if (permission.isHardRestricted()
+                && (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) == 0) {
+            return false;
+        }
+
         final long token = Binder.clearCallingIdentity();
         try {
             if (permName.equals(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
@@ -2504,11 +2518,11 @@
         }
     }
 
-    @Nullable
+    @NonNull
     private int[] getPermissionGids(@NonNull String permissionName, @UserIdInt int userId) {
         BasePermission permission = mSettings.getPermission(permissionName);
         if (permission == null) {
-            return null;
+            return EmptyArray.INT;
         }
         return permission.computeGids(userId);
     }
@@ -2629,8 +2643,6 @@
                 }
             }
 
-            uidState.setGlobalGids(mGlobalGids);
-
             ArraySet<String> newImplicitPermissions = new ArraySet<>();
             final String friendlyName = pkg.getPackageName() + "(" + pkg.getUid() + ")";
 
@@ -2765,7 +2777,7 @@
                         switch (grant) {
                             case GRANT_INSTALL: {
                                 // Grant an install permission.
-                                if (uidState.grantPermission(bp) != PERMISSION_OPERATION_FAILURE) {
+                                if (uidState.grantPermission(bp)) {
                                     changedInstallPermission = true;
                                 }
                             } break;
@@ -2797,8 +2809,7 @@
                                     if (permissionPolicyInitialized && hardRestricted) {
                                         if (!restrictionExempt) {
                                             if (origPermState != null && origPermState.isGranted()
-                                                    && uidState.revokePermission(
-                                                    bp) != PERMISSION_OPERATION_FAILURE) {
+                                                    && uidState.revokePermission(bp)) {
                                                 wasChanged = true;
                                             }
                                             if (!restrictionApplied) {
@@ -2830,8 +2841,7 @@
                                             || (!hardRestricted || restrictionExempt)) {
                                         if ((origPermState != null && origPermState.isGranted())
                                                 || upgradedActivityRecognitionPermission != null) {
-                                            if (uidState.grantPermission(bp)
-                                                    == PERMISSION_OPERATION_FAILURE) {
+                                            if (!uidState.grantPermission(bp)) {
                                                 wasChanged = true;
                                             }
                                         }
@@ -2850,8 +2860,7 @@
                                     }
 
                                     if (!uidState.isPermissionGranted(bp.name)
-                                            && uidState.grantPermission(bp)
-                                                    != PERMISSION_OPERATION_FAILURE) {
+                                            && uidState.grantPermission(bp)) {
                                         wasChanged = true;
                                     }
 
@@ -2899,13 +2908,11 @@
                             } break;
                         }
                     } else {
-                        if (uidState.revokePermission(bp) != PERMISSION_OPERATION_FAILURE) {
-                            // Also drop the permission flags.
-                            uidState.updatePermissionFlags(bp,
-                                    MASK_PERMISSION_FLAGS_ALL, 0);
-                            changedInstallPermission = true;
-                            if (DEBUG_PERMISSIONS) {
-                                Slog.i(TAG, "Un-granting permission " + perm
+                        if (DEBUG_PERMISSIONS) {
+                            boolean wasGranted = uidState.isPermissionGranted(bp.name);
+                            if (wasGranted || bp.isAppOp()) {
+                                Slog.i(TAG, (wasGranted ? "Un-granting" : "Not granting")
+                                        + " permission " + perm
                                         + " from package " + friendlyName
                                         + " (protectionLevel=" + bp.getProtectionLevel()
                                         + " flags=0x"
@@ -2913,20 +2920,9 @@
                                                 ps))
                                         + ")");
                             }
-                        } else if (bp.isAppOp()) {
-                            // Don't print warning for app op permissions, since it is fine for them
-                            // not to be granted, there is a UI for the user to decide.
-                            if (DEBUG_PERMISSIONS
-                                    && (packageOfInterest == null
-                                            || packageOfInterest.equals(pkg.getPackageName()))) {
-                                Slog.i(TAG, "Not granting permission " + perm
-                                        + " to package " + friendlyName
-                                        + " (protectionLevel=" + bp.getProtectionLevel()
-                                        + " flags=0x"
-                                        + Integer.toHexString(PackageInfoUtils.appInfoFlags(pkg,
-                                                ps))
-                                        + ")");
-                            }
+                        }
+                        if (uidState.removePermissionState(bp.name)) {
+                            changedInstallPermission = true;
                         }
                     }
                 }
@@ -3005,8 +3001,7 @@
 
                         if ((flags & BLOCKING_PERMISSION_FLAGS) == 0
                                 && supportsRuntimePermissions) {
-                            int revokeResult = ps.revokePermission(bp);
-                            if (revokeResult != PERMISSION_OPERATION_FAILURE) {
+                            if (ps.revokePermission(bp)) {
                                 if (DEBUG_PERMISSIONS) {
                                     Slog.i(TAG, "Revoking runtime permission "
                                             + permission + " for " + pkgName
@@ -3730,6 +3725,15 @@
                             }
                         }
                         break;
+                        case FLAG_PERMISSION_ALLOWLIST_ROLE: {
+                            mask |= FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT;
+                            if (permissions != null && permissions.contains(permissionName)) {
+                                newFlags |= FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT;
+                            } else {
+                                newFlags &= ~FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT;
+                            }
+                        }
+                        break;
                     }
                 }
 
@@ -3865,14 +3869,9 @@
                 }
             }
 
-            // The package is gone - no need to keep flags for applying policy.
-            uidState.updatePermissionFlags(bp, PackageManager.MASK_PERMISSION_FLAGS_ALL, 0);
-
-            // Try to revoke as a runtime permission which is per user.
-            // TODO(zhanghai): This doesn't make sense. revokePermission() doesn't fail, and why are
-            //  we only killing the uid when gids changed, instead of any permission change?
-            if (uidState.revokePermission(bp)
-                    == UidPermissionState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) {
+            // TODO(zhanghai): Why are we only killing the UID when GIDs changed, instead of any
+            //  permission change?
+            if (uidState.removePermissionState(bp.name) && bp.hasGids()) {
                 affectedUserId = userId;
             }
         }
@@ -3905,17 +3904,14 @@
         boolean runtimePermissionChanged = false;
 
         // Prune permissions
-        final List<com.android.server.pm.permission.PermissionState> permissionStates =
-                uidState.getPermissionStates();
+        final List<PermissionState> permissionStates = uidState.getPermissionStates();
         final int permissionStatesSize = permissionStates.size();
         for (int i = permissionStatesSize - 1; i >= 0; i--) {
             PermissionState permissionState = permissionStates.get(i);
             if (!usedPermissions.contains(permissionState.getName())) {
                 BasePermission bp = mSettings.getPermissionLocked(permissionState.getName());
                 if (bp != null) {
-                    uidState.revokePermission(bp);
-                    uidState.updatePermissionFlags(bp, MASK_PERMISSION_FLAGS_ALL, 0);
-                    if (permissionState.isRuntime()) {
+                    if (uidState.removePermissionState(bp.name) && permissionState.isRuntime()) {
                         runtimePermissionChanged = true;
                     }
                 }
@@ -4178,11 +4174,7 @@
                                             + p.getPackageName() + " and user " + userId);
                                     return;
                                 }
-                                if (uidState.getPermissionState(bp.getName()) != null) {
-                                    uidState.revokePermission(bp);
-                                    uidState.updatePermissionFlags(bp, MASK_PERMISSION_FLAGS_ALL,
-                                            0);
-                                }
+                                uidState.removePermissionState(bp.name);
                             }
                         });
                     }
@@ -4741,7 +4733,7 @@
             Slog.e(TAG, "Missing permissions state for app ID " + appId + " and user ID " + userId);
             return EMPTY_INT_ARRAY;
         }
-        return uidState.computeGids(userId);
+        return uidState.computeGids(mGlobalGids, userId);
     }
 
     private class PermissionManagerServiceInternalImpl extends PermissionManagerServiceInternal {
@@ -4804,7 +4796,7 @@
                 @UserIdInt int userId) {
             return PermissionManagerService.this.getGrantedPermissions(packageName, userId);
         }
-        @Nullable
+        @NonNull
         @Override
         public int[] getPermissionGids(@NonNull String permissionName, @UserIdInt int userId) {
             return PermissionManagerService.this.getPermissionGids(permissionName, userId);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionState.java b/services/core/java/com/android/server/pm/permission/PermissionState.java
index 38264c8..59b204f 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionState.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionState.java
@@ -17,7 +17,6 @@
 package com.android.server.pm.permission;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 
 import com.android.internal.annotations.GuardedBy;
@@ -62,7 +61,7 @@
         return mPermission.getName();
     }
 
-    @Nullable
+    @NonNull
     public int[] computeGids(@UserIdInt int userId) {
         return mPermission.computeGids(userId);
     }
diff --git a/services/core/java/com/android/server/pm/permission/UidPermissionState.java b/services/core/java/com/android/server/pm/permission/UidPermissionState.java
index 06a7f8d..c73e2f3 100644
--- a/services/core/java/com/android/server/pm/permission/UidPermissionState.java
+++ b/services/core/java/com/android/server/pm/permission/UidPermissionState.java
@@ -22,35 +22,19 @@
 import android.content.pm.PackageManager;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.IntArray;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.ArrayUtils;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 
 /**
  * Permission state for a UID.
- * <p>
- * This class is also responsible for keeping track of the Linux GIDs per
- * user for a package or a shared user. The GIDs are computed as a set of
- * the GIDs for all granted permissions' GIDs on a per user basis.
  */
 public final class UidPermissionState {
-    /** The permission operation failed. */
-    public static final int PERMISSION_OPERATION_FAILURE = -1;
-
-    /** The permission operation succeeded and no gids changed. */
-    public static final int PERMISSION_OPERATION_SUCCESS = 0;
-
-    /** The permission operation succeeded and gids changed. */
-    public static final int PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED = 1;
-
-    private static final int[] NO_GIDS = {};
-
     @NonNull
     private final Object mLock = new Object();
 
@@ -60,11 +44,6 @@
     @Nullable
     private ArrayMap<String, PermissionState> mPermissions;
 
-    private boolean mPermissionReviewRequired;
-
-    @NonNull
-    private int[] mGlobalGids = NO_GIDS;
-
     public UidPermissionState() {}
 
     public UidPermissionState(@NonNull UidPermissionState other) {
@@ -80,12 +59,6 @@
                     mPermissions.put(name, new PermissionState(permissionState));
                 }
             }
-
-            mPermissionReviewRequired = other.mPermissionReviewRequired;
-
-            if (other.mGlobalGids != NO_GIDS) {
-                mGlobalGids = other.mGlobalGids.clone();
-            }
         }
     }
 
@@ -96,8 +69,6 @@
         synchronized (mLock) {
             mMissing = false;
             mPermissions = null;
-            mPermissionReviewRequired = false;
-            mGlobalGids = NO_GIDS;
             invalidateCache();
         }
     }
@@ -156,8 +127,8 @@
     /**
      * Gets the state for a permission or null if none.
      *
-     * @param name the permission name.
-     * @return the permission state.
+     * @param name the permission name
+     * @return the permission state
      */
     @Nullable
     public PermissionState getPermissionState(@NonNull String name) {
@@ -169,6 +140,22 @@
         }
     }
 
+    @NonNull
+    private PermissionState getOrCreatePermissionState(@NonNull BasePermission permission) {
+        synchronized (mLock) {
+            if (mPermissions == null) {
+                mPermissions = new ArrayMap<>();
+            }
+            final String name = permission.getName();
+            PermissionState permissionState = mPermissions.get(name);
+            if (permissionState == null) {
+                permissionState = new PermissionState(permission);
+                mPermissions.put(name, permissionState);
+            }
+            return permissionState;
+        }
+    }
+
     /**
      * Get all permission states.
      *
@@ -186,19 +173,44 @@
 
     /**
      * Put a permission state.
+     *
+     * @param permission the permission
+     * @param granted whether the permission is granted
+     * @param flags the permission flags
      */
-    public void putPermissionState(@NonNull BasePermission permission, boolean isGranted,
-            int flags) {
+    public void putPermissionState(@NonNull BasePermission permission, boolean granted, int flags) {
         synchronized (mLock) {
-            ensureNoPermissionState(permission.name);
-            PermissionState permissionState = ensurePermissionState(permission);
-            if (isGranted) {
+            final String name = permission.getName();
+            if (mPermissions == null) {
+                mPermissions = new ArrayMap<>();
+            } else {
+                mPermissions.remove(name);
+            }
+            final PermissionState permissionState = new PermissionState(permission);
+            if (granted) {
                 permissionState.grant();
             }
             permissionState.updateFlags(flags, flags);
-            if ((flags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
-                mPermissionReviewRequired = true;
+            mPermissions.put(name, permissionState);
+        }
+    }
+
+    /**
+     * Remove a permission state.
+     *
+     * @param name the permission name
+     * @return whether the permission state changed
+     */
+    public boolean removePermissionState(@NonNull String name) {
+        synchronized (mLock) {
+            if (mPermissions == null) {
+                return false;
             }
+            boolean changed = mPermissions.remove(name) != null;
+            if (changed && mPermissions.isEmpty()) {
+                mPermissions = null;
+            }
+            return changed;
         }
     }
 
@@ -209,13 +221,8 @@
      * @return whether the permission is granted
      */
     public boolean isPermissionGranted(@NonNull String name) {
-        synchronized (mLock) {
-            if (mPermissions == null) {
-                return false;
-            }
-            PermissionState permissionState = mPermissions.get(name);
-            return permissionState != null && permissionState.isGranted();
-        }
+        final PermissionState permissionState = getPermissionState(name);
+        return permissionState != null && permissionState.isGranted();
     }
 
     /**
@@ -246,61 +253,37 @@
     /**
      * Grant a permission.
      *
-     * @param permission the permission to grantt
-     * @return the operation result, which is either {@link #PERMISSION_OPERATION_SUCCESS},
-     *     or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
-     *     #PERMISSION_OPERATION_FAILURE}.
+     * @param permission the permission to grant
+     * @return whether the permission grant state changed
      */
-    public int grantPermission(@NonNull BasePermission permission) {
-        if (isPermissionGranted(permission.getName())) {
-            return PERMISSION_OPERATION_SUCCESS;
-        }
-
-        PermissionState permissionState = ensurePermissionState(permission);
-
-        if (!permissionState.grant()) {
-            return PERMISSION_OPERATION_FAILURE;
-        }
-
-        return permission.hasGids() ? PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED
-                : PERMISSION_OPERATION_SUCCESS;
+    public boolean grantPermission(@NonNull BasePermission permission) {
+        PermissionState permissionState = getOrCreatePermissionState(permission);
+        return permissionState.grant();
     }
 
     /**
      * Revoke a permission.
      *
      * @param permission the permission to revoke
-     * @return the operation result, which is either {@link #PERMISSION_OPERATION_SUCCESS},
-     *     or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
-     *     #PERMISSION_OPERATION_FAILURE}.
+     * @return whether the permission grant state changed
      */
-    public int revokePermission(@NonNull BasePermission permission) {
+    public boolean revokePermission(@NonNull BasePermission permission) {
         final String name = permission.getName();
-        if (!isPermissionGranted(name)) {
-            return PERMISSION_OPERATION_SUCCESS;
+        final PermissionState permissionState = getPermissionState(name);
+        if (permissionState == null) {
+            return false;
         }
-
-        PermissionState permissionState;
-        synchronized (mLock) {
-            permissionState = mPermissions.get(name);
+        final boolean changed = permissionState.revoke();
+        if (changed && permissionState.isDefault()) {
+            removePermissionState(name);
         }
-
-        if (!permissionState.revoke()) {
-            return PERMISSION_OPERATION_FAILURE;
-        }
-
-        if (permissionState.isDefault()) {
-            ensureNoPermissionState(name);
-        }
-
-        return permission.hasGids() ? PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED
-                : PERMISSION_OPERATION_SUCCESS;
+        return changed;
     }
 
     /**
      * Get the flags for a permission.
      *
-     * @param name the permission name.
+     * @param name the permission name
      * @return the permission flags
      */
     public int getPermissionFlags(@NonNull String name) {
@@ -324,76 +307,36 @@
         if (flagMask == 0) {
             return false;
         }
-
-        synchronized (mLock) {
-            final PermissionState permissionState = ensurePermissionState(permission);
-            final int oldFlags = permissionState.getFlags();
-
-            final boolean updated = permissionState.updateFlags(flagMask, flagValues);
-            if (updated) {
-                final int newFlags = permissionState.getFlags();
-                if ((oldFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) == 0
-                        && (newFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
-                    mPermissionReviewRequired = true;
-                } else if ((oldFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0
-                        && (newFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
-                    if (mPermissionReviewRequired && !hasPermissionRequiringReview()) {
-                        mPermissionReviewRequired = false;
-                    }
-                }
-            }
-            return updated;
+        final PermissionState permissionState = getOrCreatePermissionState(permission);
+        final boolean changed = permissionState.updateFlags(flagMask, flagValues);
+        if (changed && permissionState.isDefault()) {
+            removePermissionState(permission.name);
         }
+        return changed;
     }
 
     public boolean updatePermissionFlagsForAllPermissions(int flagMask, int flagValues) {
+        if (flagMask == 0) {
+            return false;
+        }
         synchronized (mLock) {
             if (mPermissions == null) {
                 return false;
             }
-            boolean changed = false;
-            final int permissionsSize = mPermissions.size();
-            for (int i = 0; i < permissionsSize; i++) {
+            boolean anyChanged = false;
+            for (int i = mPermissions.size() - 1; i >= 0; i--) {
                 final PermissionState permissionState = mPermissions.valueAt(i);
-                changed |= permissionState.updateFlags(flagMask, flagValues);
+                final boolean changed = permissionState.updateFlags(flagMask, flagValues);
+                if (changed && permissionState.isDefault()) {
+                    mPermissions.removeAt(i);
+                }
+                anyChanged |= changed;
             }
-            return changed;
-        }
-    }
-
-    @NonNull
-    private PermissionState ensurePermissionState(@NonNull BasePermission permission) {
-        final String name = permission.getName();
-        synchronized (mLock) {
-            if (mPermissions == null) {
-                mPermissions = new ArrayMap<>();
-            }
-            PermissionState permissionState = mPermissions.get(name);
-            if (permissionState == null) {
-                permissionState = new PermissionState(permission);
-                mPermissions.put(name, permissionState);
-            }
-            return permissionState;
-        }
-    }
-
-    private void ensureNoPermissionState(@NonNull String name) {
-        synchronized (mLock) {
-            if (mPermissions == null) {
-                return;
-            }
-            mPermissions.remove(name);
-            if (mPermissions.isEmpty()) {
-                mPermissions = null;
-            }
+            return anyChanged;
         }
     }
 
     public boolean isPermissionReviewRequired() {
-        return mPermissionReviewRequired;
-    }
-
-    private boolean hasPermissionRequiringReview() {
         synchronized (mLock) {
             final int permissionsSize = mPermissions.size();
             for (int i = 0; i < permissionsSize; i++) {
@@ -407,81 +350,31 @@
     }
 
     /**
-     * Gets the global gids, applicable to all users.
-     */
-    @NonNull
-    public int[] getGlobalGids() {
-        return mGlobalGids;
-    }
-
-    /**
-     * Sets the global gids, applicable to all users.
-     *
-     * @param globalGids The global gids.
-     */
-    public void setGlobalGids(@NonNull int[] globalGids) {
-        if (!ArrayUtils.isEmpty(globalGids)) {
-            mGlobalGids = Arrays.copyOf(globalGids, globalGids.length);
-        } else {
-            mGlobalGids = NO_GIDS;
-        }
-    }
-
-    /**
      * Compute the Linux GIDs from the permissions granted to a user.
      *
      * @param userId the user ID
      * @return the GIDs for the user
      */
     @NonNull
-    public int[] computeGids(@UserIdInt int userId) {
-        int[] gids = mGlobalGids;
-
+    public int[] computeGids(@NonNull int[] globalGids, @UserIdInt int userId) {
         synchronized (mLock) {
-            if (mPermissions != null) {
-                final int permissionCount = mPermissions.size();
-                for (int i = 0; i < permissionCount; i++) {
-                    PermissionState permissionState = mPermissions.valueAt(i);
-                    if (!permissionState.isGranted()) {
-                        continue;
-                    }
-                    final int[] permGids = permissionState.computeGids(userId);
-                    if (permGids != NO_GIDS) {
-                        gids = appendInts(gids, permGids);
-                    }
+            IntArray gids = IntArray.wrap(globalGids);
+            if (mPermissions == null) {
+                return gids.toArray();
+            }
+            final int permissionsSize = mPermissions.size();
+            for (int i = 0; i < permissionsSize; i++) {
+                PermissionState permissionState = mPermissions.valueAt(i);
+                if (!permissionState.isGranted()) {
+                    continue;
+                }
+                final int[] permissionGids = permissionState.computeGids(userId);
+                if (permissionGids.length != 0) {
+                    gids.addAll(permissionGids);
                 }
             }
+            return gids.toArray();
         }
-
-        return gids;
-    }
-
-    /**
-     * Compute the Linux GIDs from the permissions granted to specified users.
-     *
-     * @param userIds the user IDs
-     * @return the GIDs for the user
-     */
-    @NonNull
-    public int[] computeGids(@NonNull int[] userIds) {
-        int[] gids = mGlobalGids;
-
-        for (final int userId : userIds) {
-            final int[] userGids = computeGids(userId);
-            gids = appendInts(gids, userGids);
-        }
-
-        return gids;
-    }
-
-    // TODO: fix this to use arraycopy and append all ints in one go
-    private static int[] appendInts(int[] current, int[] added) {
-        if (current != null && added != null) {
-            for (int guid : added) {
-                current = ArrayUtils.appendInt(current, guid);
-            }
-        }
-        return current;
     }
 
     static void invalidateCache() {
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 874b910..d1fd0ad 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -56,6 +56,7 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.inputmethod.SoftInputShowHideReason;
+import com.android.internal.os.TransferPipe;
 import com.android.internal.statusbar.IStatusBar;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
@@ -1510,6 +1511,23 @@
         return mContext.getResources().getStringArray(R.array.config_statusBarIcons);
     }
 
+    /** @hide */
+    public void passThroughShellCommand(String[] args, FileDescriptor fd) {
+        enforceStatusBarOrShell();
+        if (mBar == null)  return;
+
+        try (TransferPipe tp = new TransferPipe()) {
+            // Sending the command to the remote, which needs to execute async to avoid blocking
+            // See Binder#dumpAsync() for inspiration
+            tp.setBufferPrefix("  ");
+            mBar.passThroughShellCommand(args, tp.getWriteFd());
+            // Times out after 5s
+            tp.go(fd);
+        } catch (Throwable t) {
+            Slog.e(TAG, "Error sending command to IStatusBar", t);
+        }
+    }
+
     // ================================================================================
     // Can be called from any thread
     // ================================================================================
diff --git a/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java b/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
index a79c19f..6171822 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
@@ -46,7 +46,8 @@
     @Override
     public int onCommand(String cmd) {
         if (cmd == null) {
-            return handleDefaultCommands(cmd);
+            onHelp();
+            return 1;
         }
         try {
             switch (cmd) {
@@ -74,8 +75,16 @@
                     return runSendDisableFlag();
                 case "tracing":
                     return runTracing();
+                // Handle everything that would be handled in `handleDefaultCommand()` explicitly,
+                // so the default can be to pass all args to StatusBar
+                case "-h":
+                case "help":
+                    onHelp();
+                    return 0;
+                case "dump":
+                    return super.handleDefaultCommands(cmd);
                 default:
-                    return handleDefaultCommands(cmd);
+                    return runPassArgsToStatusBar();
             }
         } catch (RemoteException e) {
             final PrintWriter pw = getOutPrintWriter();
@@ -187,6 +196,11 @@
         return 0;
     }
 
+    private int runPassArgsToStatusBar() {
+        mInterface.passThroughShellCommand(getAllArgs(), getOutFileDescriptor());
+        return 0;
+    }
+
     private int runTracing() {
         switch (getNextArg()) {
             case "start":
@@ -250,6 +264,12 @@
         pw.println("  tracing (start | stop)");
         pw.println("    Start or stop SystemUI tracing");
         pw.println("");
+        pw.println("  NOTE: any command not listed here will be passed through to IStatusBar");
+        pw.println("");
+        pw.println("  Commands implemented in SystemUI:");
+        pw.flush();
+        // Sending null args to systemui will print help
+        mInterface.passThroughShellCommand(new String[] {}, getOutFileDescriptor());
     }
 
     /**
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 3406bd9..70ab48b 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -84,6 +84,7 @@
         contentResolver.registerContentObserver(
                 Settings.Global.getUriFor(Settings.Global.AUTO_TIME), true,
                 new ContentObserver(handler) {
+                    @Override
                     public void onChange(boolean selfChange) {
                         timeDetectorService.handleAutoTimeDetectionChanged();
                     }
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index fe0e82e..9c18aad 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -572,7 +572,7 @@
      */
     @VisibleForTesting
     @Nullable
-    public NetworkTimeSuggestion findLatestValidNetworkSuggestionForTests() {
+    public synchronized NetworkTimeSuggestion findLatestValidNetworkSuggestionForTests() {
         return findLatestValidNetworkSuggestion();
     }
 
@@ -590,7 +590,7 @@
      */
     @VisibleForTesting
     @Nullable
-    public NetworkTimeSuggestion getLatestNetworkSuggestion() {
+    public synchronized NetworkTimeSuggestion getLatestNetworkSuggestion() {
         return mLastNetworkSuggestion.get();
     }
 
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
index b68c54f..9e76bc1 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
@@ -16,15 +16,16 @@
 
 package com.android.server.timezonedetector;
 
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED;
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_POSSESSED;
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
-import android.app.timezonedetector.TimeZoneCapabilities;
-import android.app.timezonedetector.TimeZoneConfiguration;
+import android.app.time.TimeZoneCapabilities;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
 import android.os.UserHandle;
 
 import java.util.Objects;
@@ -32,7 +33,7 @@
 /**
  * Holds all configuration values that affect time zone behavior and some associated logic, e.g.
  * {@link #getAutoDetectionEnabledBehavior()}, {@link #getGeoDetectionEnabledBehavior()} and {@link
- * #createCapabilities()}.
+ * #createCapabilitiesAndConfig()}.
  */
 public final class ConfigurationInternal {
 
@@ -106,10 +107,15 @@
         return false;
     }
 
-    /** Creates a {@link TimeZoneCapabilities} object using the configuration values. */
-    public TimeZoneCapabilities createCapabilities() {
-        TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder()
-                .setConfiguration(asConfiguration());
+    /** Creates a {@link TimeZoneCapabilitiesAndConfig} object using the configuration values. */
+    public TimeZoneCapabilitiesAndConfig createCapabilitiesAndConfig() {
+        return new TimeZoneCapabilitiesAndConfig(asCapabilities(), asConfiguration());
+    }
+
+    @NonNull
+    private TimeZoneCapabilities asCapabilities() {
+        UserHandle userHandle = UserHandle.of(mUserId);
+        TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(userHandle);
 
         boolean allowConfigDateTime = isUserConfigAllowed();
 
@@ -125,7 +131,7 @@
         } else {
             configureAutoDetectionEnabledCapability = CAPABILITY_POSSESSED;
         }
-        builder.setConfigureAutoDetectionEnabled(configureAutoDetectionEnabledCapability);
+        builder.setConfigureAutoDetectionEnabledCapability(configureAutoDetectionEnabledCapability);
 
         final int configureGeolocationDetectionEnabledCapability;
         if (!deviceHasTimeZoneDetection) {
@@ -137,7 +143,8 @@
         } else {
             configureGeolocationDetectionEnabledCapability = CAPABILITY_POSSESSED;
         }
-        builder.setConfigureGeoDetectionEnabled(configureGeolocationDetectionEnabledCapability);
+        builder.setConfigureGeoDetectionEnabledCapability(
+                configureGeolocationDetectionEnabledCapability);
 
         // The ability to make manual time zone suggestions can also be restricted by policy. With
         // the current logic above, this could lead to a situation where a device hardware does not
@@ -151,14 +158,14 @@
         } else {
             suggestManualTimeZoneCapability = CAPABILITY_POSSESSED;
         }
-        builder.setSuggestManualTimeZone(suggestManualTimeZoneCapability);
+        builder.setSuggestManualTimeZoneCapability(suggestManualTimeZoneCapability);
 
         return builder.build();
     }
 
     /** Returns a {@link TimeZoneConfiguration} from the configuration values. */
-    public TimeZoneConfiguration asConfiguration() {
-        return new TimeZoneConfiguration.Builder(mUserId)
+    private TimeZoneConfiguration asConfiguration() {
+        return new TimeZoneConfiguration.Builder()
                 .setAutoDetectionEnabled(getAutoDetectionEnabledSetting())
                 .setGeoDetectionEnabled(getGeoDetectionEnabledSetting())
                 .build();
@@ -171,10 +178,10 @@
      */
     public ConfigurationInternal merge(TimeZoneConfiguration newConfiguration) {
         Builder builder = new Builder(this);
-        if (newConfiguration.hasSetting(TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED)) {
+        if (newConfiguration.hasIsAutoDetectionEnabled()) {
             builder.setAutoDetectionEnabled(newConfiguration.isAutoDetectionEnabled());
         }
-        if (newConfiguration.hasSetting(TimeZoneConfiguration.SETTING_GEO_DETECTION_ENABLED)) {
+        if (newConfiguration.hasIsGeoDetectionEnabled()) {
             builder.setGeoDetectionEnabled(newConfiguration.isGeoDetectionEnabled());
         }
         return builder.build();
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
index 6a12b7c..964dbec 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
@@ -23,7 +23,7 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManagerInternal;
 import android.app.AlarmManager;
-import android.app.timezonedetector.TimeZoneConfiguration;
+import android.app.time.TimeZoneConfiguration;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -87,6 +87,7 @@
         contentResolver.registerContentObserver(
                 Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true,
                 new ContentObserver(mHandler) {
+                    @Override
                     public void onChange(boolean selfChange) {
                         handleConfigChangeOnHandlerThread();
                     }
@@ -97,6 +98,7 @@
                 Settings.Secure.getUriFor(Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED),
                 true,
                 new ContentObserver(mHandler) {
+                    @Override
                     public void onChange(boolean selfChange) {
                         handleConfigChangeOnHandlerThread();
                     }
@@ -157,7 +159,7 @@
     }
 
     @Override
-    public void storeConfiguration(TimeZoneConfiguration configuration) {
+    public void storeConfiguration(@UserIdInt int userId, TimeZoneConfiguration configuration) {
         Objects.requireNonNull(configuration);
 
         // Avoid writing the auto detection enabled setting for devices that do not support auto
@@ -169,7 +171,6 @@
             setAutoDetectionEnabled(autoDetectionEnabled);
 
             if (mGeoDetectionFeatureEnabled) {
-                final int userId = configuration.getUserId();
                 final boolean geoTzDetectionEnabled = configuration.isGeoDetectionEnabled();
                 setGeoDetectionEnabled(userId, geoTzDetectionEnabled);
             }
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index 73322a6..6e1f89b 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -18,18 +18,19 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.timezonedetector.ITimeZoneConfigurationListener;
+import android.app.time.ITimeZoneDetectorListener;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
 import android.app.timezonedetector.ITimeZoneDetectorService;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.app.timezonedetector.TimeZoneCapabilities;
-import android.app.timezonedetector.TimeZoneConfiguration;
 import android.content.Context;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
+import android.util.ArrayMap;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
@@ -41,7 +42,6 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Objects;
 
 /**
@@ -109,10 +109,14 @@
     @NonNull
     private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
 
-    @GuardedBy("mConfigurationListeners")
+    /**
+     * Holds the listeners. The key is the {@link IBinder} associated with the listener, the value
+     * is the listener itself.
+     */
+    @GuardedBy("mListeners")
     @NonNull
-    private final ArrayList<ITimeZoneConfigurationListener> mConfigurationListeners =
-            new ArrayList<>();
+    private final ArrayMap<IBinder, ITimeZoneDetectorListener> mListeners =
+            new ArrayMap<>();
 
     private static TimeZoneDetectorService create(
             @NonNull Context context, @NonNull Handler handler,
@@ -133,20 +137,22 @@
         mCallerIdentityInjector = Objects.requireNonNull(callerIdentityInjector);
         mTimeZoneDetectorStrategy = Objects.requireNonNull(timeZoneDetectorStrategy);
 
-        // Wire up a change listener so that ITimeZoneConfigurationListeners can be notified when
+        // Wire up a change listener so that ITimeZoneDetectorListeners can be notified when
         // the configuration changes for any reason.
         mTimeZoneDetectorStrategy.addConfigChangeListener(this::handleConfigurationChanged);
     }
 
     @Override
     @NonNull
-    public TimeZoneCapabilities getCapabilities() {
-        enforceManageTimeZoneDetectorConfigurationPermission();
+    public TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig() {
+        enforceManageTimeZoneDetectorPermission();
 
         int userId = mCallerIdentityInjector.getCallingUserId();
         long token = mCallerIdentityInjector.clearCallingIdentity();
         try {
-            return mTimeZoneDetectorStrategy.getConfigurationInternal(userId).createCapabilities();
+            ConfigurationInternal configurationInternal =
+                    mTimeZoneDetectorStrategy.getConfigurationInternal(userId);
+            return configurationInternal.createCapabilitiesAndConfig();
         } finally {
             mCallerIdentityInjector.restoreCallingIdentity(token);
         }
@@ -154,37 +160,34 @@
 
     @Override
     public boolean updateConfiguration(@NonNull TimeZoneConfiguration configuration) {
-        enforceManageTimeZoneDetectorConfigurationPermission();
+        enforceManageTimeZoneDetectorPermission();
         Objects.requireNonNull(configuration);
 
         int callingUserId = mCallerIdentityInjector.getCallingUserId();
-        if (callingUserId != configuration.getUserId()) {
-            return false;
-        }
-
         long token = mCallerIdentityInjector.clearCallingIdentity();
         try {
-            return mTimeZoneDetectorStrategy.updateConfiguration(configuration);
+            return mTimeZoneDetectorStrategy.updateConfiguration(callingUserId, configuration);
         } finally {
             mCallerIdentityInjector.restoreCallingIdentity(token);
         }
     }
 
     @Override
-    public void addConfigurationListener(@NonNull ITimeZoneConfigurationListener listener) {
-        enforceManageTimeZoneDetectorConfigurationPermission();
+    public void addListener(@NonNull ITimeZoneDetectorListener listener) {
+        enforceManageTimeZoneDetectorPermission();
         Objects.requireNonNull(listener);
 
-        synchronized (mConfigurationListeners) {
-            if (mConfigurationListeners.contains(listener)) {
+        synchronized (mListeners) {
+            IBinder listenerBinder = listener.asBinder();
+            if (mListeners.containsKey(listenerBinder)) {
                 return;
             }
             try {
                 // Ensure the reference to the listener will be removed if the client process dies.
-                listener.asBinder().linkToDeath(this, 0 /* flags */);
+                listenerBinder.linkToDeath(this, 0 /* flags */);
 
                 // Only add the listener if we can linkToDeath().
-                mConfigurationListeners.add(listener);
+                mListeners.put(listenerBinder, listener);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Unable to linkToDeath() for listener=" + listener, e);
             }
@@ -192,21 +195,22 @@
     }
 
     @Override
-    public void removeConfigurationListener(@NonNull ITimeZoneConfigurationListener listener) {
-        enforceManageTimeZoneDetectorConfigurationPermission();
+    public void removeListener(@NonNull ITimeZoneDetectorListener listener) {
+        enforceManageTimeZoneDetectorPermission();
         Objects.requireNonNull(listener);
 
-        synchronized (mConfigurationListeners) {
+        synchronized (mListeners) {
+            IBinder listenerBinder = listener.asBinder();
             boolean removedListener = false;
-            if (mConfigurationListeners.remove(listener)) {
+            if (mListeners.remove(listenerBinder) != null) {
                 // Stop listening for the client process to die.
-                listener.asBinder().unlinkToDeath(this, 0 /* flags */);
+                listenerBinder.unlinkToDeath(this, 0 /* flags */);
                 removedListener = true;
             }
             if (!removedListener) {
                 Slog.w(TAG, "Client asked to remove listener=" + listener
                         + ", but no listeners were removed."
-                        + " mConfigurationListeners=" + mConfigurationListeners);
+                        + " mListeners=" + mListeners);
             }
         }
     }
@@ -218,19 +222,18 @@
     }
 
     /**
-     * Called when one of the ITimeZoneConfigurationListener processes dies before calling
-     * {@link #removeConfigurationListener(ITimeZoneConfigurationListener)}.
+     * Called when one of the ITimeZoneDetectorListener processes dies before calling
+     * {@link #removeListener(ITimeZoneDetectorListener)}.
      */
     @Override
     public void binderDied(IBinder who) {
-        synchronized (mConfigurationListeners) {
+        synchronized (mListeners) {
             boolean removedListener = false;
-            final int listenerCount = mConfigurationListeners.size();
+            final int listenerCount = mListeners.size();
             for (int listenerIndex = listenerCount - 1; listenerIndex >= 0; listenerIndex--) {
-                ITimeZoneConfigurationListener listener =
-                        mConfigurationListeners.get(listenerIndex);
-                if (listener.asBinder().equals(who)) {
-                    mConfigurationListeners.remove(listenerIndex);
+                IBinder listenerBinder = mListeners.keyAt(listenerIndex);
+                if (listenerBinder.equals(who)) {
+                    mListeners.removeAt(listenerIndex);
                     removedListener = true;
                     break;
                 }
@@ -238,7 +241,7 @@
             if (!removedListener) {
                 Slog.w(TAG, "Notified of binder death for who=" + who
                         + ", but did not remove any listeners."
-                        + " mConfigurationListeners=" + mConfigurationListeners);
+                        + " mConfigurationListeners=" + mListeners);
             }
         }
     }
@@ -247,11 +250,10 @@
         // Configuration has changed, but each user may have a different view of the configuration.
         // It's possible that this will cause unnecessary notifications but that shouldn't be a
         // problem.
-        synchronized (mConfigurationListeners) {
-            final int listenerCount = mConfigurationListeners.size();
+        synchronized (mListeners) {
+            final int listenerCount = mListeners.size();
             for (int listenerIndex = 0; listenerIndex < listenerCount; listenerIndex++) {
-                ITimeZoneConfigurationListener listener =
-                        mConfigurationListeners.get(listenerIndex);
+                ITimeZoneDetectorListener listener = mListeners.valueAt(listenerIndex);
                 try {
                     listener.onChange();
                 } catch (RemoteException e) {
@@ -302,11 +304,10 @@
         ipw.flush();
     }
 
-    private void enforceManageTimeZoneDetectorConfigurationPermission() {
-        // TODO Switch to a dedicated MANAGE_TIME_AND_ZONE_CONFIGURATION permission.
+    private void enforceManageTimeZoneDetectorPermission() {
         mContext.enforceCallingPermission(
-                android.Manifest.permission.WRITE_SECURE_SETTINGS,
-                "manage time and time zone configuration");
+                android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION,
+                "manage time and time zone detection");
     }
 
     private void enforceSuggestGeolocationTimeZonePermission() {
@@ -333,7 +334,7 @@
     public void onShellCommand(FileDescriptor in, FileDescriptor out,
             FileDescriptor err, String[] args, ShellCallback callback,
             ResultReceiver resultReceiver) {
-        (new TimeZoneDetectorShellCommand(this)).exec(
+        new TimeZoneDetectorShellCommand(this).exec(
                 this, in, out, err, args, callback, resultReceiver);
     }
 }
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index f944c56..0b1d6d7 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -17,9 +17,9 @@
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.app.time.TimeZoneConfiguration;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.app.timezonedetector.TimeZoneConfiguration;
 import android.util.IndentingPrintWriter;
 
 /**
@@ -96,7 +96,8 @@
      * <p>This method returns {@code true} if the configuration was changed,
      * {@code false} otherwise.
      */
-    boolean updateConfiguration(@NonNull TimeZoneConfiguration configuration);
+    boolean updateConfiguration(
+            @UserIdInt int userId, @NonNull TimeZoneConfiguration configuration);
 
     /**
      * Suggests zero, one or more time zones for the device, or withdraws a previous suggestion if
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index 8a42b18..781668b 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -15,20 +15,21 @@
  */
 package com.android.server.timezonedetector;
 
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_POSSESSED;
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID;
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY;
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.time.TimeZoneCapabilities;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.app.timezonedetector.TimeZoneCapabilities;
-import android.app.timezonedetector.TimeZoneConfiguration;
 import android.content.Context;
 import android.os.Handler;
 import android.util.IndentingPrintWriter;
@@ -93,7 +94,7 @@
          * All checks about user capabilities must be done by the caller and
          * {@link TimeZoneConfiguration#isComplete()} must be {@code true}.
          */
-        void storeConfiguration(TimeZoneConfiguration newConfiguration);
+        void storeConfiguration(@UserIdInt int userId, TimeZoneConfiguration newConfiguration);
     }
 
     private static final String LOG_TAG = "TimeZoneDetectorStrategy";
@@ -240,27 +241,26 @@
     }
 
     @Override
-    public synchronized boolean updateConfiguration(
+    public synchronized boolean updateConfiguration(@UserIdInt int userId,
             @NonNull TimeZoneConfiguration requestedConfiguration) {
         Objects.requireNonNull(requestedConfiguration);
 
-        int userId = requestedConfiguration.getUserId();
-        TimeZoneCapabilities capabilities = getConfigurationInternal(userId).createCapabilities();
+        TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+                getConfigurationInternal(userId).createCapabilitiesAndConfig();
+        TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+        TimeZoneConfiguration oldConfiguration = capabilitiesAndConfig.getConfiguration();
 
-        // Create a new configuration builder, and copy across the mutable properties users are
-        // able to modify. Other properties are therefore ignored.
         final TimeZoneConfiguration newConfiguration =
-                capabilities.applyUpdate(requestedConfiguration);
+                capabilities.tryApplyConfigChanges(oldConfiguration, requestedConfiguration);
         if (newConfiguration == null) {
-            // The changes could not be made due to
+            // The changes could not be made because the user's capabilities do not allow it.
             return false;
         }
 
         // Store the configuration / notify as needed. This will cause the mCallback to invoke
         // handleConfigChanged() asynchronously.
-        mCallback.storeConfiguration(newConfiguration);
+        mCallback.storeConfiguration(userId, newConfiguration);
 
-        TimeZoneConfiguration oldConfiguration = capabilities.getConfiguration();
         String logMsg = "Configuration changed:"
                 + " oldConfiguration=" + oldConfiguration
                 + ", newConfiguration=" + newConfiguration;
@@ -313,8 +313,10 @@
         String timeZoneId = suggestion.getZoneId();
         String cause = "Manual time suggestion received: suggestion=" + suggestion;
 
-        TimeZoneCapabilities capabilities = getConfigurationInternal(userId).createCapabilities();
-        if (capabilities.getSuggestManualTimeZone() != CAPABILITY_POSSESSED) {
+        TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+                getConfigurationInternal(userId).createCapabilitiesAndConfig();
+        TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+        if (capabilities.getSuggestManualTimeZoneCapability() != CAPABILITY_POSSESSED) {
             Slog.i(LOG_TAG, "User does not have the capability needed to set the time zone manually"
                     + ", capabilities=" + capabilities
                     + ", timeZoneId=" + timeZoneId
@@ -605,7 +607,7 @@
         ipw.println("mCallback.getCurrentUserId()=" + currentUserId);
         ConfigurationInternal configuration = mCallback.getConfigurationInternal(currentUserId);
         ipw.println("mCallback.getConfiguration(currentUserId)=" + configuration);
-        ipw.println("[Capabilities=" + configuration.createCapabilities() + "]");
+        ipw.println("[Capabilities=" + configuration.createCapabilitiesAndConfig() + "]");
         ipw.println("mCallback.isDeviceTimeZoneInitialized()="
                 + mCallback.isDeviceTimeZoneInitialized());
         ipw.println("mCallback.getDeviceTimeZone()=" + mCallback.getDeviceTimeZone());
@@ -644,7 +646,7 @@
      * A method used to inspect strategy state during tests. Not intended for general use.
      */
     @VisibleForTesting
-    public GeolocationTimeZoneSuggestion getLatestGeolocationSuggestion() {
+    public synchronized GeolocationTimeZoneSuggestion getLatestGeolocationSuggestion() {
         return mLatestGeoLocationSuggestion.get();
     }
 
@@ -652,7 +654,7 @@
      * A {@link TelephonyTimeZoneSuggestion} with additional qualifying metadata.
      */
     @VisibleForTesting
-    public static class QualifiedTelephonyTimeZoneSuggestion {
+    public static final class QualifiedTelephonyTimeZoneSuggestion {
 
         @VisibleForTesting
         public final TelephonyTimeZoneSuggestion suggestion;
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index 39f6c59..f6acc64 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -115,7 +115,7 @@
     private static final String TAG = "UriGrantsManagerService";
     // Maximum number of persisted Uri grants a package is allowed
     private static final int MAX_PERSISTED_URI_GRANTS = 512;
-    private static final boolean ENABLE_DYNAMIC_PERMISSIONS = false;
+    private static final boolean ENABLE_DYNAMIC_PERMISSIONS = true;
 
     private final Object mLock = new Object();
     private final H mH;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index f29ad24..2355ed3 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1308,6 +1308,8 @@
             return;
         }
 
+        // TODO(b/169035022): move to a more-appropriate place.
+        mAtmService.getTransitionController().collect(this);
         if (prevDc.mOpeningApps.remove(this)) {
             // Transfer opening transition to new display.
             mDisplayContent.mOpeningApps.add(this);
@@ -3234,6 +3236,8 @@
         mStackSupervisor.getActivityMetricsLogger().notifyActivityRemoved(this);
         waitingToShow = false;
 
+        // TODO(b/169035022): move to a more-appropriate place.
+        mAtmService.getTransitionController().collect(this);
         // Defer removal of this activity when either a child is animating, or app transition is on
         // going. App transition animation might be applied on the parent stack not on the activity,
         // but the actual frame buffer is associated with the activity, so we have to keep the
@@ -3245,6 +3249,8 @@
         } else if (getDisplayContent().mAppTransition.isTransitionSet()) {
             getDisplayContent().mClosingApps.add(this);
             delayed = true;
+        } else if (mAtmService.getTransitionController().inTransition()) {
+            delayed = true;
         }
 
         ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
@@ -4076,6 +4082,11 @@
         return mVisible;
     }
 
+    @Override
+    boolean isVisibleRequested() {
+        return mVisibleRequested;
+    }
+
     void setVisible(boolean visible) {
         if (visible != mVisible) {
             mVisible = visible;
@@ -4206,6 +4217,11 @@
             transferStartingWindowFromHiddenAboveTokenIfNeeded();
         }
 
+        // TODO(b/169035022): move to a more-appropriate place.
+        mAtmService.getTransitionController().collect(this);
+        if (!visible && mAtmService.getTransitionController().inTransition()) {
+            return;
+        }
         // If we are preparing an app transition, then delay changing
         // the visibility of this token until we execute that transition.
         // Note that we ignore display frozen since we want the opening / closing transition type
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 783f8e8..3ef383b 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -1830,7 +1830,8 @@
         for (int i = mStoppingActivities.size() - 1; i >= 0; --i) {
             final ActivityRecord s = mStoppingActivities.get(i);
             final boolean animating = s.isAnimating(TRANSITION | PARENTS,
-                    ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS);
+                    ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)
+                    || mService.getTransitionController().inTransition(s);
             if (DEBUG_STATES) Slog.v(TAG, "Stopping " + s + ": nowVisible=" + s.nowVisible
                     + " animating=" + animating + " finishing=" + s.finishing);
             if (!animating || mService.mShuttingDown) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 0402140..c0e31f9 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -958,6 +958,10 @@
         return mLockTaskController;
     }
 
+    TransitionController getTransitionController() {
+        return mWindowOrganizerController.getTransitionController();
+    }
+
     /**
      * Return the global configuration used by the process corresponding to the input pid. This is
      * usually the global configuration with some overrides specific to that process.
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index f76108f..0e47ea8 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -2214,6 +2214,10 @@
      */
     boolean prepareAppTransitionLocked(@TransitionType int transit, boolean alwaysKeepCurrent,
             @TransitionFlags int flags, boolean forceOverride) {
+        if (mService.mAtmService.getTransitionController().adaptLegacyPrepare(
+                transit, flags, forceOverride)) {
+            return false;
+        }
         ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
                 "Prepare app transition: transit=%s %s alwaysKeepCurrent=%b displayId=%d "
                         + "Callers=%s",
@@ -2255,7 +2259,7 @@
                 || transit == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
     }
 
-    private static boolean isKeyguardTransit(int transit) {
+    static boolean isKeyguardTransit(int transit) {
         return isKeyguardGoingAwayTransit(transit) || transit == TRANSIT_KEYGUARD_OCCLUDE
                 || transit == TRANSIT_KEYGUARD_UNOCCLUDE;
     }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 1153c46..22b446d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1465,6 +1465,15 @@
                 // intermediate orientation change, it is more stable to freeze the display.
                 return false;
             }
+            if (r.isState(RESUMED) && !r.getRootTask().mInResumeTopActivity) {
+                // If the activity is executing or has done the lifecycle callback, use normal
+                // rotation animation so the display info can be updated immediately (see
+                // updateDisplayAndOrientation). This prevents a compatibility issue such as
+                // calling setRequestedOrientation in Activity#onCreate and then get display info.
+                // If fixed rotation is applied, the display rotation will still be the old one,
+                // unless the client side gets the rotation again after the adjustments arrive.
+                return false;
+            }
         } else if (r != topRunningActivity()) {
             // If the transition has not started yet, the activity must be the top.
             return false;
@@ -2273,6 +2282,11 @@
     }
 
     @Override
+    boolean isVisibleRequested() {
+        return isVisible();
+    }
+
+    @Override
     void onAppTransitionDone() {
         super.onAppTransitionDone();
         mWmService.mWindowsChanged = true;
@@ -4432,6 +4446,7 @@
     }
 
     void executeAppTransition() {
+        mAtmService.getTransitionController().setReady();
         if (mAppTransition.isTransitionSet()) {
             ProtoLog.w(WM_DEBUG_APP_TRANSITIONS,
                     "Execute app transition: %s, displayId: %d Callers=%s",
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index a236699..22776e0 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2372,6 +2372,7 @@
     private void initializeChangeTransition(Rect startBounds) {
         mDisplayContent.prepareAppTransition(TRANSIT_TASK_CHANGE_WINDOWING_MODE,
                 false /* alwaysKeepCurrent */, 0, false /* forceOverride */);
+        mAtmService.getTransitionController().collect(this);
         mDisplayContent.mChangingContainers.add(this);
 
         mSurfaceFreezer.freeze(getPendingTransaction(), startBounds);
@@ -4762,6 +4763,16 @@
     }
 
     @Override
+    boolean showSurfaceOnCreation() {
+        // Organized tasks handle their own surface visibility
+        final boolean willBeOrganized =
+                mAtmService.mTaskOrganizerController.isSupportedWindowingMode(getWindowingMode())
+                && isRootTask();
+        return !mAtmService.getTransitionController().isShellTransitionsEnabled()
+                || !willBeOrganized;
+    }
+
+    @Override
     protected void reparentSurfaceControl(SurfaceControl.Transaction t, SurfaceControl newParent) {
         /**
          * Avoid reparenting SurfaceControl of the organized tasks that are always on top, since
@@ -4781,7 +4792,9 @@
             // hide it to allow the task organizer to show it when it is properly reparented. We
             // skip this for tasks created by the organizer because they can synchronously update
             // the leash before new children are added to the task.
-            if (!mCreatedByOrganizer && mTaskOrganizer != null && !prevHasBeenVisible) {
+            if (!mAtmService.getTransitionController().isShellTransitionsEnabled()
+                    && !mCreatedByOrganizer
+                    && mTaskOrganizer != null && !prevHasBeenVisible) {
                 getSyncTransaction().hide(getSurfaceControl());
                 commitPendingTransaction();
             }
@@ -6362,7 +6375,15 @@
                         transit = TRANSIT_TASK_OPEN;
                     }
                 }
-                dc.prepareAppTransition(transit, keepCurTransition);
+                if (mAtmService.getTransitionController().isShellTransitionsEnabled()
+                        // TODO(shell-transitions): eventually all transitions.
+                        && transit == TRANSIT_TASK_OPEN) {
+                    Transition transition =
+                            mAtmService.getTransitionController().requestTransition(transit);
+                    transition.collect(task);
+                } else {
+                    dc.prepareAppTransition(transit, keepCurTransition);
+                }
                 mStackSupervisor.mNoAnimActivities.remove(r);
             }
             boolean doShow = true;
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index e07c567..8201d10 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -354,7 +354,7 @@
                 : null;
     }
 
-    private boolean isSupportedWindowingMode(int winMode) {
+    boolean isSupportedWindowingMode(int winMode) {
         return !ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES, winMode);
     }
 
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
new file mode 100644
index 0000000..fc67cd2
--- /dev/null
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -0,0 +1,487 @@
+/*
+ * Copyright (C) 2020 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.wm;
+
+
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
+
+import android.annotation.NonNull;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.view.Display;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.view.animation.Animation;
+import android.window.TransitionInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.ProtoLogGroup;
+import com.android.internal.protolog.common.ProtoLog;
+
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Represents a logical transition.
+ * @see TransitionController
+ */
+class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListener {
+    private static final String TAG = "Transition";
+
+    /** The transition has been created and is collecting, but hasn't formally started. */
+    private static final int STATE_COLLECTING = 0;
+
+    /**
+     * The transition has formally started. It is still collecting but will stop once all
+     * participants are ready to animate (finished drawing).
+     */
+    private static final int STATE_STARTED = 1;
+
+    /**
+     * This transition is currently playing its animation and can no longer collect or be changed.
+     */
+    private static final int STATE_PLAYING = 2;
+
+    final @WindowManager.TransitionType int mType;
+    private int mSyncId;
+    private @WindowManager.TransitionFlags int mFlags;
+    private final TransitionController mController;
+    final ArrayMap<WindowContainer, ChangeInfo> mParticipants = new ArrayMap<>();
+    private int mState = STATE_COLLECTING;
+    private boolean mReadyCalled = false;
+
+    Transition(@WindowManager.TransitionType int type,
+            @WindowManager.TransitionFlags int flags, TransitionController controller) {
+        mType = type;
+        mFlags = flags;
+        mController = controller;
+        mSyncId = mController.mSyncEngine.startSyncSet(this);
+    }
+
+    /**
+     * Formally starts the transition. Participants can be collected before this is started,
+     * but this won't consider itself ready until started -- even if all the participants have
+     * drawn.
+     */
+    void start() {
+        if (mState >= STATE_STARTED) {
+            Slog.w(TAG, "Transition already started: " + mSyncId);
+        }
+        mState = STATE_STARTED;
+        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Starting Transition %d",
+                mSyncId);
+        if (mReadyCalled) {
+            setReady();
+        }
+    }
+
+    /** Adds wc to set of WindowContainers participating in this transition. */
+    void collect(@NonNull WindowContainer wc) {
+        if (mSyncId < 0) return;
+        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s",
+                mSyncId, wc);
+        // Add to sync set before checking contains because it may not have added it at other
+        // times (eg. if wc was previously invisible).
+        mController.mSyncEngine.addToSyncSet(mSyncId, wc);
+        if (mParticipants.containsKey(wc)) return;
+        mParticipants.put(wc, new ChangeInfo());
+    }
+
+    /**
+     * Call this when all known changes related to this transition have been applied. Until
+     * all participants have finished drawing, the transition can still collect participants.
+     *
+     * If this is called before the transition is started, it will be deferred until start.
+     */
+    void setReady() {
+        if (mSyncId < 0) return;
+        if (mState < STATE_STARTED) {
+            mReadyCalled = true;
+            return;
+        }
+        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                "Finish collecting in transition %d", mSyncId);
+        mController.mSyncEngine.setReady(mSyncId);
+        mController.mAtm.mWindowManager.mWindowPlacerLocked.requestTraversal();
+    }
+
+    /** The transition has finished animating and is ready to finalize WM state */
+    void finishTransition() {
+        if (mState < STATE_PLAYING) {
+            throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId);
+        }
+        for (int i = 0; i < mParticipants.size(); ++i) {
+            final ActivityRecord ar = mParticipants.keyAt(i).asActivityRecord();
+            if (ar == null || ar.mVisibleRequested) {
+                continue;
+            }
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                    "  Commit activity becoming invisible: %s", ar);
+            ar.commitVisibility(false /* visible */, false /* performLayout */);
+        }
+    }
+
+    @Override
+    public void onTransactionReady(int syncId, Set<WindowContainer> windowContainersReady) {
+        if (syncId != mSyncId) {
+            Slog.e(TAG, "Unexpected Sync ID " + syncId + ". Expected " + mSyncId);
+            return;
+        }
+        mState = STATE_PLAYING;
+        mController.moveToPlaying(this);
+        final TransitionInfo info = calculateTransitionInfo(mType, mParticipants);
+
+        SurfaceControl.Transaction mergedTransaction = new SurfaceControl.Transaction();
+        int displayId = Display.DEFAULT_DISPLAY;
+        for (WindowContainer container : windowContainersReady) {
+            container.mergeBlastSyncTransaction(mergedTransaction);
+            displayId = container.mDisplayContent.getDisplayId();
+        }
+
+        handleNonAppWindowsInTransition(displayId, mType, mFlags);
+
+        if (mController.getTransitionPlayer() != null) {
+            try {
+                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                        "Calling onTransitionReady: %s", info);
+                mController.getTransitionPlayer().onTransitionReady(this, info, mergedTransaction);
+            } catch (RemoteException e) {
+                // If there's an exception when trying to send the mergedTransaction to the
+                // client, we should immediately apply it here so the transactions aren't lost.
+                mergedTransaction.apply();
+            }
+        } else {
+            mergedTransaction.apply();
+        }
+        mSyncId = -1;
+    }
+
+    private void handleNonAppWindowsInTransition(int displayId, int transit, int flags) {
+        final DisplayContent dc =
+                mController.mAtm.mRootWindowContainer.getDisplayContent(displayId);
+        if (dc == null) {
+            return;
+        }
+        if (transit == TRANSIT_KEYGUARD_GOING_AWAY) {
+            if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0
+                    && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0
+                    && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) == 0) {
+                Animation anim = mController.mAtm.mWindowManager.mPolicy
+                        .createKeyguardWallpaperExit(
+                                (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0);
+                if (anim != null) {
+                    anim.scaleCurrentDuration(
+                            mController.mAtm.mWindowManager.getTransitionAnimationScaleLocked());
+                    dc.mWallpaperController.startWallpaperAnimation(anim);
+                }
+            }
+        }
+        if (transit == TRANSIT_KEYGUARD_GOING_AWAY
+                || transit == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER) {
+            dc.startKeyguardExitOnNonAppWindows(
+                    transit == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
+                    (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0,
+                    (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) != 0);
+            mController.mAtm.mWindowManager.mPolicy.startKeyguardExitAnimation(transit, 0);
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(64);
+        sb.append("TransitionRecord{");
+        sb.append(Integer.toHexString(System.identityHashCode(this)));
+        sb.append(" id=" + mSyncId);
+        sb.append(" type=" + mType);
+        sb.append(" flags=" + mFlags);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    private static boolean reportIfNotTop(WindowContainer wc) {
+        // Organized tasks need to be reported anyways because Core won't show() their surfaces
+        // and we can't rely on onTaskAppeared because it isn't in sync.
+        // TODO(shell-transitions): switch onTaskAppeared usage over to transitions OPEN.
+        return wc.isOrganized();
+    }
+
+    /** @return the depth of child within ancestor, 0 if child == ancestor, or -1 if not a child. */
+    private static int getChildDepth(WindowContainer child, WindowContainer ancestor) {
+        WindowContainer parent = child;
+        int depth = 0;
+        while (parent != null) {
+            if (parent == ancestor) {
+                return depth;
+            }
+            parent = parent.getParent();
+            ++depth;
+        }
+        return -1;
+    }
+
+    private static @TransitionInfo.TransitionMode int getModeFor(WindowContainer wc) {
+        if (wc.isVisibleRequested()) {
+            final Task t = wc.asTask();
+            if (t != null && t.getHasBeenVisible()) {
+                return TransitionInfo.TRANSIT_SHOW;
+            }
+            return TransitionInfo.TRANSIT_OPEN;
+        }
+        return TransitionInfo.TRANSIT_CLOSE;
+    }
+
+    /**
+     * Under some conditions (eg. all visible targets within a parent container are transitioning
+     * the same way) the transition can be "promoted" to the parent container. This means an
+     * animation can play just on the parent rather than all the individual children.
+     *
+     * @return {@code true} if transition in target can be promoted to its parent.
+     */
+    private static boolean canPromote(
+            WindowContainer target, ArraySet<WindowContainer> topTargets) {
+        final WindowContainer parent = target.getParent();
+        if (parent == null || !parent.canCreateRemoteAnimationTarget()) {
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "      SKIP: %s",
+                    parent == null ? "no parent" : ("parent can't be target " + parent));
+            return false;
+        }
+        @TransitionInfo.TransitionMode int mode = TransitionInfo.TRANSIT_NONE;
+        // Go through all siblings of this target to see if any of them would prevent
+        // the target from promoting.
+        siblingLoop:
+        for (int i = parent.getChildCount() - 1; i >= 0; --i) {
+            final WindowContainer sibling = parent.getChildAt(i);
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "      check sibling %s",
+                    sibling);
+            // Check if any topTargets are the sibling or within it
+            for (int j = topTargets.size() - 1; j >= 0; --j) {
+                final int depth = getChildDepth(topTargets.valueAt(j), sibling);
+                if (depth < 0) continue;
+                if (depth == 0) {
+                    final int siblingMode = sibling.isVisibleRequested()
+                            ? TransitionInfo.TRANSIT_OPEN : TransitionInfo.TRANSIT_CLOSE;
+                    ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                            "        sibling is a top target with mode %s",
+                            TransitionInfo.modeToString(siblingMode));
+                    if (mode == TransitionInfo.TRANSIT_NONE) {
+                        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                                "          no common mode yet, so set it");
+                        mode = siblingMode;
+                    } else if (mode != siblingMode) {
+                        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                                "          SKIP: common mode mismatch. was %s",
+                                TransitionInfo.modeToString(mode));
+                        return false;
+                    }
+                    continue siblingLoop;
+                } else {
+                    // Sibling subtree may not be promotable.
+                    ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                            "        SKIP: sibling contains top target %s",
+                            topTargets.valueAt(j));
+                    return false;
+                }
+            }
+            // No other animations are playing in this sibling
+            if (sibling.isVisibleRequested()) {
+                // Sibling is visible but not animating, so no promote.
+                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                        "        SKIP: sibling is visible but not part of transition");
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Go through topTargets and try to promote (see {@link #canPromote}) one of them.
+     *
+     * @param topTargets set of just the top-most targets in the hierarchy of participants.
+     * @param targets all targets that will be sent to the player.
+     * @return {@code true} if something was promoted.
+     */
+    private static boolean tryPromote(ArraySet<WindowContainer> topTargets,
+            ArrayMap<WindowContainer, ChangeInfo> targets) {
+        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "  --- Start combine pass ---");
+        // Go through each target until we find one that can be promoted.
+        targetLoop:
+        for (WindowContainer targ : topTargets) {
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "    checking %s", targ);
+            if (!canPromote(targ, topTargets)) {
+                continue;
+            }
+            final WindowContainer parent = targ.getParent();
+            // No obstructions found to promotion, so promote
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                    "      CAN PROMOTE: promoting to parent %s", parent);
+            final ChangeInfo parentInfo = new ChangeInfo();
+            targets.put(parent, parentInfo);
+            // Go through all children of newly-promoted container and remove them from
+            // the top-targets.
+            for (int i = parent.getChildCount() - 1; i >= 0; --i) {
+                final WindowContainer child = parent.getChildAt(i);
+                int idx = targets.indexOfKey(child);
+                if (idx >= 0) {
+                    if (reportIfNotTop(child)) {
+                        targets.valueAt(idx).mParent = parent;
+                        parentInfo.addChild(child);
+                        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                                "        keep as target %s", child);
+                    } else {
+                        if (targets.valueAt(idx).mChildren != null) {
+                            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                                    "        merging children in from %s: %s", child,
+                                    targets.valueAt(idx).mChildren);
+                            parentInfo.addChildren(targets.valueAt(idx).mChildren);
+                        }
+                        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                                "        remove from targets %s", child);
+                        targets.removeAt(idx);
+                    }
+                }
+                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                        "        remove from topTargets %s", child);
+                topTargets.remove(child);
+            }
+            topTargets.add(parent);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Find WindowContainers to be animated from a set of opening and closing apps. We will promote
+     * animation targets to higher level in the window hierarchy if possible.
+     */
+    @VisibleForTesting
+    static TransitionInfo calculateTransitionInfo(
+            int type, Map<WindowContainer, ChangeInfo> participants) {
+        final TransitionInfo out = new TransitionInfo(type);
+
+        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                "Start calculating TransitionInfo based on participants: %s",
+                new ArraySet<>(participants.keySet()));
+
+        final ArraySet<WindowContainer> topTargets = new ArraySet<>();
+        // The final animation targets which cannot promote to higher level anymore.
+        final ArrayMap<WindowContainer, ChangeInfo> targets = new ArrayMap<>();
+
+        final ArrayList<WindowContainer> tmpList = new ArrayList<>();
+
+        // Build initial set of top-level participants by removing any participants that are
+        // children of other participants or are otherwise invalid.
+        for (Map.Entry<WindowContainer, ChangeInfo> entry : participants.entrySet()) {
+            final WindowContainer wc = entry.getKey();
+            // Don't include detached windows.
+            if (!wc.isAttached()) continue;
+
+            final ChangeInfo changeInfo = entry.getValue();
+            WindowContainer parent = wc.getParent();
+            WindowContainer topParent = null;
+            // Keep track of always-report parents in bottom-to-top order
+            tmpList.clear();
+            while (parent != null) {
+                if (participants.containsKey(parent)) {
+                    topParent = parent;
+                } else if (reportIfNotTop(parent)) {
+                    tmpList.add(parent);
+                }
+                parent = parent.getParent();
+            }
+            if (topParent != null) {
+                // Add always-report parents along the way
+                parent = topParent;
+                for (int i = tmpList.size() - 1; i >= 0; --i) {
+                    if (!participants.containsKey(tmpList.get(i))) {
+                        final ChangeInfo info = new ChangeInfo();
+                        info.mParent = parent;
+                        targets.put(tmpList.get(i), info);
+                    }
+                    parent = tmpList.get(i);
+                }
+                continue;
+            }
+            targets.put(wc, changeInfo);
+            topTargets.add(wc);
+        }
+
+        // Populate children lists
+        for (int i = targets.size() - 1; i >= 0; --i) {
+            if (targets.valueAt(i).mParent != null) {
+                targets.get(targets.valueAt(i).mParent).addChild(targets.keyAt(i));
+            }
+        }
+
+        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                "  Initial targets: %s", new ArraySet<>(targets.keySet()));
+        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "  Top targets: %s", topTargets);
+
+        // Combine targets by repeatedly going through the topTargets to see if they can be
+        // promoted until there aren't any promotions possible.
+        while (tryPromote(topTargets, targets)) {
+            // Empty on purpose
+        }
+
+        // Convert all the resolved ChangeInfos into a TransactionInfo object.
+        for (int i = targets.size() - 1; i >= 0; --i) {
+            final WindowContainer target = targets.keyAt(i);
+            final ChangeInfo info = targets.valueAt(i);
+            final TransitionInfo.Change change = new TransitionInfo.Change(
+                    target.mRemoteToken.toWindowContainerToken(), target.getSurfaceControl());
+            if (info.mParent != null) {
+                change.setParent(info.mParent.mRemoteToken.toWindowContainerToken());
+            }
+            change.setMode(getModeFor(target));
+            out.addChange(change);
+        }
+
+        return out;
+    }
+
+    static Transition fromBinder(IBinder binder) {
+        return (Transition) binder;
+    }
+
+    @VisibleForTesting
+    static class ChangeInfo {
+        WindowContainer mParent;
+        ArraySet<WindowContainer> mChildren;
+        // TODO(shell-transitions): other tracking like before state and bounds
+        void addChild(@NonNull WindowContainer wc) {
+            if (mChildren == null) {
+                mChildren = new ArraySet<>();
+            }
+            mChildren.add(wc);
+        }
+        void addChildren(@NonNull ArraySet<WindowContainer> wcs) {
+            if (mChildren == null) {
+                mChildren = new ArraySet<>();
+            }
+            mChildren.addAll(wcs);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
new file mode 100644
index 0000000..d102c19
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2020 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.wm;
+
+import static android.view.WindowManager.TRANSIT_CRASHING_ACTIVITY_CLOSE;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_TASK_CLOSE;
+import static android.view.WindowManager.TRANSIT_TASK_OPEN;
+import static android.view.WindowManager.TRANSIT_TASK_OPEN_BEHIND;
+import static android.view.WindowManager.TRANSIT_TASK_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.view.WindowManager;
+import android.window.ITransitionPlayer;
+
+import com.android.internal.protolog.ProtoLogGroup;
+import com.android.internal.protolog.common.ProtoLog;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Handles all the aspects of recording and synchronizing transitions.
+ */
+class TransitionController {
+    private static final String TAG = "TransitionController";
+
+    private static final int[] SUPPORTED_LEGACY_TRANSIT_TYPES = {TRANSIT_TASK_OPEN,
+            TRANSIT_TASK_CLOSE, TRANSIT_TASK_TO_FRONT, TRANSIT_TASK_TO_BACK,
+            TRANSIT_TASK_OPEN_BEHIND, TRANSIT_KEYGUARD_GOING_AWAY};
+    static {
+        Arrays.sort(SUPPORTED_LEGACY_TRANSIT_TYPES);
+    }
+
+    final BLASTSyncEngine mSyncEngine = new BLASTSyncEngine();
+    private ITransitionPlayer mTransitionPlayer;
+    private final IBinder.DeathRecipient mTransitionPlayerDeath = () -> mTransitionPlayer = null;
+    final ActivityTaskManagerService mAtm;
+
+    /** Currently playing transitions. When finished, records are removed from this list. */
+    private final ArrayList<Transition> mPlayingTransitions = new ArrayList<>();
+
+    /**
+     * The transition currently being constructed (collecting participants).
+     * TODO(shell-transitions): When simultaneous transitions are supported, merge this with
+     *                          mPlayingTransitions.
+     */
+    private Transition mCollectingTransition = null;
+
+    TransitionController(ActivityTaskManagerService atm) {
+        mAtm = atm;
+    }
+
+    /** @see #createTransition(int, int) */
+    @NonNull
+    Transition createTransition(int type) {
+        return createTransition(type, 0 /* flags */);
+    }
+
+    /**
+     * Creates a transition. It can immediately collect participants.
+     */
+    @NonNull
+    Transition createTransition(@WindowManager.TransitionType int type,
+            @WindowManager.TransitionFlags int flags) {
+        if (mCollectingTransition != null) {
+            throw new IllegalStateException("Simultaneous transitions not supported yet.");
+        }
+        mCollectingTransition = new Transition(type, flags, this);
+        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Transition: %s",
+                mCollectingTransition);
+        return mCollectingTransition;
+    }
+
+    void registerTransitionPlayer(@Nullable ITransitionPlayer player) {
+        try {
+            if (mTransitionPlayer != null) {
+                mTransitionPlayer.asBinder().unlinkToDeath(mTransitionPlayerDeath, 0);
+                mTransitionPlayer = null;
+            }
+            player.asBinder().linkToDeath(mTransitionPlayerDeath, 0);
+            mTransitionPlayer = player;
+        } catch (RemoteException e) {
+            throw new RuntimeException("Unable to set transition player");
+        }
+    }
+
+    @Nullable ITransitionPlayer getTransitionPlayer() {
+        return mTransitionPlayer;
+    }
+
+    boolean isShellTransitionsEnabled() {
+        return mTransitionPlayer != null;
+    }
+
+    /** @return {@code true} if a transition is running */
+    boolean inTransition() {
+        // TODO(shell-transitions): eventually properly support multiple
+        return mCollectingTransition != null || !mPlayingTransitions.isEmpty();
+    }
+
+    /** @return {@code true} if wc is in a participant subtree */
+    boolean inTransition(@NonNull WindowContainer wc) {
+        if (mCollectingTransition != null && mCollectingTransition.mParticipants.containsKey(wc)) {
+            return true;
+        }
+        for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
+            for (WindowContainer p = wc; p != null; p = p.getParent()) {
+                if (mPlayingTransitions.get(i).mParticipants.containsKey(p)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Creates a transition and asks the TransitionPlayer (Shell) to start it.
+     * @return the created transition. Collection can start immediately.
+     */
+    @NonNull
+    Transition requestTransition(@WindowManager.TransitionType int type) {
+        return requestTransition(type, 0 /* flags */);
+    }
+
+    /** @see #requestTransition */
+    @NonNull
+    Transition requestTransition(@WindowManager.TransitionType int type,
+            @WindowManager.TransitionFlags int flags) {
+        if (mTransitionPlayer == null) {
+            throw new IllegalStateException("Shell Transitions not enabled");
+        }
+        final Transition transition = createTransition(type, flags);
+        try {
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                    "Requesting StartTransition: %s", transition);
+            mTransitionPlayer.requestStartTransition(type, transition);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Error requesting transition", e);
+            transition.start();
+        }
+        return transition;
+    }
+
+    /**
+     * Temporary adapter that converts the legacy AppTransition's prepareAppTransition call into
+     * a Shell transition request. If shell transitions are enabled, this will take priority in
+     * handling transition types that it supports. All other transitions will be ignored and thus
+     * be handled by the legacy apptransition system. This allows both worlds to live in tandem
+     * during migration.
+     *
+     * @return {@code true} if the transition is handled.
+     */
+    boolean adaptLegacyPrepare(@WindowManager.TransitionType int transit,
+            @WindowManager.TransitionFlags int flags, boolean forceOverride) {
+        if (!isShellTransitionsEnabled()
+                || Arrays.binarySearch(SUPPORTED_LEGACY_TRANSIT_TYPES, transit) < 0) {
+            return false;
+        }
+        if (inTransition()) {
+            if (AppTransition.isKeyguardTransit(transit)) {
+                // TODO(shell-transitions): add to flags
+            } else if (forceOverride) {
+                // TODO(shell-transitions): sort out these flags
+            } else if (transit == TRANSIT_CRASHING_ACTIVITY_CLOSE) {
+                // TODO(shell-transitions): record crashing
+            }
+        } else {
+            requestTransition(transit, flags);
+        }
+        return true;
+    }
+
+    /** @see Transition#collect */
+    void collect(@NonNull WindowContainer wc) {
+        if (mCollectingTransition == null) return;
+        mCollectingTransition.collect(wc);
+    }
+
+    /** @see Transition#setReady */
+    void setReady() {
+        if (mCollectingTransition == null) return;
+        mCollectingTransition.setReady();
+    }
+
+    /** @see Transition#finishTransition */
+    void finishTransition(@NonNull IBinder token) {
+        final Transition record = Transition.fromBinder(token);
+        if (record == null || !mPlayingTransitions.contains(record)) {
+            Slog.e(TAG, "Trying to finish a non-playing transition " + token);
+            return;
+        }
+        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Finish Transition: %s", record);
+        mPlayingTransitions.remove(record);
+        record.finishTransition();
+    }
+
+    void moveToPlaying(Transition transition) {
+        if (transition != mCollectingTransition) {
+            throw new IllegalStateException("Trying to move non-collecting transition to playing");
+        }
+        mCollectingTransition = null;
+        mPlayingTransitions.add(transition);
+    }
+
+}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 2b93080..2ece30d 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -417,7 +417,9 @@
 
     void setInitialSurfaceControlProperties(SurfaceControl.Builder b) {
         setSurfaceControl(b.setCallsite("WindowContainer.setInitialSurfaceControlProperties").build());
-        getSyncTransaction().show(mSurfaceControl);
+        if (showSurfaceOnCreation()) {
+            getSyncTransaction().show(mSurfaceControl);
+        }
         onSurfaceShown(getSyncTransaction());
         updateSurfacePositionNonOrganized();
     }
@@ -999,6 +1001,21 @@
     }
 
     /**
+     * Is this window's surface needed?  This is almost like isVisible, except when participating
+     * in a transition, this will reflect the final visibility while isVisible won't change until
+     * the transition is finished.
+     */
+    boolean isVisibleRequested() {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowContainer child = mChildren.get(i);
+            if (child.isVisibleRequested()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * Called when the visibility of a child is asked to change. This is before visibility actually
      * changes (eg. a transition animation might play out first).
      */
@@ -2816,6 +2833,13 @@
         return false;
     }
 
+    /**
+     * @return {@code true} if this container's surface should be shown when it is created.
+     */
+    boolean showSurfaceOnCreation() {
+        return true;
+    }
+
     static WindowContainer fromBinder(IBinder binder) {
         return RemoteToken.fromBinder(binder).getContainer();
     }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d434bf9..19cfcb2 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -665,6 +665,8 @@
 
     // Whether the system should use BLAST for ViewRootImpl
     final boolean mUseBLAST;
+    // Whether to enable BLASTSyncEngine Transaction passing.
+    final boolean mUseBLASTSync = false;
 
     int mDockedStackCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
     Rect mDockedStackCreateBounds;
@@ -2129,7 +2131,7 @@
                 win.finishSeamlessRotation(false /* timeout */);
             }
 
-            if (win.useBLASTSync()) {
+            if (mUseBLASTSync && win.useBLASTSync()) {
                 result |= RELAYOUT_RES_BLAST_SYNC;
             }
 
@@ -2460,7 +2462,10 @@
         if (win.mAttrs.type == TYPE_APPLICATION_STARTING) {
             transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
         }
-        if (win.isWinVisibleLw() && winAnimator.applyAnimationLocked(transit, false)) {
+        if (mAtmService.getTransitionController().inTransition(win)) {
+            focusMayChange = true;
+            win.mAnimatingExit = true;
+        } else if (win.isWinVisibleLw() && winAnimator.applyAnimationLocked(transit, false)) {
             focusMayChange = true;
             win.mAnimatingExit = true;
         } else if (win.isAnimating(TRANSITION | PARENTS)) {
@@ -5127,6 +5132,10 @@
         return mUseBLAST;
     }
 
+    public boolean useBLASTSync() {
+        return mUseBLASTSync;
+    }
+
     @Override
     public void getInitialDisplaySize(int displayId, Point size) {
         synchronized (mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 999181d..f1641cd 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -26,6 +26,8 @@
 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.WindowConfiguration;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
@@ -40,6 +42,7 @@
 import android.view.SurfaceControl;
 import android.window.IDisplayAreaOrganizerController;
 import android.window.ITaskOrganizerController;
+import android.window.ITransitionPlayer;
 import android.window.IWindowContainerTransactionCallback;
 import android.window.IWindowOrganizerController;
 import android.window.WindowContainerToken;
@@ -88,21 +91,85 @@
     final TaskOrganizerController mTaskOrganizerController;
     final DisplayAreaOrganizerController mDisplayAreaOrganizerController;
 
+    final TransitionController mTransitionController;
+
     WindowOrganizerController(ActivityTaskManagerService atm) {
         mService = atm;
         mGlobalLock = atm.mGlobalLock;
         mTaskOrganizerController = new TaskOrganizerController(mService);
         mDisplayAreaOrganizerController = new DisplayAreaOrganizerController(mService);
+        mTransitionController = new TransitionController(atm);
+    }
+
+    TransitionController getTransitionController() {
+        return mTransitionController;
     }
 
     @Override
     public void applyTransaction(WindowContainerTransaction t) {
-        applySyncTransaction(t, null /*callback*/);
+        applyTransaction(t, null /*callback*/, null /*transition*/);
     }
 
     @Override
     public int applySyncTransaction(WindowContainerTransaction t,
             IWindowContainerTransactionCallback callback) {
+        return applyTransaction(t, callback, null /*transition*/);
+    }
+
+    @Override
+    public IBinder startTransition(int type, @Nullable IBinder transitionToken,
+            @Nullable WindowContainerTransaction t) {
+        enforceStackPermission("startTransition()");
+        long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                Transition transition = Transition.fromBinder(transitionToken);
+                if (transition == null) {
+                    if (type < 0) {
+                        throw new IllegalArgumentException("Can't create transition with no type");
+                    }
+                    transition = mTransitionController.createTransition(type);
+                }
+                transition.start();
+                if (t == null) {
+                    t = new WindowContainerTransaction();
+                }
+                applyTransaction(t, null /*callback*/, transition);
+                return transition;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
+    public int finishTransition(@NonNull IBinder transitionToken,
+            @Nullable WindowContainerTransaction t,
+            @Nullable IWindowContainerTransactionCallback callback) {
+        enforceStackPermission("finishTransition()");
+        long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                int syncId = -1;
+                if (t != null) {
+                    syncId = applyTransaction(t, callback, null /*transition*/);
+                }
+                getTransitionController().finishTransition(transitionToken);
+                return syncId;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    /**
+     * @param callback If non-null, this will be a sync-transaction.
+     * @param transition A transition to collect changes into.
+     * @return a BLAST sync-id if this is a non-transition, sync transaction.
+     */
+    private int applyTransaction(@NonNull WindowContainerTransaction t,
+            @Nullable IWindowContainerTransactionCallback callback,
+            @Nullable Transition transition) {
         enforceStackPermission("applySyncTransaction()");
         int syncId = -1;
         if (t == null) {
@@ -152,6 +219,7 @@
                         }
 
                         int containerEffect = applyWindowContainerChange(wc, entry.getValue());
+                        if (transition != null) transition.collect(wc);
                         effects |= containerEffect;
 
                         // Lifecycle changes will trigger ensureConfig for everything.
@@ -173,6 +241,12 @@
                             addToSyncSet(syncId, wc);
                         }
                         effects |= sanitizeAndApplyHierarchyOp(wc, hop);
+                        if (transition != null) {
+                            transition.collect(wc);
+                            if (hop.isReparent() && hop.getNewParent() != null) {
+                                transition.collect(WindowContainer.fromBinder(hop.getNewParent()));
+                            }
+                        }
                     }
                     // Queue-up bounds-change transactions for tasks which are now organized. Do
                     // this after hierarchy ops so we have the final organized state.
@@ -512,6 +586,19 @@
         return true;
     }
 
+    @Override
+    public void registerTransitionPlayer(ITransitionPlayer player) {
+        enforceStackPermission("registerTransitionPlayer()");
+        long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                mTransitionController.registerTransitionPlayer(player);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
     private void enforceStackPermission(String func) {
         mService.mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, func);
     }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ad28177..8f42b3f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1683,6 +1683,11 @@
                         || mControllableInsetProvider.isClientVisible());
     }
 
+    @Override
+    boolean isVisibleRequested() {
+        return isVisible();
+    }
+
     /**
      * Ensures that all the policy visibility bits are set.
      * @return {@code true} if all flags about visiblity are set
@@ -1771,7 +1776,7 @@
         }
         final ActivityRecord atoken = mActivityRecord;
         if (atoken != null) {
-            return ((!isParentWindowHidden() && atoken.mVisibleRequested)
+            return ((!isParentWindowHidden() && atoken.isVisible())
                     || isAnimating(TRANSITION | PARENTS));
         }
         return !isParentWindowHidden() || isAnimating(TRANSITION | PARENTS);
@@ -5803,7 +5808,9 @@
         }
 
         mNotifyBlastOnSurfacePlacement = true;
-        return mWinAnimator.finishDrawingLocked(null);
+        mWinAnimator.finishDrawingLocked(null);
+        // We always want to force a traversal after a finish draw for blast sync.
+        return true;
     }
 
     private void notifyBlastSyncTransaction() {
@@ -5814,6 +5821,15 @@
             return;
         }
 
+        final Task task = getTask();
+        if (task != null) {
+            final SurfaceControl.Transaction t = task.getMainWindowSizeChangeTransaction();
+            if (t != null) {
+                mBLASTSyncTransaction.merge(t);
+            }
+            task.setMainWindowSizeChangeTransaction(null);
+        }
+
         // If localSyncId is >0 then we are syncing with children and will
         // invoke transaction ready from our own #transactionReady callback
         // we just need to signal our side of the sync (setReady). But if we
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 2ace23f..9f653d6 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -636,6 +636,11 @@
     }
 
     private boolean shouldConsumeMainWindowSizeTransaction() {
+      // If we use BLASTSync we always consume the transaction when finishing
+      // the sync.
+      if (mService.useBLASTSync()) {
+          return false;
+      }
       // We only consume the transaction when the client is calling relayout
       // because this is the only time we know the frameNumber will be valid
       // due to the client renderer being paused. Put otherwise, only when
diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml
index a398961..182fe9a 100644
--- a/services/tests/mockingservicestests/AndroidManifest.xml
+++ b/services/tests/mockingservicestests/AndroidManifest.xml
@@ -17,7 +17,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.frameworks.mockingservicestests">
 
-    <uses-sdk android:targetSdkVersion="30" />
+    <uses-sdk android:targetSdkVersion="31" />
 
     <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/>
     <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java
index b4e8825..be258dc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java
@@ -16,8 +16,6 @@
 
 package com.android.server.location;
 
-import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
-import static android.app.AlarmManager.WINDOW_EXACT;
 import static android.app.AppOpsManager.OP_FINE_LOCATION;
 import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
 import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
@@ -41,7 +39,6 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.after;
@@ -55,8 +52,6 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.MockitoAnnotations.initMocks;
 
-import android.app.AlarmManager;
-import android.app.AlarmManager.OnAlarmListener;
 import android.content.Context;
 import android.location.ILocationCallback;
 import android.location.ILocationListener;
@@ -66,14 +61,11 @@
 import android.location.LocationRequest;
 import android.location.util.identity.CallerIdentity;
 import android.os.Bundle;
-import android.os.Handler;
 import android.os.ICancellationSignal;
 import android.os.IRemoteCallback;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.WorkSource;
 import android.platform.test.annotations.Presubmit;
 import android.util.Log;
 
@@ -127,8 +119,6 @@
     @Mock
     private Context mContext;
     @Mock
-    private AlarmManager mAlarmManager;
-    @Mock
     private PowerManager mPowerManager;
     @Mock
     private PowerManager.WakeLock mWakeLock;
@@ -151,7 +141,6 @@
         LocalServices.addService(LocationManagerInternal.class, mInternal);
 
         doReturn("android").when(mContext).getPackageName();
-        doReturn(mAlarmManager).when(mContext).getSystemService(AlarmManager.class);
         doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
         doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString());
 
@@ -505,19 +494,8 @@
         ILocationListener listener = createMockLocationListener();
         LocationRequest request = new LocationRequest.Builder(0).setDurationMillis(5000).build();
         mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
-        long baseTimeMs = SystemClock.elapsedRealtime();
 
-        ArgumentCaptor<Long> timeoutCapture = ArgumentCaptor.forClass(Long.class);
-        ArgumentCaptor<OnAlarmListener> listenerCapture = ArgumentCaptor.forClass(
-                OnAlarmListener.class);
-        verify(mAlarmManager).set(eq(ELAPSED_REALTIME_WAKEUP), timeoutCapture.capture(),
-                eq(WINDOW_EXACT), eq(0L), listenerCapture.capture(), any(Handler.class),
-                any(WorkSource.class));
-
-        assertThat(timeoutCapture.getValue()).isAtLeast(baseTimeMs + 4000);
-        assertThat(timeoutCapture.getValue()).isAtMost(baseTimeMs + 5000);
-        listenerCapture.getValue().onAlarm();
-
+        mInjector.getAlarmHelper().incrementAlarmTime(5000);
         mProvider.setProviderLocation(createLocation(NAME, mRandom));
         verify(listener, never()).onLocationChanged(any(Location.class),
                 nullable(IRemoteCallback.class));
@@ -684,13 +662,7 @@
         LocationRequest locationRequest = new LocationRequest.Builder(0).build();
         mManager.getCurrentLocation(locationRequest, IDENTITY, PERMISSION_FINE, listener);
 
-        ArgumentCaptor<OnAlarmListener> listenerCapture = ArgumentCaptor.forClass(
-                OnAlarmListener.class);
-        verify(mAlarmManager).set(eq(ELAPSED_REALTIME_WAKEUP), anyLong(),
-                eq(WINDOW_EXACT), eq(0L), listenerCapture.capture(), any(Handler.class),
-                any(WorkSource.class));
-        listenerCapture.getValue().onAlarm();
-
+        mInjector.getAlarmHelper().incrementAlarmTime(60000);
         verify(listener, times(1)).onLocation(isNull());
     }
 
@@ -769,6 +741,40 @@
     }
 
     @Test
+    public void testProviderRequest_DelayedRequest() throws Exception {
+        mProvider.setProviderLocation(createLocation(NAME, mRandom));
+
+        ILocationListener listener1 = createMockLocationListener();
+        LocationRequest request1 = new LocationRequest.Builder(60000).build();
+        mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
+
+        verify(listener1).onLocationChanged(any(Location.class), nullable(IRemoteCallback.class));
+
+        assertThat(mProvider.getRequest().isActive()).isFalse();
+
+        mInjector.getAlarmHelper().incrementAlarmTime(60000);
+        assertThat(mProvider.getRequest().isActive()).isTrue();
+        assertThat(mProvider.getRequest().getIntervalMillis()).isEqualTo(60000);
+    }
+
+    @Test
+    public void testProviderRequest_SpamRequesting() {
+        mProvider.setProviderLocation(createLocation(NAME, mRandom));
+
+        ILocationListener listener1 = createMockLocationListener();
+        LocationRequest request1 = new LocationRequest.Builder(60000).build();
+
+        mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
+        assertThat(mProvider.getRequest().isActive()).isFalse();
+        mManager.unregisterLocationRequest(listener1);
+        assertThat(mProvider.getRequest().isActive()).isFalse();
+        mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
+        assertThat(mProvider.getRequest().isActive()).isFalse();
+        mManager.unregisterLocationRequest(listener1);
+        assertThat(mProvider.getRequest().isActive()).isFalse();
+    }
+
+    @Test
     public void testProviderRequest_BackgroundThrottle() {
         ILocationListener listener1 = createMockLocationListener();
         LocationRequest request1 = new LocationRequest.Builder(5).build();
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeAlarmHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeAlarmHelper.java
new file mode 100644
index 0000000..0e3e6ef
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeAlarmHelper.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 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.location.util;
+
+import android.app.AlarmManager.OnAlarmListener;
+import android.os.WorkSource;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+public class FakeAlarmHelper extends AlarmHelper {
+
+    private static class Alarm {
+        public long delayMs;
+        public final OnAlarmListener listener;
+
+        Alarm(long delayMs, OnAlarmListener listener) {
+            this.delayMs = delayMs;
+            this.listener = listener;
+        }
+    }
+
+    private final ArrayList<Alarm> mAlarms = new ArrayList<>();
+
+    @Override
+    public void setDelayedAlarmInternal(long delayMs, OnAlarmListener listener,
+            WorkSource workSource) {
+        mAlarms.add(new Alarm(delayMs, listener));
+    }
+
+    @Override
+    public void cancel(OnAlarmListener listener) {
+        mAlarms.removeIf(alarm -> alarm.listener == listener);
+    }
+
+    public void incrementAlarmTime(long incrementMs) {
+        Iterator<Alarm> it = mAlarms.iterator();
+        while (it.hasNext()) {
+            Alarm alarm = it.next();
+            alarm.delayMs -= incrementMs;
+            if (alarm.delayMs <= 0) {
+                it.remove();
+                alarm.listener.onAlarm();
+            }
+        }
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java b/services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java
index 1867be0..69f7376 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java
@@ -21,6 +21,7 @@
 public class TestInjector implements Injector {
 
     private final FakeUserInfoHelper mUserInfoHelper;
+    private final FakeAlarmHelper mAlarmHelper;
     private final FakeAppOpsHelper mAppOpsHelper;
     private final FakeLocationPermissionsHelper mLocationPermissionsHelper;
     private final FakeSettingsHelper mSettingsHelper;
@@ -33,6 +34,7 @@
 
     public TestInjector() {
         mUserInfoHelper = new FakeUserInfoHelper();
+        mAlarmHelper = new FakeAlarmHelper();
         mAppOpsHelper = new FakeAppOpsHelper();
         mLocationPermissionsHelper = new FakeLocationPermissionsHelper(mAppOpsHelper);
         mSettingsHelper = new FakeSettingsHelper();
@@ -50,6 +52,11 @@
     }
 
     @Override
+    public FakeAlarmHelper getAlarmHelper() {
+        return mAlarmHelper;
+    }
+
+    @Override
     public FakeAppOpsHelper getAppOpsHelper() {
         return mAppOpsHelper;
     }
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java b/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
index 631b8d3..9ee9259 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
@@ -15,17 +15,22 @@
  */
 package com.android.server.location.timezone;
 
+import static android.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_PERMANENT_FAILURE;
 import static android.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_SUCCESS;
 import static android.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_UNCERTAIN;
 
 import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_CERTAIN;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_INITIALIZING;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_UNCERTAIN;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
 import static com.android.server.location.timezone.TestSupport.USER1_CONFIG_GEO_DETECTION_DISABLED;
 import static com.android.server.location.timezone.TestSupport.USER1_CONFIG_GEO_DETECTION_ENABLED;
 import static com.android.server.location.timezone.TestSupport.USER1_ID;
 import static com.android.server.location.timezone.TestSupport.USER2_CONFIG_GEO_DETECTION_ENABLED;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -39,6 +44,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.util.IndentingPrintWriter;
 
+import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.ProviderStateEnum;
 import com.android.server.timezonedetector.ConfigurationInternal;
 import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
 import com.android.server.timezonedetector.TestState;
@@ -65,10 +71,12 @@
             createLocationTimeZoneEvent(USER1_ID, EVENT_TYPE_SUCCESS, asList("Europe/Paris"));
     private static final LocationTimeZoneEvent USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT =
             createLocationTimeZoneEvent(USER1_ID, EVENT_TYPE_UNCERTAIN, null);
+    private static final LocationTimeZoneEvent USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT =
+            createLocationTimeZoneEvent(USER1_ID, EVENT_TYPE_PERMANENT_FAILURE, null);
 
     private TestThreadingDomain mTestThreadingDomain;
     private TestCallback mTestCallback;
-    private TestLocationTimeZoneProvider mTestLocationTimeZoneProvider;
+    private TestLocationTimeZoneProvider mTestPrimaryLocationTimeZoneProvider;
 
     @Before
     public void setUp() {
@@ -77,276 +85,446 @@
         // will never get a chance to execute.
         mTestThreadingDomain = new TestThreadingDomain();
         mTestCallback = new TestCallback(mTestThreadingDomain);
-        mTestLocationTimeZoneProvider =
+        mTestPrimaryLocationTimeZoneProvider =
                 new TestLocationTimeZoneProvider(mTestThreadingDomain, "primary");
     }
 
     @Test
     public void initialState_enabled() {
-        ControllerImpl controllerImpl =
-                new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+        ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+                mTestPrimaryLocationTimeZoneProvider);
         TestEnvironment testEnvironment = new TestEnvironment(
                 mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+        Duration expectedInitTimeout = testEnvironment.getProviderInitializationTimeout()
+                .plus(testEnvironment.getProviderInitializationTimeoutFuzz());
+
+        // Initialize. After initialization the provider must be initialized and should be
+        // enabled.
         controllerImpl.initialize(testEnvironment, mTestCallback);
 
-        mTestLocationTimeZoneProvider.assertInitialized();
+        mTestPrimaryLocationTimeZoneProvider.assertInitialized();
 
-        mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
-        Duration expectedTimeout = expectedProviderInitializationTimeout();
-        mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+        mTestPrimaryLocationTimeZoneProvider.assertInitializationTimeoutSet(expectedInitTimeout);
         mTestCallback.assertNoSuggestionMade();
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
     }
 
     @Test
     public void initialState_disabled() {
-        ControllerImpl controllerImpl =
-                new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+        ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+                mTestPrimaryLocationTimeZoneProvider);
         TestEnvironment testEnvironment = new TestEnvironment(
                 mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_DISABLED);
+
+        // Initialize. After initialization the provider must be initialized but should not be
+        // enabled.
         controllerImpl.initialize(testEnvironment, mTestCallback);
 
-        mTestLocationTimeZoneProvider.assertInitialized();
+        mTestPrimaryLocationTimeZoneProvider.assertInitialized();
 
-        mTestLocationTimeZoneProvider.assertIsDisabled();
-        mTestThreadingDomain.assertQueueEmpty();
+        mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
         mTestCallback.assertNoSuggestionMade();
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
     }
 
     @Test
     public void enabled_uncertaintySuggestionSentIfNoEventReceived() {
-        ControllerImpl controllerImpl =
-                new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+        ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+                mTestPrimaryLocationTimeZoneProvider);
         TestEnvironment testEnvironment = new TestEnvironment(
                 mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+        // Initialize and check initial state.
         controllerImpl.initialize(testEnvironment, mTestCallback);
 
-        mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestCallback.assertNoSuggestionMade();
-        Duration expectedTimeout = expectedProviderInitializationTimeout();
-        mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
 
-        // Simulate time passing with no event being received.
+        // Simulate time passing with no provider event being received from the primary.
         mTestThreadingDomain.executeNext();
 
-        mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
+        // The primary should have reported uncertainty, which should trigger the controller to
+        // start the uncertainty timeout.
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+        mTestCallback.assertNoSuggestionMade();
+        assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+
+        // Finally, the uncertainty timeout should cause the controller to make an uncertain
+        // suggestion.
+        mTestThreadingDomain.executeNext();
+
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestCallback.assertUncertainSuggestionMadeAndCommit();
-        mTestThreadingDomain.assertQueueEmpty();
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
     }
 
     @Test
-    public void enabled_uncertaintySuggestionCancelledIfEventReceived() {
-        ControllerImpl controllerImpl =
-                new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+    public void enabled_eventReceivedBeforeInitializationTimeout() {
+        ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+                mTestPrimaryLocationTimeZoneProvider);
         TestEnvironment testEnvironment = new TestEnvironment(
                 mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+        // Initialize and check initial state.
         controllerImpl.initialize(testEnvironment, mTestCallback);
 
-        mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
-        Duration expectedTimeout = expectedProviderInitializationTimeout();
-        mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestCallback.assertNoSuggestionMade();
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
 
-        // Simulate a location event being received by the provider. This should cause a suggestion
-        // to be made, and the timeout to be cleared.
-        mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+        // Simulate a location event being received from the primary provider. This should cause a
+        // suggestion to be made.
+        mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
 
-        mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
-        mTestThreadingDomain.assertQueueEmpty();
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestCallback.assertSuggestionMadeAndCommit(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
     }
 
     @Test
-    public void enabled_repeatedCertainty() {
-        ControllerImpl controllerImpl =
-                new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+    public void enabled_eventReceivedFromPrimaryAfterInitializationTimeout() {
+        ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+                mTestPrimaryLocationTimeZoneProvider);
         TestEnvironment testEnvironment = new TestEnvironment(
                 mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+        // Initialize and check initial state.
         controllerImpl.initialize(testEnvironment, mTestCallback);
 
-        mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
-        Duration expectedTimeout = expectedProviderInitializationTimeout();
-        mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestCallback.assertNoSuggestionMade();
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
 
-        // Simulate a location event being received by the provider. This should cause a suggestion
-        // to be made, and the timeout to be cleared.
-        mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+        // Simulate time passing with no provider event being received from the primary.
+        mTestThreadingDomain.executeNext();
+
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+        mTestCallback.assertNoSuggestionMade();
+        assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+
+        // Simulate a location event being received from the primary provider. This should cause a
+        // suggestion to be made.
+        mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
 
-        mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
-        mTestThreadingDomain.assertQueueEmpty();
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestCallback.assertSuggestionMadeAndCommit(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+    }
+
+    @Test
+    public void enabled_repeatedPrimaryCertainty() {
+        ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+                mTestPrimaryLocationTimeZoneProvider);
+        TestEnvironment testEnvironment = new TestEnvironment(
+                mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+        // Initialize and check initial state.
+        controllerImpl.initialize(testEnvironment, mTestCallback);
+
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+        mTestCallback.assertNoSuggestionMade();
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+        // Simulate a location event being received from the primary provider. This should cause a
+        // suggestion to be made.
+        mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+                USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
+
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+        mTestCallback.assertSuggestionMadeAndCommit(
+                USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
 
         // A second, identical event should not cause another suggestion.
-        mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+        mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
 
-        mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
-        mTestThreadingDomain.assertQueueEmpty();
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestCallback.assertNoSuggestionMade();
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
 
         // And a third, different event should cause another suggestion.
-        mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+        mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
 
-        mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
-        mTestThreadingDomain.assertQueueEmpty();
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestCallback.assertSuggestionMadeAndCommit(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getTimeZoneIds());
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+    }
+
+    @Test
+    public void enabled_uncertaintyTriggersASuggestionAfterUncertaintyTimeout() {
+        ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+                mTestPrimaryLocationTimeZoneProvider);
+        TestEnvironment testEnvironment = new TestEnvironment(
+                mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+        // Initialize and check initial state.
+        controllerImpl.initialize(testEnvironment, mTestCallback);
+
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+        mTestCallback.assertNoSuggestionMade();
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+        // Simulate a location event being received from the primary provider. This should cause a
+        // suggestion to be made and ensure the primary is considered initialized.
+        mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+                USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
+
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+        mTestCallback.assertSuggestionMadeAndCommit(
+                USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+        // Simulate an uncertain event being received from the primary provider. This should not
+        // cause a suggestion to be made straight away, but the uncertainty timeout should be
+        // started.
+        mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
+
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+        mTestCallback.assertNoSuggestionMade();
+        assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+
+        // Simulate time passing. This means the uncertainty timeout should fire and the uncertain
+        // suggestion should be made.
+        mTestThreadingDomain.executeNext();
+
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+        mTestCallback.assertUncertainSuggestionMadeAndCommit();
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
     }
 
     @Test
     public void enabled_briefUncertaintyTriggersNoSuggestion() {
-        ControllerImpl controllerImpl =
-                new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+        ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+                mTestPrimaryLocationTimeZoneProvider);
         TestEnvironment testEnvironment = new TestEnvironment(
                 mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+        // Initialize and check initial state.
         controllerImpl.initialize(testEnvironment, mTestCallback);
 
-        mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
-        Duration expectedTimeout = expectedProviderInitializationTimeout();
-        mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestCallback.assertNoSuggestionMade();
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
 
-        // Simulate a location event being received by the provider. This should cause a suggestion
-        // to be made, and the timeout to be cleared.
-        mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+        // Simulate a location event being received from the primary provider. This should cause a
+        // suggestion to be made.
+        mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
 
-        mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
-        mTestThreadingDomain.assertQueueEmpty();
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestCallback.assertSuggestionMadeAndCommit(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
 
-        // Uncertainty should cause a suggestion to (only) be queued.
-        mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+        // Uncertainty should not cause a suggestion to be made straight away, but the uncertainty
+        // timeout should be started.
+        mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
                 USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
 
-        mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
-        mTestThreadingDomain.assertSingleDelayedQueueItem(testEnvironment.getUncertaintyDelay());
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestCallback.assertNoSuggestionMade();
+        assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
 
-        // And a third event should cause yet another suggestion and for the queued item to be
-        // removed.
-        mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+        // And a success event from the primary provider should cause the controller to make another
+        // suggestion, the uncertainty timeout should be cancelled.
+        mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
 
-        mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
-        mTestThreadingDomain.assertQueueEmpty();
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestCallback.assertSuggestionMadeAndCommit(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getTimeZoneIds());
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
     }
 
     @Test
-    public void configChanges_enableAndDisable() {
-        ControllerImpl controllerImpl =
-                new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+    public void configChanges_enableAndDisableWithNoPreviousSuggestion() {
+        ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+                mTestPrimaryLocationTimeZoneProvider);
         TestEnvironment testEnvironment = new TestEnvironment(
                 mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_DISABLED);
+
+        // Initialize and check initial state.
         controllerImpl.initialize(testEnvironment, mTestCallback);
 
-        mTestLocationTimeZoneProvider.assertIsDisabled();
-        mTestThreadingDomain.assertQueueEmpty();
+        mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
         mTestCallback.assertNoSuggestionMade();
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
 
         // Now signal a config change so that geo detection is enabled.
         testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED);
 
-        mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
-        Duration expectedTimeout = expectedProviderInitializationTimeout();
-        mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestCallback.assertNoSuggestionMade();
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
 
         // Now signal a config change so that geo detection is disabled.
         testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED);
 
-        mTestLocationTimeZoneProvider.assertIsDisabled();
-        mTestThreadingDomain.assertQueueEmpty();
+        mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
         mTestCallback.assertNoSuggestionMade();
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
     }
 
     @Test
-    public void configChanges_disableWithPreviousSuggestion() {
-        ControllerImpl controllerImpl =
-                new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+    public void configChanges_enableAndDisableWithPreviousSuggestion() {
+        ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+                mTestPrimaryLocationTimeZoneProvider);
         TestEnvironment testEnvironment = new TestEnvironment(
-                mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+                mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_DISABLED);
+
+        // Initialize and check initial state.
         controllerImpl.initialize(testEnvironment, mTestCallback);
 
-        mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
-        Duration expectedTimeout = expectedProviderInitializationTimeout();
-        mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
+        mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
         mTestCallback.assertNoSuggestionMade();
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
 
-        // Simulate a location event being received by the provider. This should cause a suggestion
-        // to be made, and the timeout to be cleared.
-        mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+        // Now signal a config change so that geo detection is enabled.
+        testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+        mTestCallback.assertNoSuggestionMade();
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+        // Simulate a success event being received from the primary provider.
+        mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
 
-        mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
-        mTestThreadingDomain.assertQueueEmpty();
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestCallback.assertSuggestionMadeAndCommit(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
 
-        // Simulate the user disabling the provider.
-        testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED);
-
+        // Now signal a config change so that geo detection is disabled.
         // Because there had been a previous suggestion, the controller should withdraw it
         // immediately to let the downstream components know that the provider can no longer be sure
         // of the time zone.
-        mTestLocationTimeZoneProvider.assertIsDisabled();
-        mTestThreadingDomain.assertQueueEmpty();
-        mTestCallback.assertSuggestionMadeAndCommit(null);
+        testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED);
+
+        mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
+        mTestCallback.assertUncertainSuggestionMadeAndCommit();
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
     }
 
     @Test
     public void configChanges_userSwitch_enabledToEnabled() {
-        ControllerImpl controllerImpl =
-                new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+        ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+                mTestPrimaryLocationTimeZoneProvider);
         TestEnvironment testEnvironment = new TestEnvironment(
                 mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+        // Initialize and check initial state.
         controllerImpl.initialize(testEnvironment, mTestCallback);
 
-        // There should be a runnable scheduled to suggest uncertainty if no event is received.
-        mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
-        Duration expectedTimeout = expectedProviderInitializationTimeout();
-        mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestCallback.assertNoSuggestionMade();
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
 
-        // Have the provider suggest a time zone.
-        mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+        // Simulate the primary provider suggesting a time zone.
+        mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
 
         // Receiving a "success" provider event should cause a suggestion to be made synchronously,
         // and also clear the scheduled uncertainty suggestion.
-        mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
-        mTestThreadingDomain.assertQueueEmpty();
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestCallback.assertSuggestionMadeAndCommit(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
 
         // Simulate the user change (but geo detection still enabled).
         testEnvironment.simulateConfigChange(USER2_CONFIG_GEO_DETECTION_ENABLED);
 
         // We expect the provider to end up in PROVIDER_STATE_ENABLED, but it should have been
         // disabled when the user changed.
-        // The controller should schedule a runnable to make a suggestion if the provider doesn't
-        // send a success event.
-        int[] expectedStateTransitions = { PROVIDER_STATE_DISABLED, PROVIDER_STATE_ENABLED };
-        mTestLocationTimeZoneProvider.assertStateChangesAndCommit(expectedStateTransitions);
-        mTestLocationTimeZoneProvider.assertConfig(USER2_CONFIG_GEO_DETECTION_ENABLED);
-        expectedTimeout = expectedProviderInitializationTimeout();
-        mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
+        int[] expectedStateTransitions =
+                { PROVIDER_STATE_DISABLED, PROVIDER_STATE_ENABLED_INITIALIZING };
+        mTestPrimaryLocationTimeZoneProvider.assertStateChangesAndCommit(expectedStateTransitions);
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfig(
+                PROVIDER_STATE_ENABLED_INITIALIZING, USER2_CONFIG_GEO_DETECTION_ENABLED);
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+    }
+
+    @Test
+    public void primaryPermFailure_disableAndEnable() {
+        ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+                mTestPrimaryLocationTimeZoneProvider);
+        TestEnvironment testEnvironment = new TestEnvironment(
+                mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+        // Initialize and check initial state.
+        controllerImpl.initialize(testEnvironment, mTestCallback);
+
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestCallback.assertNoSuggestionMade();
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
 
-        // Simulate no event being received, and time passing.
-        mTestThreadingDomain.executeNext();
+        // Simulate a failure location event being received from the primary provider. This should
+        // cause an uncertain suggestion to be made.
+        mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+                USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT);
 
-        mTestLocationTimeZoneProvider.assertIsEnabled(USER2_CONFIG_GEO_DETECTION_ENABLED);
-        mTestThreadingDomain.assertQueueEmpty();
+        mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
         mTestCallback.assertUncertainSuggestionMadeAndCommit();
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+        // Now signal a config change so that geo detection is disabled.
+        testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED);
+
+        mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
+        mTestCallback.assertNoSuggestionMade();
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+        // Now signal a config change so that geo detection is enabled.
+        testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+        mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
+        mTestCallback.assertUncertainSuggestionMadeAndCommit();
+        assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+    }
+
+    private static void assertUncertaintyTimeoutSet(
+            LocationTimeZoneProviderController.Environment environment,
+            LocationTimeZoneProviderController controller) {
+        assertTrue(controller.isUncertaintyTimeoutSet());
+        assertEquals(environment.getUncertaintyDelay().toMillis(),
+                controller.getUncertaintyTimeoutDelayMillis());
     }
 
     private static LocationTimeZoneEvent createLocationTimeZoneEvent(@UserIdInt int userId,
@@ -362,16 +540,15 @@
     }
 
 
-    private Duration expectedProviderInitializationTimeout() {
-        return TestEnvironment.PROVIDER_INITIALIZATION_TIMEOUT
-                .plus(TestEnvironment.PROVIDER_INITIALIZATION_TIMEOUT_FUZZ);
-    }
-
     private static class TestEnvironment extends LocationTimeZoneProviderController.Environment {
 
+        // These timeouts are set deliberately so that:
+        // (initialization timeout * 2) < uncertainty delay
+        //
+        // That makes the order of initialization timeout Vs uncertainty delay deterministic.
         static final Duration PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(5);
         static final Duration PROVIDER_INITIALIZATION_TIMEOUT_FUZZ = Duration.ofMinutes(1);
-        private static final Duration UNCERTAINTY_DELAY = Duration.ofMinutes(3);
+        private static final Duration UNCERTAINTY_DELAY = Duration.ofMinutes(15);
 
         private final LocationTimeZoneProviderController mController;
         private ConfigurationInternal mConfigurationInternal;
@@ -491,38 +668,57 @@
             assertTrue(mInitialized);
         }
 
-        void assertIsDisabled() {
-            // Disabled providers don't hold config.
-            assertConfig(null);
-            assertIsEnabledAndCommit(false);
-        }
-
-        /**
-         * Asserts the provider's config matches the expected, and the current state is set
-         * accordingly. Commits the latest changes to the state.
-         */
-        void assertIsEnabled(@NonNull ConfigurationInternal expectedConfig) {
-            assertConfig(expectedConfig);
-
-            boolean expectIsEnabled = expectedConfig.getAutoDetectionEnabledBehavior();
-            assertIsEnabledAndCommit(expectIsEnabled);
-        }
-
-        private void assertIsEnabledAndCommit(boolean enabled) {
-            ProviderState currentState = mCurrentState.get();
-            if (enabled) {
-                assertEquals(PROVIDER_STATE_ENABLED, currentState.stateEnum);
-            } else {
-                assertEquals(PROVIDER_STATE_DISABLED, currentState.stateEnum);
-            }
+        public void assertIsPermFailedAndCommit() {
+            // A failed provider doesn't hold config.
+            assertStateEnumAndConfig(PROVIDER_STATE_PERM_FAILED, null /* config */);
             mTestProviderState.commitLatest();
         }
 
-        void assertConfig(@NonNull ConfigurationInternal expectedConfig) {
+        void assertIsDisabledAndCommit() {
+            // A disabled provider doesn't hold config.
+            assertStateEnumAndConfig(PROVIDER_STATE_DISABLED, null /* config */);
+            mTestProviderState.commitLatest();
+        }
+
+        /**
+         * Asserts the provider's state enum and config matches the expected.
+         * Commits the latest changes to the state.
+         */
+        void assertStateEnumAndConfigAndCommit(
+                @ProviderStateEnum int expectedStateEnum,
+                @Nullable ConfigurationInternal expectedConfig) {
+            assertStateEnumAndConfig(expectedStateEnum, expectedConfig);
+            mTestProviderState.commitLatest();
+        }
+
+        /**
+         * Asserts the provider's state enum and config matches the expected.
+         * Does not commit any state changes.
+         */
+        void assertStateEnumAndConfig(
+                @ProviderStateEnum int expectedStateEnum,
+                @Nullable ConfigurationInternal expectedConfig) {
+            ProviderState currentState = mCurrentState.get();
+            assertEquals(expectedStateEnum, currentState.stateEnum);
+
+            // If and only if the controller is initializing, the initialization timeout must be
+            // set.
+            assertEquals(expectedStateEnum == PROVIDER_STATE_ENABLED_INITIALIZING,
+                    isInitializationTimeoutSet());
+
+            assertConfig(expectedConfig);
+        }
+
+        private void assertConfig(@Nullable ConfigurationInternal expectedConfig) {
             ProviderState currentState = mCurrentState.get();
             assertEquals(expectedConfig, currentState.currentUserConfiguration);
         }
 
+        void assertInitializationTimeoutSet(Duration expectedTimeout) {
+            assertTrue(isInitializationTimeoutSet());
+            assertEquals(expectedTimeout, getInitializationTimeoutDelay());
+        }
+
         void simulateLocationTimeZoneEvent(@NonNull LocationTimeZoneEvent event) {
             handleLocationTimeZoneEvent(event);
         }
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/HandlerThreadingDomainTest.java b/services/tests/servicestests/src/com/android/server/location/timezone/HandlerThreadingDomainTest.java
index cbaf0f3..4d6775d 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/HandlerThreadingDomainTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/HandlerThreadingDomainTest.java
@@ -115,28 +115,7 @@
     }
 
     @Test
-    public void singleRunnableHandler_runSynchronously() throws Exception {
-        ThreadingDomain domain = new HandlerThreadingDomain(mTestHandler);
-        SingleRunnableQueue singleRunnableQueue = domain.createSingleRunnableQueue();
-
-        AtomicBoolean testPassed = new AtomicBoolean(false);
-        // Calls to SingleRunnableQueue must be made on the handler thread it is associated with,
-        // so this uses runWithScissors() to block until the lambda has completed.
-        mTestHandler.runWithScissors(() -> {
-            Thread testThread = Thread.currentThread();
-            CountDownLatch latch = new CountDownLatch(1);
-            singleRunnableQueue.runSynchronously(() -> {
-                assertSame(Thread.currentThread(), testThread);
-                latch.countDown();
-            });
-            assertTrue(awaitWithRuntimeException(latch, 60, TimeUnit.SECONDS));
-            testPassed.set(true);
-        }, TimeUnit.SECONDS.toMillis(60));
-        assertTrue(testPassed.get());
-    }
-
-    @Test
-    public void singleRunnableHandler_runDelayed() throws Exception {
+    public void singleRunnableQueue_runDelayed() throws Exception {
         ThreadingDomain domain = new HandlerThreadingDomain(mTestHandler);
         SingleRunnableQueue singleRunnableQueue = domain.createSingleRunnableQueue();
 
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/NullLocationTimeZoneProviderTest.java b/services/tests/servicestests/src/com/android/server/location/timezone/NullLocationTimeZoneProviderTest.java
index 5542db0..5403a65 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/NullLocationTimeZoneProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/NullLocationTimeZoneProviderTest.java
@@ -16,7 +16,7 @@
 package com.android.server.location.timezone;
 
 import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_INITIALIZING;
 import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
 import static com.android.server.location.timezone.TestSupport.USER1_CONFIG_GEO_DETECTION_ENABLED;
 
@@ -76,15 +76,16 @@
 
         ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED;
         Duration arbitraryInitializationTimeout = Duration.ofMinutes(5);
-        provider.enable(config, arbitraryInitializationTimeout);
+        Duration arbitraryInitializationTimeoutFuzz = Duration.ofMinutes(2);
+        provider.enable(config, arbitraryInitializationTimeout, arbitraryInitializationTimeoutFuzz);
 
-        // The StubbedProvider should enters enabled state, but immediately schedule a runnable to
-        // switch to perm failure.
+        // The NullProvider should enter the enabled state, but have schedule an immediate runnable
+        // to switch to perm failure.
         ProviderState currentState = provider.getCurrentState();
         assertSame(provider, currentState.provider);
-        assertEquals(PROVIDER_STATE_ENABLED, currentState.stateEnum);
+        assertEquals(PROVIDER_STATE_ENABLED_INITIALIZING, currentState.stateEnum);
         assertEquals(config, currentState.currentUserConfiguration);
-        mTestThreadingDomain.assertSingleImmediateQueueItem();
+        mTestThreadingDomain.assertNextQueueItemIsImmediate();
         // Entering enabled() does not trigger an onProviderStateChanged() as it is requested by the
         // controller.
         mTestController.assertProviderChangeNotTriggered();
@@ -116,6 +117,18 @@
             // Not needed for provider testing.
         }
 
+        @Override
+        boolean isUncertaintyTimeoutSet() {
+            // Not needed for provider testing.
+            return false;
+        }
+
+        @Override
+        long getUncertaintyTimeoutDelayMillis() {
+            // Not needed for provider testing.
+            return 0;
+        }
+
         void onProviderStateChange(ProviderState providerState) {
             this.mProviderState.set(providerState);
         }
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java b/services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java
index def919e..7359abd 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java
@@ -16,13 +16,15 @@
 package com.android.server.location.timezone;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
 import java.time.Duration;
-import java.util.LinkedList;
+import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.Objects;
 
 /**
@@ -33,6 +35,10 @@
 class TestThreadingDomain extends ThreadingDomain {
 
     static class QueuedRunnable {
+
+        static final Comparator<? super QueuedRunnable> COMPARATOR =
+                (o1, o2) -> (int) (o1.executionTimeMillis - o2.executionTimeMillis);
+
         @NonNull public final Runnable runnable;
         @Nullable public final Object token;
         public final long executionTimeMillis;
@@ -55,7 +61,7 @@
     }
 
     private long mCurrentTimeMillis;
-    private LinkedList<QueuedRunnable> mQueue = new LinkedList<>();
+    private ArrayList<QueuedRunnable> mQueue = new ArrayList<>();
 
     TestThreadingDomain() {
         // Pick an arbitrary time.
@@ -69,22 +75,23 @@
 
     @Override
     void post(Runnable r) {
-        mQueue.add(new QueuedRunnable(r, null, mCurrentTimeMillis));
+        postDelayed(r, null, 0);
     }
 
     @Override
     void postDelayed(Runnable r, long delayMillis) {
-        mQueue.add(new QueuedRunnable(r, null, mCurrentTimeMillis + delayMillis));
+        postDelayed(r, null, delayMillis);
     }
 
     @Override
     void postDelayed(Runnable r, Object token, long delayMillis) {
         mQueue.add(new QueuedRunnable(r, token, mCurrentTimeMillis + delayMillis));
+        mQueue.sort(QueuedRunnable.COMPARATOR);
     }
 
     @Override
     void removeQueuedRunnables(Object token) {
-        mQueue.removeIf(runnable -> runnable.token != null && runnable.token.equals(token));
+        mQueue.removeIf(runnable -> runnable.token != null && runnable.token == token);
     }
 
     void assertSingleDelayedQueueItem(Duration expectedDelay) {
@@ -114,14 +121,14 @@
     }
 
     long getNextQueueItemDelayMillis() {
-        assertQueueLength(1);
-        return mQueue.getFirst().executionTimeMillis - mCurrentTimeMillis;
+        assertFalse(mQueue.isEmpty());
+        return mQueue.get(0).executionTimeMillis - mCurrentTimeMillis;
     }
 
     void executeNext() {
-        assertQueueLength(1);
+        assertFalse(mQueue.isEmpty());
+        QueuedRunnable queued = mQueue.remove(0);
 
-        QueuedRunnable queued = mQueue.removeFirst();
         mCurrentTimeMillis = queued.executionTimeMillis;
         queued.runnable.run();
     }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
index d7ed96f..54b5bee 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
@@ -16,22 +16,23 @@
 
 package com.android.server.timezonedetector;
 
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED;
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_POSSESSED;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import android.app.timezonedetector.TimeZoneCapabilities;
+import android.app.time.TimeZoneCapabilities;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
 
 import org.junit.Test;
 
 /**
- * Tests for {@link ConfigurationInternal} and the {@link TimeZoneCapabilities} and
- * {@link android.app.timezonedetector.TimeZoneConfiguration} that can be generated from it.
+ * Tests for {@link ConfigurationInternal} and the {@link TimeZoneCapabilitiesAndConfig}.
  */
 public class ConfigurationInternalTest {
 
@@ -59,13 +60,20 @@
             assertTrue(autoOnConfig.getAutoDetectionEnabledBehavior());
             assertTrue(autoOnConfig.getGeoDetectionEnabledBehavior());
 
-            TimeZoneCapabilities capabilities = autoOnConfig.createCapabilities();
-            assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabled());
-            assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureGeoDetectionEnabled());
-            assertEquals(CAPABILITY_NOT_APPLICABLE, capabilities.getSuggestManualTimeZone());
-            assertEquals(autoOnConfig.asConfiguration(), capabilities.getConfiguration());
-            assertTrue(capabilities.getConfiguration().isAutoDetectionEnabled());
-            assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+                    autoOnConfig.createCapabilitiesAndConfig();
+
+            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            assertEquals(CAPABILITY_POSSESSED,
+                    capabilities.getConfigureAutoDetectionEnabledCapability());
+            assertEquals(CAPABILITY_POSSESSED,
+                    capabilities.getConfigureGeoDetectionEnabledCapability());
+            assertEquals(CAPABILITY_NOT_APPLICABLE,
+                    capabilities.getSuggestManualTimeZoneCapability());
+
+            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            assertTrue(configuration.isAutoDetectionEnabled());
+            assertTrue(configuration.isGeoDetectionEnabled());
         }
 
         {
@@ -77,13 +85,20 @@
             assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
             assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior());
 
-            TimeZoneCapabilities capabilities = autoOffConfig.createCapabilities();
-            assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabled());
-            assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureGeoDetectionEnabled());
-            assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
-            assertEquals(autoOffConfig.asConfiguration(), capabilities.getConfiguration());
-            assertFalse(capabilities.getConfiguration().isAutoDetectionEnabled());
-            assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+                    autoOffConfig.createCapabilitiesAndConfig();
+
+            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            assertEquals(CAPABILITY_POSSESSED,
+                    capabilities.getConfigureAutoDetectionEnabledCapability());
+            assertEquals(CAPABILITY_POSSESSED,
+                    capabilities.getConfigureGeoDetectionEnabledCapability());
+            assertEquals(CAPABILITY_POSSESSED,
+                    capabilities.getSuggestManualTimeZoneCapability());
+
+            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            assertFalse(configuration.isAutoDetectionEnabled());
+            assertTrue(configuration.isGeoDetectionEnabled());
         }
     }
 
@@ -106,13 +121,20 @@
             assertTrue(autoOnConfig.getAutoDetectionEnabledBehavior());
             assertTrue(autoOnConfig.getGeoDetectionEnabledBehavior());
 
-            TimeZoneCapabilities capabilities = autoOnConfig.createCapabilities();
-            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureAutoDetectionEnabled());
-            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureGeoDetectionEnabled());
-            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeZone());
-            assertEquals(autoOnConfig.asConfiguration(), capabilities.getConfiguration());
-            assertTrue(capabilities.getConfiguration().isAutoDetectionEnabled());
-            assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+                    autoOnConfig.createCapabilitiesAndConfig();
+
+            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            assertEquals(CAPABILITY_NOT_ALLOWED,
+                    capabilities.getConfigureAutoDetectionEnabledCapability());
+            assertEquals(CAPABILITY_NOT_ALLOWED,
+                    capabilities.getConfigureGeoDetectionEnabledCapability());
+            assertEquals(CAPABILITY_NOT_ALLOWED,
+                    capabilities.getSuggestManualTimeZoneCapability());
+
+            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            assertTrue(configuration.isAutoDetectionEnabled());
+            assertTrue(configuration.isGeoDetectionEnabled());
         }
 
         {
@@ -124,13 +146,20 @@
             assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
             assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior());
 
-            TimeZoneCapabilities capabilities = autoOffConfig.createCapabilities();
-            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureAutoDetectionEnabled());
-            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureGeoDetectionEnabled());
-            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeZone());
-            assertEquals(autoOffConfig.asConfiguration(), capabilities.getConfiguration());
-            assertFalse(capabilities.getConfiguration().isAutoDetectionEnabled());
-            assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+                    autoOffConfig.createCapabilitiesAndConfig();
+
+            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            assertEquals(CAPABILITY_NOT_ALLOWED,
+                    capabilities.getConfigureAutoDetectionEnabledCapability());
+            assertEquals(CAPABILITY_NOT_ALLOWED,
+                    capabilities.getConfigureGeoDetectionEnabledCapability());
+            assertEquals(CAPABILITY_NOT_ALLOWED,
+                    capabilities.getSuggestManualTimeZoneCapability());
+
+            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            assertFalse(configuration.isAutoDetectionEnabled());
+            assertTrue(configuration.isGeoDetectionEnabled());
         }
     }
 
@@ -153,13 +182,19 @@
             assertFalse(autoOnConfig.getAutoDetectionEnabledBehavior());
             assertFalse(autoOnConfig.getGeoDetectionEnabledBehavior());
 
-            TimeZoneCapabilities capabilities = autoOnConfig.createCapabilities();
-            assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureAutoDetectionEnabled());
-            assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureGeoDetectionEnabled());
-            assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
-            assertEquals(autoOnConfig.asConfiguration(), capabilities.getConfiguration());
-            assertTrue(capabilities.getConfiguration().isAutoDetectionEnabled());
-            assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+                    autoOnConfig.createCapabilitiesAndConfig();
+
+            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            assertEquals(CAPABILITY_NOT_SUPPORTED,
+                    capabilities.getConfigureAutoDetectionEnabledCapability());
+            assertEquals(CAPABILITY_NOT_SUPPORTED,
+                    capabilities.getConfigureGeoDetectionEnabledCapability());
+            assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZoneCapability());
+
+            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            assertTrue(configuration.isAutoDetectionEnabled());
+            assertTrue(configuration.isGeoDetectionEnabled());
         }
         {
             ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
@@ -170,13 +205,19 @@
             assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
             assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior());
 
-            TimeZoneCapabilities capabilities = autoOffConfig.createCapabilities();
-            assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureAutoDetectionEnabled());
-            assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureGeoDetectionEnabled());
-            assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
-            assertEquals(autoOffConfig.asConfiguration(), capabilities.getConfiguration());
-            assertFalse(capabilities.getConfiguration().isAutoDetectionEnabled());
-            assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+                    autoOffConfig.createCapabilitiesAndConfig();
+
+            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            assertEquals(CAPABILITY_NOT_SUPPORTED,
+                    capabilities.getConfigureAutoDetectionEnabledCapability());
+            assertEquals(CAPABILITY_NOT_SUPPORTED,
+                    capabilities.getConfigureGeoDetectionEnabledCapability());
+            assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZoneCapability());
+
+            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            assertFalse(configuration.isAutoDetectionEnabled());
+            assertTrue(configuration.isGeoDetectionEnabled());
         }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
index 4ef2082..bad380a 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
@@ -22,10 +22,11 @@
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.app.time.TimeZoneCapabilities;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.app.timezonedetector.TimeZoneCapabilities;
-import android.app.timezonedetector.TimeZoneConfiguration;
 import android.util.IndentingPrintWriter;
 
 import java.util.ArrayList;
@@ -67,20 +68,25 @@
     }
 
     @Override
-    public boolean updateConfiguration(@NonNull TimeZoneConfiguration requestedChanges) {
+    public boolean updateConfiguration(
+            @UserIdInt int userID, @NonNull TimeZoneConfiguration requestedChanges) {
         assertNotNull(mConfigurationInternal);
         assertNotNull(requestedChanges);
 
         // Simulate the real strategy's behavior: the new configuration will be updated to be the
         // old configuration merged with the new if the user has the capability to up the settings.
         // Then, if the configuration changed, the change listener is invoked.
-        TimeZoneCapabilities capabilities = mConfigurationInternal.createCapabilities();
-        TimeZoneConfiguration newConfiguration = capabilities.applyUpdate(requestedChanges);
+        TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+                mConfigurationInternal.createCapabilitiesAndConfig();
+        TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+        TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+        TimeZoneConfiguration newConfiguration =
+                capabilities.tryApplyConfigChanges(configuration, requestedChanges);
         if (newConfiguration == null) {
             return false;
         }
 
-        if (!newConfiguration.equals(capabilities.getConfiguration())) {
+        if (!newConfiguration.equals(capabilitiesAndConfig.getConfiguration())) {
             mConfigurationInternal = mConfigurationInternal.merge(newConfiguration);
 
             // Note: Unlike the real strategy, the listeners is invoked synchronously.
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
index 27b04b6..cb27657 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -31,10 +31,10 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
-import android.app.timezonedetector.ITimeZoneConfigurationListener;
+import android.app.time.ITimeZoneDetectorListener;
+import android.app.time.TimeZoneConfiguration;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.app.timezonedetector.TimeZoneConfiguration;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.HandlerThread;
@@ -91,85 +91,85 @@
     }
 
     @Test(expected = SecurityException.class)
-    public void testGetCapabilities_withoutPermission() {
+    public void testGetCapabilitiesAndConfig_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
 
         try {
-            mTimeZoneDetectorService.getCapabilities();
+            mTimeZoneDetectorService.getCapabilitiesAndConfig();
             fail();
         } finally {
             verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
+                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
                     anyString());
         }
     }
 
     @Test
-    public void testGetCapabilities() {
+    public void testGetCapabilitiesAndConfig() {
         doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
 
         ConfigurationInternal configuration =
                 createConfigurationInternal(true /* autoDetectionEnabled*/);
         mFakeTimeZoneDetectorStrategy.initializeConfiguration(configuration);
 
-        assertEquals(configuration.createCapabilities(),
-                mTimeZoneDetectorService.getCapabilities());
+        assertEquals(configuration.createCapabilitiesAndConfig(),
+                mTimeZoneDetectorService.getCapabilitiesAndConfig());
 
         verify(mMockContext).enforceCallingPermission(
-                eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
                 anyString());
     }
 
     @Test(expected = SecurityException.class)
-    public void testAddConfigurationListener_withoutPermission() {
+    public void testAddListener_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
 
-        ITimeZoneConfigurationListener mockListener = mock(ITimeZoneConfigurationListener.class);
+        ITimeZoneDetectorListener mockListener = mock(ITimeZoneDetectorListener.class);
         try {
-            mTimeZoneDetectorService.addConfigurationListener(mockListener);
+            mTimeZoneDetectorService.addListener(mockListener);
             fail();
         } finally {
             verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
+                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
                     anyString());
         }
     }
 
     @Test(expected = SecurityException.class)
-    public void testRemoveConfigurationListener_withoutPermission() {
+    public void testRemoveListener_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
 
-        ITimeZoneConfigurationListener mockListener = mock(ITimeZoneConfigurationListener.class);
+        ITimeZoneDetectorListener mockListener = mock(ITimeZoneDetectorListener.class);
         try {
-            mTimeZoneDetectorService.removeConfigurationListener(mockListener);
+            mTimeZoneDetectorService.removeListener(mockListener);
             fail("Expected a SecurityException");
         } finally {
             verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
+                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
                     anyString());
         }
     }
 
     @Test
-    public void testConfigurationChangeListenerRegistrationAndCallbacks() throws Exception {
+    public void testListenerRegistrationAndCallbacks() throws Exception {
         ConfigurationInternal initialConfiguration =
                 createConfigurationInternal(false /* autoDetectionEnabled */);
         mFakeTimeZoneDetectorStrategy.initializeConfiguration(initialConfiguration);
 
         IBinder mockListenerBinder = mock(IBinder.class);
-        ITimeZoneConfigurationListener mockListener = mock(ITimeZoneConfigurationListener.class);
+        ITimeZoneDetectorListener mockListener = mock(ITimeZoneDetectorListener.class);
 
         {
             doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
             when(mockListener.asBinder()).thenReturn(mockListenerBinder);
 
-            mTimeZoneDetectorService.addConfigurationListener(mockListener);
+            mTimeZoneDetectorService.addListener(mockListener);
 
             verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
+                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
                     anyString());
             verify(mockListener).asBinder();
             verify(mockListenerBinder).linkToDeath(any(), anyInt());
@@ -186,7 +186,7 @@
             mTimeZoneDetectorService.updateConfiguration(autoDetectEnabledConfiguration);
 
             verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
+                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
                     anyString());
             verify(mockListener).onChange();
             verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
@@ -200,10 +200,10 @@
 
             // Now remove the listener, change the config again, and verify the listener is not
             // called.
-            mTimeZoneDetectorService.removeConfigurationListener(mockListener);
+            mTimeZoneDetectorService.removeListener(mockListener);
 
             verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
+                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
                     anyString());
             verify(mockListener).asBinder();
             verify(mockListenerBinder).unlinkToDeath(any(), eq(0));
@@ -219,7 +219,7 @@
             mTimeZoneDetectorService.updateConfiguration(autoDetectDisabledConfiguration);
 
             verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
+                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
                     anyString());
             verify(mockListener, never()).onChange();
             verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
@@ -354,7 +354,7 @@
     }
 
     private static TimeZoneConfiguration createTimeZoneConfiguration(boolean autoDetectionEnabled) {
-        return new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+        return new TimeZoneConfiguration.Builder()
                 .setAutoDetectionEnabled(autoDetectionEnabled)
                 .build();
     }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index 1cdf193..296aa73 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -39,11 +39,11 @@
 
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.time.TimeZoneConfiguration;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion.MatchType;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion.Quality;
-import android.app.timezonedetector.TimeZoneConfiguration;
 import android.util.IndentingPrintWriter;
 
 import com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.QualifiedTelephonyTimeZoneSuggestion;
@@ -186,26 +186,27 @@
         Script script = new Script().initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
 
         // Set the configuration with auto detection enabled.
-        script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */);
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */);
 
         // Nothing should have happened: it was initialized in this state.
         script.verifyConfigurationNotChanged();
 
         // Update the configuration with auto detection disabled.
-        script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, true /* expectedResult */);
+        script.simulateUpdateConfiguration(
+                USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */);
 
         // The settings should have been changed and the StrategyListener onChange() called.
         script.verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_DISABLED_GEO_DISABLED);
 
         // Update the configuration with auto detection enabled.
-        script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */);
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */);
 
         // The settings should have been changed and the StrategyListener onChange() called.
         script.verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
 
         // Update the configuration to enable geolocation time zone detection.
         script.simulateUpdateConfiguration(
-                CONFIG_GEO_DETECTION_ENABLED,  true /* expectedResult */);
+                USER_ID, CONFIG_GEO_DETECTION_ENABLED,  true /* expectedResult */);
 
         // The settings should have been changed and the StrategyListener onChange() called.
         script.verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED);
@@ -216,20 +217,22 @@
         Script script = new Script().initializeConfig(CONFIG_INT_USER_RESTRICTED_AUTO_ENABLED);
 
         // Try to update the configuration with auto detection disabled.
-        script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, false /* expectedResult */);
+        script.simulateUpdateConfiguration(
+                USER_ID, CONFIG_AUTO_DISABLED, false /* expectedResult */);
 
         // The settings should not have been changed: user shouldn't have the capabilities.
         script.verifyConfigurationNotChanged();
 
         // Update the configuration with auto detection enabled.
-        script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED,  false /* expectedResult */);
+        script.simulateUpdateConfiguration(
+                USER_ID, CONFIG_AUTO_ENABLED,  false /* expectedResult */);
 
         // The settings should not have been changed: user shouldn't have the capabilities.
         script.verifyConfigurationNotChanged();
 
         // Try to  update the configuration to enable geolocation time zone detection.
         script.simulateUpdateConfiguration(
-                CONFIG_GEO_DETECTION_ENABLED,  false /* expectedResult */);
+                USER_ID, CONFIG_GEO_DETECTION_ENABLED,  false /* expectedResult */);
 
         // The settings should not have been changed: user shouldn't have the capabilities.
         script.verifyConfigurationNotChanged();
@@ -240,13 +243,15 @@
         Script script = new Script().initializeConfig(CONFIG_INT_AUTO_DETECT_NOT_SUPPORTED);
 
         // Try to update the configuration with auto detection disabled.
-        script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, false /* expectedResult */);
+        script.simulateUpdateConfiguration(
+                USER_ID, CONFIG_AUTO_DISABLED, false /* expectedResult */);
 
         // The settings should not have been changed: user shouldn't have the capabilities.
         script.verifyConfigurationNotChanged();
 
         // Update the configuration with auto detection enabled.
-        script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, false /* expectedResult */);
+        script.simulateUpdateConfiguration(
+                USER_ID, CONFIG_AUTO_ENABLED, false /* expectedResult */);
 
         // The settings should not have been changed: user shouldn't have the capabilities.
         script.verifyConfigurationNotChanged();
@@ -389,7 +394,8 @@
                     mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
 
             // Toggling the time zone setting on should cause the device setting to be set.
-            script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */);
+            script.simulateUpdateConfiguration(
+                    USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */);
 
             // When time zone detection is already enabled the suggestion (if it scores highly
             // enough) should be set immediately.
@@ -406,7 +412,8 @@
                     mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
 
             // Toggling the time zone setting should off should do nothing.
-            script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, true /* expectedResult */)
+            script.simulateUpdateConfiguration(
+                    USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
                     .verifyTimeZoneNotChanged();
 
             // Assert internal service state.
@@ -588,18 +595,20 @@
 
         // Toggling time zone detection should set the device time zone only if the current setting
         // value is different from the most recent telephony suggestion.
-        script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, true /* expectedResult */)
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
                 .verifyTimeZoneNotChanged()
-                .simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */)
+                .simulateUpdateConfiguration(
+                        USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
                 .verifyTimeZoneNotChanged();
 
         // Simulate a user turning auto detection off, a new suggestion being made while auto
         // detection is off, and the user turning it on again.
-        script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, true /* expectedResult */)
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
                 .simulateTelephonyTimeZoneSuggestion(newYorkSuggestion)
                 .verifyTimeZoneNotChanged();
         // Latest suggestion should be used.
-        script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */)
+        script.simulateUpdateConfiguration(
+                USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
                 .verifyTimeZoneChangedAndReset(newYorkSuggestion);
     }
 
@@ -784,7 +793,7 @@
         assertEquals(suggestion, mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
 
         // Turn off geo detection and verify the latest suggestion is cleared.
-        script.simulateUpdateConfiguration(CONFIG_GEO_DETECTION_DISABLED, true)
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_GEO_DETECTION_DISABLED, true)
                 .verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
 
         // Assert internal service state.
@@ -824,19 +833,21 @@
         // Toggling the time zone detection enabled setting on should cause the device setting to be
         // set from the telephony signal, as we've started with geolocation time zone detection
         // disabled.
-        script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */)
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
                 .verifyTimeZoneChangedAndReset(telephonySuggestion);
 
         // Changing the detection to enable geo detection won't cause the device tz setting to
         // change because the geo suggestion is empty.
-        script.simulateUpdateConfiguration(CONFIG_GEO_DETECTION_ENABLED, true /* expectedResult */)
+        script.simulateUpdateConfiguration(
+                USER_ID, CONFIG_GEO_DETECTION_ENABLED, true /* expectedResult */)
                 .verifyTimeZoneNotChanged()
                 .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
                 .verifyTimeZoneChangedAndReset(geolocationSuggestion.getZoneIds().get(0));
 
         // Changing the detection to disable geo detection should cause the device tz setting to
         // change to the telephony suggestion.
-        script.simulateUpdateConfiguration(CONFIG_GEO_DETECTION_DISABLED, true /* expectedResult */)
+        script.simulateUpdateConfiguration(
+                USER_ID, CONFIG_GEO_DETECTION_DISABLED, true /* expectedResult */)
                 .verifyTimeZoneChangedAndReset(telephonySuggestion);
 
         assertNull(mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
@@ -898,7 +909,7 @@
 
     private static TimeZoneConfiguration createConfig(
             @Nullable Boolean autoDetection, @Nullable Boolean geoDetection) {
-        TimeZoneConfiguration.Builder builder = new TimeZoneConfiguration.Builder(USER_ID);
+        TimeZoneConfiguration.Builder builder = new TimeZoneConfiguration.Builder();
         if (autoDetection != null) {
             builder.setAutoDetectionEnabled(autoDetection);
         }
@@ -957,9 +968,10 @@
         }
 
         @Override
-        public void storeConfiguration(TimeZoneConfiguration newConfiguration) {
+        public void storeConfiguration(
+                @UserIdInt int userId, TimeZoneConfiguration newConfiguration) {
             ConfigurationInternal oldConfiguration = mConfigurationInternal.getLatest();
-            if (newConfiguration.getUserId() != oldConfiguration.getUserId()) {
+            if (userId != oldConfiguration.getUserId()) {
                 fail("FakeCallback does not support multiple users");
             }
 
@@ -1014,9 +1026,9 @@
          * the return value.
          */
         Script simulateUpdateConfiguration(
-                TimeZoneConfiguration configuration, boolean expectedResult) {
+                int userId, TimeZoneConfiguration configuration, boolean expectedResult) {
             assertEquals(expectedResult,
-                    mTimeZoneDetectorStrategy.updateConfiguration(configuration));
+                    mTimeZoneDetectorStrategy.updateConfiguration(userId, configuration));
             return this;
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 2c64526..0c6d638 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -84,6 +84,7 @@
 import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -703,6 +704,7 @@
         assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
     }
 
+    @Ignore
     @Test
     public void testPredictionTimedOut() throws Exception {
         // Set it to timeout or usage, so that prediction can override it
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 633d216..4a90466 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1349,6 +1349,27 @@
     }
 
     @Test
+    public void testNoFixedRotationOnResumedScheduledApp() {
+        unblockDisplayRotation(mDisplayContent);
+        final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        app.setVisible(false);
+        app.setState(Task.ActivityState.RESUMED, "test");
+        mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_ACTIVITY_OPEN,
+                false /* alwaysKeepCurrent */);
+        mDisplayContent.mOpeningApps.add(app);
+        final int newOrientation = getRotatedOrientation(mDisplayContent);
+        app.setRequestedOrientation(newOrientation);
+
+        // The condition should reject using fixed rotation because the resumed client in real case
+        // might get display info immediately. And the fixed rotation adjustments haven't arrived
+        // client side so the info may be inconsistent with the requested orientation.
+        verify(mDisplayContent).handleTopActivityLaunchingInDifferentOrientation(eq(app),
+                eq(true) /* checkOpening */);
+        assertFalse(app.isFixedRotationTransforming());
+        assertFalse(mDisplayContent.hasTopFixedRotationLaunchingApp());
+    }
+
+    @Test
     public void testRecentsNotRotatingWithFixedRotation() {
         unblockDisplayRotation(mDisplayContent);
         final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
new file mode 100644
index 0000000..ce22205
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2020 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.wm;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowManager.TRANSIT_TASK_OPEN;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.mock;
+
+import android.platform.test.annotations.Presubmit;
+import android.util.ArrayMap;
+import android.window.ITaskOrganizer;
+import android.window.TransitionInfo;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Build/Install/Run:
+ *  atest WmTests:TransitionRecordTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class TransitionTests extends WindowTestsBase {
+
+    @Test
+    public void testCreateInfo_NewTask() {
+        final Task newTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
+                ACTIVITY_TYPE_STANDARD, mDisplayContent);
+        final Task oldTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
+                ACTIVITY_TYPE_STANDARD, mDisplayContent);
+        newTask.setHasBeenVisible(true);
+        oldTask.setHasBeenVisible(false);
+        final ActivityRecord closing = createActivityRecordInTask(oldTask);
+        final ActivityRecord opening = createActivityRecordInTask(newTask);
+        closing.setVisible(true);
+        closing.mVisibleRequested = false;
+        opening.setVisible(false);
+        opening.mVisibleRequested = true;
+        ArrayMap<WindowContainer, Transition.ChangeInfo> participants = new ArrayMap<>();
+
+        int transitType = TRANSIT_TASK_OPEN;
+
+        // Check basic both tasks participating
+        participants.put(oldTask, new Transition.ChangeInfo());
+        participants.put(newTask, new Transition.ChangeInfo());
+        TransitionInfo info =
+                Transition.calculateTransitionInfo(transitType, participants);
+        assertEquals(2, info.getChanges().size());
+        assertEquals(transitType, info.getType());
+
+        // Check that children are pruned
+        participants.put(opening, new Transition.ChangeInfo());
+        participants.put(closing, new Transition.ChangeInfo());
+        info = Transition.calculateTransitionInfo(transitType, participants);
+        assertEquals(2, info.getChanges().size());
+        assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
+        assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
+
+        // Check combined prune and promote
+        participants.remove(newTask);
+        info = Transition.calculateTransitionInfo(transitType, participants);
+        assertEquals(2, info.getChanges().size());
+        assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
+        assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
+
+        // Check multi promote
+        participants.remove(oldTask);
+        info = Transition.calculateTransitionInfo(transitType, participants);
+        assertEquals(2, info.getChanges().size());
+        assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
+        assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
+    }
+
+    @Test
+    public void testCreateInfo_NestedTasks() {
+        final Task newTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
+                ACTIVITY_TYPE_STANDARD, mDisplayContent);
+        final Task newNestedTask = createTaskInStack(newTask, 0);
+        final Task newNestedTask2 = createTaskInStack(newTask, 0);
+        final Task oldTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
+                ACTIVITY_TYPE_STANDARD, mDisplayContent);
+        newTask.setHasBeenVisible(true);
+        oldTask.setHasBeenVisible(false);
+        final ActivityRecord closing = createActivityRecordInTask(oldTask);
+        final ActivityRecord opening = createActivityRecordInTask(newNestedTask);
+        final ActivityRecord opening2 = createActivityRecordInTask(newNestedTask2);
+        closing.setVisible(true);
+        closing.mVisibleRequested = false;
+        opening.setVisible(false);
+        opening.mVisibleRequested = true;
+        opening2.setVisible(false);
+        opening2.mVisibleRequested = true;
+        ArrayMap<WindowContainer, Transition.ChangeInfo> participants = new ArrayMap<>();
+
+        int transitType = TRANSIT_TASK_OPEN;
+
+        // Check full promotion from leaf
+        participants.put(oldTask, new Transition.ChangeInfo());
+        participants.put(opening, new Transition.ChangeInfo());
+        participants.put(opening2, new Transition.ChangeInfo());
+        TransitionInfo info =
+                Transition.calculateTransitionInfo(transitType, participants);
+        assertEquals(2, info.getChanges().size());
+        assertEquals(transitType, info.getType());
+        assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
+        assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
+
+        // Check that unchanging but visible descendant of sibling prevents promotion
+        participants.remove(opening2);
+        info = Transition.calculateTransitionInfo(transitType, participants);
+        assertEquals(2, info.getChanges().size());
+        assertNotNull(info.getChange(newNestedTask.mRemoteToken.toWindowContainerToken()));
+        assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
+    }
+
+    @Test
+    public void testCreateInfo_DisplayArea() {
+        final Task showTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
+                ACTIVITY_TYPE_STANDARD, mDisplayContent);
+        final Task showNestedTask = createTaskInStack(showTask, 0);
+        final Task showTask2 = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
+                ACTIVITY_TYPE_STANDARD, mDisplayContent);
+        final DisplayArea tda = showTask.getDisplayArea();
+        showTask.setHasBeenVisible(true);
+        showTask2.setHasBeenVisible(true);
+        final ActivityRecord showing = createActivityRecordInTask(showNestedTask);
+        final ActivityRecord showing2 = createActivityRecordInTask(showTask2);
+        showing.setVisible(false);
+        showing.mVisibleRequested = true;
+        showing2.setVisible(false);
+        showing2.mVisibleRequested = true;
+        ArrayMap<WindowContainer, Transition.ChangeInfo> participants = new ArrayMap<>();
+
+        int transitType = TRANSIT_TASK_OPEN;
+
+        // Check promotion to DisplayArea
+        participants.put(showing, new Transition.ChangeInfo());
+        participants.put(showing2, new Transition.ChangeInfo());
+        TransitionInfo info =
+                Transition.calculateTransitionInfo(transitType, participants);
+        assertEquals(1, info.getChanges().size());
+        assertEquals(transitType, info.getType());
+        assertNotNull(info.getChange(tda.mRemoteToken.toWindowContainerToken()));
+
+        ITaskOrganizer mockOrg = mock(ITaskOrganizer.class);
+        // Check that organized tasks get reported even if not top
+        showTask.mTaskOrganizer = mockOrg;
+        info = Transition.calculateTransitionInfo(transitType, participants);
+        assertEquals(2, info.getChanges().size());
+        assertNotNull(info.getChange(tda.mRemoteToken.toWindowContainerToken()));
+        assertNotNull(info.getChange(showTask.mRemoteToken.toWindowContainerToken()));
+        // Even if DisplayArea explicitly participating
+        participants.put(tda, new Transition.ChangeInfo());
+        info = Transition.calculateTransitionInfo(transitType, participants);
+        assertEquals(2, info.getChanges().size());
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 986807e..6237be0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -265,6 +265,11 @@
         return activity;
     }
 
+    /** Creates an {@link ActivityRecord} and adds it to the specified {@link Task}. */
+    static ActivityRecord createActivityRecordInTask(Task task) {
+        return createActivityRecordInTask(task.getDisplayContent(), task);
+    }
+
     static ActivityRecord createTestActivityRecord(DisplayContent dc) {
         final ActivityRecord activity = new ActivityBuilder(dc.mWmService.mAtmService).build();
         postCreateActivitySetup(activity, dc);
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index 4a3561b..866e171 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -49,12 +49,11 @@
 public final class PhoneAccount implements Parcelable {
 
     /**
-     * String extra which determines the order in which {@link PhoneAccount}s are sorted
+     * Integer extra which determines the order in which {@link PhoneAccount}s are sorted
      *
      * This is an extras key set via {@link Builder#setExtras} which determines the order in which
      * {@link PhoneAccount}s from the same {@link ConnectionService} are sorted. The accounts
-     * are sorted by this key via standard lexicographical order, (as implemented in
-     * {@link String#compareTo}), and this ordering is used to
+     * are sorted in ascending order by this key, and this ordering is used to
      * determine priority when a call can be placed via multiple accounts.
      *
      * When multiple {@link PhoneAccount}s are supplied with the same sort order key, no ordering is
diff --git a/telephony/api/system-current.txt b/telephony/api/system-current.txt
index 72a739c..69caccb 100644
--- a/telephony/api/system-current.txt
+++ b/telephony/api/system-current.txt
@@ -35,16 +35,12 @@
     method public int getTimeoutSeconds();
     method public boolean isEnabled();
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallForwardingInfo> CREATOR;
-    field public static final int ERROR_FDN_CHECK_FAILURE = 2; // 0x2
-    field public static final int ERROR_NOT_SUPPORTED = 3; // 0x3
-    field public static final int ERROR_UNKNOWN = 1; // 0x1
     field public static final int REASON_ALL = 4; // 0x4
     field public static final int REASON_ALL_CONDITIONAL = 5; // 0x5
     field public static final int REASON_BUSY = 1; // 0x1
     field public static final int REASON_NOT_REACHABLE = 3; // 0x3
     field public static final int REASON_NO_REPLY = 2; // 0x2
     field public static final int REASON_UNCONDITIONAL = 0; // 0x0
-    field public static final int SUCCESS = 0; // 0x0
   }
 
   public final class CallQuality implements android.os.Parcelable {
@@ -755,7 +751,7 @@
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setAllowedNetworkTypes(long);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallForwarding(@NonNull android.telephony.CallForwardingInfo, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallWaitingStatus(boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallWaitingEnabled(boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCarrierDataEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setCarrierRestrictionRules(@NonNull android.telephony.CarrierRestrictionRules);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataActivationState(int);
@@ -854,6 +850,10 @@
   public static interface TelephonyManager.CallForwardingInfoCallback {
     method public void onCallForwardingInfoAvailable(@NonNull android.telephony.CallForwardingInfo);
     method public void onError(int);
+    field public static final int RESULT_ERROR_FDN_CHECK_FAILURE = 2; // 0x2
+    field public static final int RESULT_ERROR_NOT_SUPPORTED = 3; // 0x3
+    field public static final int RESULT_ERROR_UNKNOWN = 1; // 0x1
+    field public static final int RESULT_SUCCESS = 0; // 0x0
   }
 
   public final class UiccAccessRule implements android.os.Parcelable {
diff --git a/telephony/java/android/telephony/CallForwardingInfo.java b/telephony/java/android/telephony/CallForwardingInfo.java
index 2106f7f..6ae6d00 100644
--- a/telephony/java/android/telephony/CallForwardingInfo.java
+++ b/telephony/java/android/telephony/CallForwardingInfo.java
@@ -39,27 +39,6 @@
     private static final String TAG = "CallForwardingInfo";
 
     /**
-     * Indicates that the operation was successful.
-     */
-    public static final int SUCCESS = 0;
-
-    /**
-     * Indicates that setting or retrieving the call forwarding info failed with an unknown error.
-     */
-    public static final int ERROR_UNKNOWN = 1;
-
-    /**
-     * Indicates that call forwarding is not enabled because the recipient is not on a
-     * Fixed Dialing Number (FDN) list.
-     */
-    public static final int ERROR_FDN_CHECK_FAILURE = 2;
-
-    /**
-     * Indicates that call forwarding is not supported on the network at this time.
-     */
-    public static final int ERROR_NOT_SUPPORTED = 3;
-
-    /**
      * Indicates that call forwarding reason is "unconditional".
      * Reference: 3GPP TS 27.007 version 10.3.0 Release 10 - 7.11 Call forwarding number
      *            and conditions +CCFC
@@ -104,19 +83,6 @@
     public static final int REASON_ALL_CONDITIONAL = 5;
 
     /**
-     * Call forwarding errors
-     * @hide
-     */
-    @IntDef(prefix = { "ERROR_" }, value = {
-            ERROR_UNKNOWN,
-            ERROR_NOT_SUPPORTED,
-            ERROR_FDN_CHECK_FAILURE
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface CallForwardingError{
-    }
-
-    /**
      * Call forwarding reason types
      * @hide
      */
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 7a0e1cd..c60b514 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -85,6 +85,8 @@
 import android.telephony.emergency.EmergencyNumber.EmergencyServiceCategories;
 import android.telephony.ims.ImsMmTelManager;
 import android.telephony.ims.aidl.IImsConfig;
+import android.telephony.ims.aidl.IImsMmTelFeature;
+import android.telephony.ims.aidl.IImsRcsFeature;
 import android.telephony.ims.aidl.IImsRegistration;
 import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
@@ -92,6 +94,7 @@
 import android.util.Log;
 import android.util.Pair;
 
+import com.android.ims.internal.IImsServiceFeatureCallback;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.CellNetworkScanResult;
@@ -7381,6 +7384,80 @@
     }
 
     /**
+     * Returns the {@link IImsMmTelFeature} that corresponds to the given slot Id and MMTel
+     * feature or {@link null} if the service is not available. If an MMTelFeature is available, the
+     * {@link IImsServiceFeatureCallback} callback is registered as a listener for feature updates.
+     * @param slotIndex The SIM slot that we are requesting the {@link IImsMmTelFeature} for.
+     * @param callback Listener that will send updates to ImsManager when there are updates to
+     * ImsServiceController.
+     * @return {@link IImsMmTelFeature} interface for the feature specified or {@code null} if
+     * it is unavailable.
+     * @hide
+     */
+    public @Nullable IImsMmTelFeature getImsMmTelFeatureAndListen(int slotIndex,
+            IImsServiceFeatureCallback callback) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.getMmTelFeatureAndListen(slotIndex, callback);
+            }
+        } catch (RemoteException e) {
+            Rlog.e(TAG, "getImsMmTelFeatureAndListen, RemoteException: "
+                    + e.getMessage());
+        }
+        return null;
+    }
+
+    /**
+     * Returns the {@link IImsRcsFeature} that corresponds to the given slot Id and RCS
+     * feature for emergency calling or {@link null} if the service is not available. If an
+     * RcsFeature is available, the {@link IImsServiceFeatureCallback} callback is registered as a
+     * listener for feature updates.
+     * @param slotIndex The SIM slot that we are requesting the {@link IImsRcsFeature} for.
+     * @param callback Listener that will send updates to ImsManager when there are updates to
+     * ImsServiceController.
+     * @return {@link IImsRcsFeature} interface for the feature specified or {@code null} if
+     * it is unavailable.
+     * @hide
+     */
+    public @Nullable IImsRcsFeature getImsRcsFeatureAndListen(int slotIndex,
+            IImsServiceFeatureCallback callback) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.getRcsFeatureAndListen(slotIndex, callback);
+            }
+        } catch (RemoteException e) {
+            Rlog.e(TAG, "getImsRcsFeatureAndListen, RemoteException: "
+                    + e.getMessage());
+        }
+        return null;
+    }
+
+    /**
+     * Unregister a IImsServiceFeatureCallback previously associated with an ImsFeature through
+     * {@link #getImsMmTelFeatureAndListen(int, IImsServiceFeatureCallback)} or
+     * {@link #getImsRcsFeatureAndListen(int, IImsServiceFeatureCallback)}.
+     * @param slotIndex The SIM slot associated with the callback.
+     * @param featureType The {@link android.telephony.ims.feature.ImsFeature.FeatureType}
+     *                    associated with the callback.
+     * @param callback The callback to be unregistered.
+     * @hide
+     */
+    public void unregisterImsFeatureCallback(int slotIndex, int featureType,
+            IImsServiceFeatureCallback callback) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                telephony.unregisterImsFeatureCallback(slotIndex, featureType, callback);
+            }
+        } catch (RemoteException e) {
+            Rlog.e(TAG, "unregisterImsFeatureCallback, RemoteException: "
+                    + e.getMessage());
+        }
+    }
+
+    /**
      * @return the {@IImsRegistration} interface that corresponds with the slot index and feature.
      * @param slotIndex The SIM slot corresponding to the ImsService ImsRegistration is active for.
      * @param feature An integer indicating the feature that we wish to get the ImsRegistration for.
@@ -12955,6 +13032,40 @@
     @SystemApi
     public interface CallForwardingInfoCallback {
         /**
+         * Indicates that the operation was successful.
+         */
+        int RESULT_SUCCESS = 0;
+
+        /**
+         * Indicates that setting or retrieving the call forwarding info failed with an unknown
+         * error.
+         */
+        int RESULT_ERROR_UNKNOWN = 1;
+
+        /**
+         * Indicates that call forwarding is not enabled because the recipient is not on a
+         * Fixed Dialing Number (FDN) list.
+         */
+        int RESULT_ERROR_FDN_CHECK_FAILURE = 2;
+
+        /**
+         * Indicates that call forwarding is not supported on the network at this time.
+         */
+        int RESULT_ERROR_NOT_SUPPORTED = 3;
+
+        /**
+         * Call forwarding errors
+         * @hide
+         */
+        @IntDef(prefix = { "RESULT_ERROR_" }, value = {
+                RESULT_ERROR_UNKNOWN,
+                RESULT_ERROR_NOT_SUPPORTED,
+                RESULT_ERROR_FDN_CHECK_FAILURE
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        @interface CallForwardingError{
+        }
+        /**
          * Called when the call forwarding info is successfully retrieved from the network.
          * @param info information about how calls are forwarded
          */
@@ -12964,7 +13075,7 @@
          * Called when there was an error retrieving the call forwarding information.
          * @param error
          */
-        void onError(@CallForwardingInfo.CallForwardingError int error);
+        void onError(@CallForwardingError int error);
     }
 
     /**
@@ -13037,9 +13148,9 @@
      * @param executor The executor on which the listener will be called. Must be non-null if
      *                 {@code listener} is non-null.
      * @param resultListener Asynchronous listener that'll be called when the operation completes.
-     *                      Called with {@link CallForwardingInfo#SUCCESS} if the operation
-     *                      succeeded and an error code from {@link CallForwardingInfo}
-     *                      if it failed.
+     *                      Called with {@link CallForwardingInfoCallback#RESULT_SUCCESS} if the
+     *                      operation succeeded and an error code from
+     *                      {@link CallForwardingInfoCallback} it failed.
      *
      * @throws IllegalArgumentException if any of the following are true for the parameter
      * callForwardingInfo:
@@ -13065,7 +13176,8 @@
     @SystemApi
     public void setCallForwarding(@NonNull CallForwardingInfo callForwardingInfo,
             @Nullable @CallbackExecutor Executor executor,
-            @Nullable @CallForwardingInfo.CallForwardingError Consumer<Integer> resultListener) {
+            @Nullable @CallForwardingInfoCallback.CallForwardingError
+                    Consumer<Integer> resultListener) {
         if (callForwardingInfo == null) {
             throw new IllegalArgumentException("callForwardingInfo is null");
         }
@@ -13221,7 +13333,7 @@
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-    public void setCallWaitingStatus(boolean enabled, @Nullable Executor executor,
+    public void setCallWaitingEnabled(boolean enabled, @Nullable Executor executor,
             @Nullable Consumer<Integer> resultListener) {
         if (resultListener != null) {
             Objects.requireNonNull(executor);
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index 39859b1..579200e 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -69,6 +69,7 @@
 
     /** {@hide} */
     @IntDef(prefix = "HANDOVER_FAILURE_MODE_", value = {
+            HANDOVER_FAILURE_MODE_UNKNOWN,
             HANDOVER_FAILURE_MODE_LEGACY,
             HANDOVER_FAILURE_MODE_DO_FALLBACK,
             HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER,
diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java
index ee2fce7..f6c14e6 100644
--- a/telephony/java/android/telephony/ims/ImsMmTelManager.java
+++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java
@@ -59,7 +59,6 @@
  * manager.
  */
 public class ImsMmTelManager implements RegistrationManager {
-    private static final String TAG = "ImsMmTelManager";
 
     /**
      * @hide
@@ -810,7 +809,7 @@
         }
 
         try {
-            iTelephony.isMmTelCapabilitySupported(mSubId, new IIntegerConsumer.Stub() {
+            getITelephony().isMmTelCapabilitySupported(mSubId, new IIntegerConsumer.Stub() {
                 @Override
                 public void accept(int result) {
                     executor.execute(() -> callback.accept(result == 1));
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index 8a05bdf..da7311c 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -16,7 +16,6 @@
 
 package android.telephony.ims;
 
-import android.annotation.LongDef;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.app.Service;
@@ -42,11 +41,6 @@
 import com.android.ims.internal.IImsFeatureStatusCallback;
 import com.android.internal.annotations.VisibleForTesting;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.HashMap;
-import java.util.Map;
-
 /**
  * Main ImsService implementation, which binds via the Telephony ImsResolver. Services that extend
  * ImsService must register the service in their AndroidManifest to be detected by the framework.
@@ -104,32 +98,6 @@
     private static final String LOG_TAG = "ImsService";
 
     /**
-     * This ImsService supports the capability to place emergency calls over MMTEL.
-     * @hide This is encoded into the {@link ImsFeature#FEATURE_EMERGENCY_MMTEL}, but we will be
-     * adding other capabilities in a central location, so track this capability here as well.
-     */
-    public static final long CAPABILITY_EMERGENCY_OVER_MMTEL = 1 << 0;
-
-    /**
-     * @hide
-     */
-    @LongDef(flag = true,
-            prefix = "CAPABILITY_",
-            value = {
-                    CAPABILITY_EMERGENCY_OVER_MMTEL
-            })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ImsServiceCapability {}
-
-    /**
-     * Used for logging purposes, see {@link #getCapabilitiesString(long)}
-     * @hide
-     */
-    private static final Map<Long, String> CAPABILITIES_LOG_MAP = new HashMap<Long, String>() {{
-            put(CAPABILITY_EMERGENCY_OVER_MMTEL, "EMERGENCY_OVER_MMTEL");
-        }};
-
-    /**
      * The intent that must be defined as an intent-filter in the AndroidManifest of the ImsService.
      * @hide
      */
@@ -441,30 +409,4 @@
     public ImsRegistrationImplBase getRegistration(int slotId) {
         return new ImsRegistrationImplBase();
     }
-
-    /**
-     * @return A string representation of the ImsService capabilties for logging.
-     * @hide
-     */
-    public static String getCapabilitiesString(@ImsServiceCapability long caps) {
-        StringBuffer result = new StringBuffer();
-        result.append("capabilities={ ");
-        // filter incrementally fills 0s from  left to right. This is used to keep filtering out
-        // more bits in the long until the remaining leftmost bits are all zero.
-        long filter = 0xFFFFFFFFFFFFFFFFL;
-        // position of iterator to potentially print capability.
-        long i = 0;
-        while ((caps & filter) != 0 && i <= 63) {
-            long bitToCheck = (1L << i);
-            if ((caps & bitToCheck) != 0) {
-                result.append(CAPABILITIES_LOG_MAP.getOrDefault(bitToCheck, bitToCheck + "?"));
-                result.append(" ");
-            }
-            // shift left by one and fill in another 1 on the leftmost bit.
-            filter <<= 1;
-            i++;
-        }
-        result.append("}");
-        return result.toString();
-    }
 }
\ No newline at end of file
diff --git a/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl
index 7bbe30a..5246470 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl
@@ -27,8 +27,11 @@
  * See MmTelFeature#Listener for more information.
  * {@hide}
  */
-oneway interface IImsMmTelListener {
+ // This interface is not considered oneway because we need to ensure that these operations are
+ // processed by telephony before the control flow returns to the ImsService to perform
+ // operations on the IImsCallSession.
+interface IImsMmTelListener {
     void onIncomingCall(IImsCallSession c, in Bundle extras);
     void onRejectedCall(in ImsCallProfile callProfile, in ImsReasonInfo reason);
-    void onVoiceMessageCountUpdate(int count);
+    oneway void onVoiceMessageCountUpdate(int count);
 }
diff --git a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
index d012703b..9e46142 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
@@ -22,7 +22,6 @@
 import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
 import android.telephony.ims.aidl.IImsRegistrationCallback;
 
-import com.android.ims.internal.IImsServiceFeatureCallback;
 import com.android.internal.telephony.IIntegerConsumer;
 
 /**
@@ -51,9 +50,4 @@
     void setUceSettingEnabled(int subId, boolean isEnabled);
     void registerUcePublishStateCallback(int subId, IRcsUcePublishStateCallback c);
     void unregisterUcePublishStateCallback(int subId, IRcsUcePublishStateCallback c);
-
-    // Internal commands that should not be made public
-    void registerRcsFeatureCallback(int slotId, in IImsServiceFeatureCallback callback,
-            boolean oneShot);
-    void unregisterImsFeatureCallback(in IImsServiceFeatureCallback callback);
 }
diff --git a/telephony/java/com/android/ims/ImsFeatureContainer.aidl b/telephony/java/com/android/ims/ImsFeatureContainer.aidl
deleted file mode 100644
index 9706f20..0000000
--- a/telephony/java/com/android/ims/ImsFeatureContainer.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2020 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.ims;
-
-parcelable ImsFeatureContainer;
\ No newline at end of file
diff --git a/telephony/java/com/android/ims/ImsFeatureContainer.java b/telephony/java/com/android/ims/ImsFeatureContainer.java
deleted file mode 100644
index b259679..0000000
--- a/telephony/java/com/android/ims/ImsFeatureContainer.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2020 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.ims;
-
-import android.annotation.NonNull;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.telephony.ims.ImsService;
-import android.telephony.ims.aidl.IImsConfig;
-import android.telephony.ims.aidl.IImsRegistration;
-import android.telephony.ims.feature.ImsFeature;
-
-import java.util.Objects;
-
-/**
- * Contains an IBinder linking to the appropriate ImsFeature as well as the associated
- * interfaces.
- * @hide
- */
-public final class ImsFeatureContainer implements Parcelable {
-    /**
-     * ImsFeature that is being tracked.
-     */
-    public final IBinder imsFeature;
-
-    /**
-     * IImsConfig interface that should be associated with the ImsFeature.
-     */
-    public final android.telephony.ims.aidl.IImsConfig imsConfig;
-
-    /**
-     * IImsRegistration interface that should be associated with this ImsFeature.
-     */
-    public final IImsRegistration imsRegistration;
-
-    /**
-     * State of the feature that is being tracked.
-     */
-    private @ImsFeature.ImsState int mState = ImsFeature.STATE_UNAVAILABLE;
-
-    /**
-     * Capabilities of this ImsService.
-     */
-    private @ImsService.ImsServiceCapability long mCapabilities;
-    /**
-     * Contains the ImsFeature IBinder as well as the ImsService interfaces associated with
-     * that feature.
-     * @param iFace IBinder connection to the ImsFeature.
-     * @param iConfig IImsConfig interface associated with the ImsFeature.
-     * @param iReg IImsRegistration interface associated with the ImsFeature
-     * @param initialCaps The initial capabilities that the ImsService supports.
-     */
-    public ImsFeatureContainer(@NonNull IBinder iFace, @NonNull IImsConfig iConfig,
-            @NonNull IImsRegistration iReg, long initialCaps) {
-        imsFeature = iFace;
-        imsConfig = iConfig;
-        imsRegistration = iReg;
-        mCapabilities = initialCaps;
-    }
-
-    /**
-     * Create an ImsFeatureContainer from a Parcel.
-     */
-    private ImsFeatureContainer(Parcel in) {
-        imsFeature = in.readStrongBinder();
-        imsConfig = IImsConfig.Stub.asInterface(in.readStrongBinder());
-        imsRegistration = IImsRegistration.Stub.asInterface(in.readStrongBinder());
-        mState = in.readInt();
-        mCapabilities = in.readLong();
-    }
-
-    /**
-     * @return the capabilties that are associated with the ImsService that this ImsFeature
-     * belongs to.
-     */
-    public @ImsService.ImsServiceCapability long getCapabilities() {
-        return mCapabilities;
-    }
-
-    /**
-     * Update the capabilities that are associated with the ImsService that this ImsFeature
-     * belongs to.
-     */
-    public void setCapabilities(@ImsService.ImsServiceCapability long caps) {
-        mCapabilities = caps;
-    }
-
-    /**
-     * @return The state of the ImsFeature.
-     */
-    public @ImsFeature.ImsState int getState() {
-        return mState;
-    }
-
-    /**
-     * Set the state that is associated with the ImsService that this ImsFeature
-     * belongs to.
-     */
-    public void setState(@ImsFeature.ImsState int state) {
-        mState = state;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        ImsFeatureContainer that = (ImsFeatureContainer) o;
-        return imsFeature.equals(that.imsFeature) &&
-                imsConfig.equals(that.imsConfig) &&
-                imsRegistration.equals(that.imsRegistration) &&
-                mState == that.getState() &&
-                mCapabilities == that.getCapabilities();
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(imsFeature, imsConfig, imsRegistration, mState, mCapabilities);
-    }
-
-    @Override
-    public String toString() {
-        return "FeatureContainer{" +
-                "imsFeature=" + imsFeature +
-                ", imsConfig=" + imsConfig +
-                ", imsRegistration=" + imsRegistration +
-                ", state=" + ImsFeature.STATE_LOG_MAP.get(mState) +
-                ", capabilities = " + ImsService.getCapabilitiesString(mCapabilities) +
-                '}';
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeStrongBinder(imsFeature);
-        dest.writeStrongInterface(imsConfig);
-        dest.writeStrongInterface(imsRegistration);
-        dest.writeInt(mState);
-        dest.writeLong(mCapabilities);
-    }
-
-
-    public static final Creator<ImsFeatureContainer> CREATOR = new Creator<ImsFeatureContainer>() {
-        @Override
-        public ImsFeatureContainer createFromParcel(Parcel source) {
-            return new ImsFeatureContainer(source);
-        }
-
-        @Override
-        public ImsFeatureContainer[] newArray(int size) {
-            return new ImsFeatureContainer[size];
-        }
-    };
-}
diff --git a/telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl b/telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl
index f5f67bd..9a9cf53 100644
--- a/telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl
+++ b/telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl
@@ -16,18 +16,13 @@
 
 package com.android.ims.internal;
 
-import com.android.ims.ImsFeatureContainer;
 /**
- *  Interface from ImsResolver to FeatureConnections.
- * Callback to FeatureConnections when a feature's status changes.
+ *  Interface from ImsResolver to ImsServiceProxy in ImsManager.
+ * Callback to ImsManager when a feature changes in the ImsServiceController.
  * {@hide}
  */
 oneway interface IImsServiceFeatureCallback {
-    void imsFeatureCreated(in ImsFeatureContainer feature);
-    // Reason defined in FeatureConnector.UnavailableReason
-    void imsFeatureRemoved(int reason);
-    // Status defined in ImsFeature.ImsState.
-    void imsStatusChanged(int status);
-    //Capabilities defined in ImsService.ImsServiceCapability
-    void updateCapabilities(long capabilities);
+    void imsFeatureCreated(int slotId, int feature);
+    void imsFeatureRemoved(int slotId, int feature);
+    void imsStatusChanged(int slotId, int feature, int status);
 }
\ No newline at end of file
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index ef5078d..02a74ba 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -829,15 +829,22 @@
      *  as well as registering the MmTelFeature for callbacks using the IImsServiceFeatureCallback
      *  interface.
      */
-    void registerMmTelFeatureCallback(int slotId, in IImsServiceFeatureCallback callback,
-            boolean oneShot);
+    IImsMmTelFeature getMmTelFeatureAndListen(int slotId, in IImsServiceFeatureCallback callback);
+
+    /**
+     *  Get IImsRcsFeature binder from ImsResolver that corresponds to the subId and RCS feature
+     *  as well as registering the RcsFeature for callbacks using the IImsServiceFeatureCallback
+     *  interface.
+     */
+    IImsRcsFeature getRcsFeatureAndListen(int slotId, in IImsServiceFeatureCallback callback);
 
     /**
      * Unregister a callback that was previously registered through
-     * {@link #registerMmTelFeatureCallback}. This should always be called when the callback is no
-     * longer being used.
+     * {@link #getMmTelFeatureAndListen} or {@link #getRcsFeatureAndListen}. This should always be
+     * called when the callback is no longer being used.
      */
-    void unregisterImsFeatureCallback(in IImsServiceFeatureCallback callback);
+    void unregisterImsFeatureCallback(int slotId, int featureType,
+            in IImsServiceFeatureCallback callback);
 
     /**
     * Returns the IImsRegistration associated with the slot and feature specified.
diff --git a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt
index 0f62c4f..e9227e94 100644
--- a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt
+++ b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt
@@ -107,7 +107,10 @@
     fun ParcelFileDescriptor.text() = FileReader(fileDescriptor).readText()
 
     @After
-    fun resetIdentity() = uiAutomation.dropShellPermissionIdentity()
+    fun resetChangeIdAndIdentity() {
+        command("am compat reset $TEST_CHANGE_ID $TEST_PKG")
+        uiAutomation.dropShellPermissionIdentity()
+    }
 
     @Test
     fun execute() {
diff --git a/wifi/api/current.txt b/wifi/api/current.txt
index 1c297e7..6928618 100644
--- a/wifi/api/current.txt
+++ b/wifi/api/current.txt
@@ -332,6 +332,7 @@
     method public boolean isEasyConnectSupported();
     method public boolean isEnhancedOpenSupported();
     method public boolean isEnhancedPowerReportingSupported();
+    method public boolean isMultiStaConcurrencySupported();
     method public boolean isP2pSupported();
     method public boolean isPreferredNetworkOffloadSupported();
     method @Deprecated public boolean isScanAlwaysAvailable();
diff --git a/wifi/api/system-current.txt b/wifi/api/system-current.txt
index 0f71ab4..bf7003c 100644
--- a/wifi/api/system-current.txt
+++ b/wifi/api/system-current.txt
@@ -608,10 +608,12 @@
 
   public final class WifiNetworkSuggestion implements android.os.Parcelable {
     method @NonNull public android.net.wifi.WifiConfiguration getWifiConfiguration();
+    method public boolean isOemPaid();
   }
 
   public static final class WifiNetworkSuggestion.Builder {
     method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING) public android.net.wifi.WifiNetworkSuggestion.Builder setCarrierId(int);
+    method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setOemPaid(boolean);
   }
 
   public class WifiScanner {
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 0973d54..a4f802a 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -992,6 +992,16 @@
     public boolean trusted;
 
     /**
+     * Indicate whether the network is oem paid or not. Networks are considered oem paid
+     * if the corresponding connection is only available to system apps.
+     *
+     * This bit can only be used by suggestion network, see
+     * {@link WifiNetworkSuggestion.Builder#setOemPaid(boolean)}
+     * @hide
+     */
+    public boolean oemPaid;
+
+    /**
      * True if this Wifi configuration is created from a {@link WifiNetworkSuggestion},
      * false otherwise.
      *
@@ -2158,6 +2168,7 @@
         ephemeral = false;
         osu = false;
         trusted = true; // Networks are considered trusted by default.
+        oemPaid = false;
         fromWifiNetworkSuggestion = false;
         fromWifiNetworkSpecifier = false;
         meteredHint = false;
@@ -2278,11 +2289,12 @@
         if (this.ephemeral) sbuf.append(" ephemeral");
         if (this.osu) sbuf.append(" osu");
         if (this.trusted) sbuf.append(" trusted");
+        if (this.oemPaid) sbuf.append(" oemPaid");
         if (this.fromWifiNetworkSuggestion) sbuf.append(" fromWifiNetworkSuggestion");
         if (this.fromWifiNetworkSpecifier) sbuf.append(" fromWifiNetworkSpecifier");
         if (this.meteredHint) sbuf.append(" meteredHint");
         if (this.useExternalScores) sbuf.append(" useExternalScores");
-        if (this.validatedInternetAccess || this.ephemeral || this.trusted
+        if (this.validatedInternetAccess || this.ephemeral || this.trusted || this.oemPaid
                 || this.fromWifiNetworkSuggestion || this.fromWifiNetworkSpecifier
                 || this.meteredHint || this.useExternalScores) {
             sbuf.append("\n");
@@ -2828,6 +2840,7 @@
             ephemeral = source.ephemeral;
             osu = source.osu;
             trusted = source.trusted;
+            oemPaid = source.oemPaid;
             fromWifiNetworkSuggestion = source.fromWifiNetworkSuggestion;
             fromWifiNetworkSpecifier = source.fromWifiNetworkSpecifier;
             meteredHint = source.meteredHint;
@@ -2906,6 +2919,7 @@
         dest.writeInt(isLegacyPasspointConfig ? 1 : 0);
         dest.writeInt(ephemeral ? 1 : 0);
         dest.writeInt(trusted ? 1 : 0);
+        dest.writeInt(oemPaid ? 1 : 0);
         dest.writeInt(fromWifiNetworkSuggestion ? 1 : 0);
         dest.writeInt(fromWifiNetworkSpecifier ? 1 : 0);
         dest.writeInt(meteredHint ? 1 : 0);
@@ -2981,6 +2995,7 @@
                 config.isLegacyPasspointConfig = in.readInt() != 0;
                 config.ephemeral = in.readInt() != 0;
                 config.trusted = in.readInt() != 0;
+                config.oemPaid = in.readInt() != 0;
                 config.fromWifiNetworkSuggestion =  in.readInt() != 0;
                 config.fromWifiNetworkSpecifier =  in.readInt() != 0;
                 config.meteredHint = in.readInt() != 0;
diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index 5388367..fe5002e 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -159,6 +159,11 @@
     private boolean mTrusted;
 
     /**
+     * Whether the network is oem paid or not.
+     */
+    private boolean mOemPaid;
+
+    /**
      * OSU (Online Sign Up) AP for Passpoint R2.
      */
     private boolean mOsuAp;
@@ -358,6 +363,7 @@
             mMeteredHint = source.mMeteredHint;
             mEphemeral = source.mEphemeral;
             mTrusted = source.mTrusted;
+            mTrusted = source.mOemPaid;
             mRequestingPackageName =
                     source.mRequestingPackageName;
             mOsuAp = source.mOsuAp;
@@ -722,6 +728,16 @@
     }
 
     /** {@hide} */
+    public void setOemPaid(boolean oemPaid) {
+        mOemPaid = oemPaid;
+    }
+
+    /** {@hide} */
+    public boolean isOemPaid() {
+        return mOemPaid;
+    }
+
+    /** {@hide} */
     public void setOsuAp(boolean osuAp) {
         mOsuAp = osuAp;
     }
@@ -958,6 +974,7 @@
         dest.writeInt(mMeteredHint ? 1 : 0);
         dest.writeInt(mEphemeral ? 1 : 0);
         dest.writeInt(mTrusted ? 1 : 0);
+        dest.writeInt(mOemPaid ? 1 : 0);
         dest.writeInt(score);
         dest.writeLong(txSuccess);
         dest.writeDouble(mSuccessfulTxPacketsPerSecond);
@@ -1003,6 +1020,7 @@
                 info.mMeteredHint = in.readInt() != 0;
                 info.mEphemeral = in.readInt() != 0;
                 info.mTrusted = in.readInt() != 0;
+                info.mOemPaid = in.readInt() != 0;
                 info.score = in.readInt();
                 info.txSuccess = in.readLong();
                 info.mSuccessfulTxPacketsPerSecond = in.readDouble();
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index b4e4210..4cf5c14 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -44,6 +44,7 @@
 import android.net.wifi.hotspot2.OsuProvider;
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.net.wifi.hotspot2.ProvisioningCallback;
+import android.net.wifi.util.SdkLevelUtil;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
@@ -2480,6 +2481,18 @@
     }
 
     /**
+     * Query whether the device supports 2 or more concurrent stations (STA) or not.
+     *
+     * @return true if this device supports multiple STA concurrency, false otherwise.
+     */
+    public boolean isMultiStaConcurrencySupported() {
+        if (!SdkLevelUtil.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        return isFeatureSupported(WIFI_FEATURE_ADDITIONAL_STA);
+    }
+
+    /**
      * @deprecated Please use {@link android.content.pm.PackageManager#hasSystemFeature(String)}
      * with {@link android.content.pm.PackageManager#FEATURE_WIFI_RTT} and
      * {@link android.content.pm.PackageManager#FEATURE_WIFI_AWARE}.
@@ -2512,14 +2525,6 @@
     }
 
     /**
-     * @return true if this adapter supports multiple simultaneous connections
-     * @hide
-     */
-    public boolean isAdditionalStaSupported() {
-        return isFeatureSupported(WIFI_FEATURE_ADDITIONAL_STA);
-    }
-
-    /**
      * @return true if this adapter supports Tunnel Directed Link Setup
      */
     public boolean isTdlsSupported() {
diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
index e992c83..9162c5f 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
@@ -24,7 +24,9 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.net.MacAddress;
+import android.net.NetworkCapabilities;
 import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.util.SdkLevelUtil;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.TelephonyManager;
@@ -150,6 +152,11 @@
         private boolean mIsNetworkUntrusted;
 
         /**
+         * Whether this network will be brought up as OEM paid (OEM_PAID capability bit added).
+         */
+        private boolean mIsNetworkOemPaid;
+
+        /**
          * Whether this network will use enhanced MAC randomization.
          */
         private boolean mIsEnhancedMacRandomizationEnabled;
@@ -175,6 +182,7 @@
             mWapiPskPassphrase = null;
             mWapiEnterpriseConfig = null;
             mIsNetworkUntrusted = false;
+            mIsNetworkOemPaid = false;
             mPriorityGroup = 0;
             mIsEnhancedMacRandomizationEnabled = false;
         }
@@ -543,7 +551,7 @@
 
         /**
          * Specifies whether the system will bring up the network (if selected) as untrusted. An
-         * untrusted network has its {@link android.net.NetworkCapabilities#NET_CAPABILITY_TRUSTED}
+         * untrusted network has its {@link NetworkCapabilities#NET_CAPABILITY_TRUSTED}
          * capability removed. The Wi-Fi network selection process may use this information to
          * influence priority of the suggested network for Wi-Fi network selection (most likely to
          * reduce it). The connectivity service may use this information to influence the overall
@@ -562,6 +570,41 @@
             return this;
         }
 
+        /**
+         * Specifies whether the system will bring up the network (if selected) as OEM paid. An
+         * OEM paid network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PAID} capability
+         * added.
+         * Note:
+         * <li>The connectivity service may use this information to influence the overall
+         * network configuration of the device. This network is typically only available to system
+         * apps.
+         * <li>On devices which support only 1 concurrent connection (indicated via
+         * {@link WifiManager#isMultiStaConcurrencySupported()}, Wi-Fi network selection process may
+         * use this information to influence priority of the suggested network for Wi-Fi network
+         * selection (most likely to reduce it).
+         * <li>On devices which support more than 1 concurrent connections (indicated via
+         * {@link WifiManager#isMultiStaConcurrencySupported()}, these OEM paid networks will be
+         * brought up as a secondary concurrent connection (primary connection will be used
+         * for networks available to the user and all apps.
+         * <p>
+         * <li> An OEM paid network's credentials may not be shared with the user using
+         * {@link #setCredentialSharedWithUser(boolean)}.</li>
+         * <li> If not set, defaults to false (i.e. network is not OEM paid).</li>
+         *
+         * @param isOemPaid Boolean indicating whether the network should be brought up as OEM paid
+         *                  (if true) or not OEM paid (if false).
+         * @return Instance of {@link Builder} to enable chaining of the builder method.
+         * @hide
+         */
+        @SystemApi
+        public @NonNull Builder setOemPaid(boolean isOemPaid) {
+            if (!SdkLevelUtil.isAtLeastS()) {
+                throw new UnsupportedOperationException();
+            }
+            mIsNetworkOemPaid = isOemPaid;
+            return this;
+        }
+
         private void setSecurityParamsInWifiConfiguration(
                 @NonNull WifiConfiguration configuration) {
             if (!TextUtils.isEmpty(mWpa2PskPassphrase)) { // WPA-PSK network.
@@ -628,6 +671,7 @@
             wifiConfiguration.meteredOverride = mMeteredOverride;
             wifiConfiguration.carrierId = mCarrierId;
             wifiConfiguration.trusted = !mIsNetworkUntrusted;
+            wifiConfiguration.oemPaid = mIsNetworkOemPaid;
             wifiConfiguration.macRandomizationSetting = mIsEnhancedMacRandomizationEnabled
                     ? WifiConfiguration.RANDOMIZATION_ENHANCED
                     : WifiConfiguration.RANDOMIZATION_PERSISTENT;
@@ -659,6 +703,7 @@
             wifiConfiguration.priority = mPriority;
             wifiConfiguration.meteredOverride = mMeteredOverride;
             wifiConfiguration.trusted = !mIsNetworkUntrusted;
+            wifiConfiguration.oemPaid = mIsNetworkOemPaid;
             mPasspointConfiguration.setCarrierId(mCarrierId);
             mPasspointConfiguration.setMeteredOverride(wifiConfiguration.meteredOverride);
             wifiConfiguration.macRandomizationSetting = mIsEnhancedMacRandomizationEnabled
@@ -764,7 +809,15 @@
                 if (mIsSharedWithUserSet && mIsSharedWithUser) {
                     throw new IllegalStateException("Should not be both"
                             + "setCredentialSharedWithUser and +"
-                            + "setIsNetworkAsUntrusted to true");
+                            + "setUntrusted to true");
+                }
+                mIsSharedWithUser = false;
+            }
+            if (mIsNetworkOemPaid) {
+                if (mIsSharedWithUserSet && mIsSharedWithUser) {
+                    throw new IllegalStateException("Should not be both"
+                            + "setCredentialSharedWithUser and +"
+                            + "setOemPaid to true");
                 }
                 mIsSharedWithUser = false;
             }
@@ -931,6 +984,7 @@
                 .append(", isCredentialSharedWithUser=").append(isUserAllowedToManuallyConnect)
                 .append(", isInitialAutoJoinEnabled=").append(isInitialAutoJoinEnabled)
                 .append(", isUnTrusted=").append(!wifiConfiguration.trusted)
+                .append(", isOemPaid=").append(wifiConfiguration.oemPaid)
                 .append(", priorityGroup=").append(priorityGroup)
                 .append(" ]");
         return sb.toString();
@@ -1026,6 +1080,18 @@
     }
 
     /**
+     * @see Builder#setOemPaid(boolean)
+     * @hide
+     */
+    @SystemApi
+    public boolean isOemPaid() {
+        if (!SdkLevelUtil.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        return wifiConfiguration.oemPaid;
+    }
+
+    /**
      * Get the WifiEnterpriseConfig, or null if unset.
      * @see Builder#setWapiEnterpriseConfig(WifiEnterpriseConfig)
      * @see Builder#setWpa2EnterpriseConfig(WifiEnterpriseConfig)
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index d4b2051..6894ba0 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -68,6 +68,7 @@
         WifiConfiguration config = new WifiConfiguration();
         config.setPasspointManagementObjectTree(cookie);
         config.trusted = false;
+        config.oemPaid = true;
         config.updateIdentifier = "1234";
         config.fromWifiNetworkSpecifier = true;
         config.fromWifiNetworkSuggestion = true;
diff --git a/wifi/tests/src/android/net/wifi/WifiInfoTest.java b/wifi/tests/src/android/net/wifi/WifiInfoTest.java
index 311bbc4..06ae13a 100644
--- a/wifi/tests/src/android/net/wifi/WifiInfoTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiInfoTest.java
@@ -61,6 +61,7 @@
         writeWifiInfo.txBad = TEST_TX_BAD;
         writeWifiInfo.rxSuccess = TEST_RX_SUCCESS;
         writeWifiInfo.setTrusted(true);
+        writeWifiInfo.setOemPaid(true);
         writeWifiInfo.setOsuAp(true);
         writeWifiInfo.setFQDN(TEST_FQDN);
         writeWifiInfo.setProviderFriendlyName(TEST_PROVIDER_NAME);
@@ -81,6 +82,7 @@
         assertEquals(TEST_TX_BAD, readWifiInfo.txBad);
         assertEquals(TEST_RX_SUCCESS, readWifiInfo.rxSuccess);
         assertTrue(readWifiInfo.isTrusted());
+        assertTrue(readWifiInfo.isOemPaid());
         assertTrue(readWifiInfo.isOsuAp());
         assertTrue(readWifiInfo.isPasspointAp());
         assertEquals(TEST_PACKAGE_NAME, readWifiInfo.getRequestingPackageName());
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index e7f1916c..bda776d 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -33,6 +33,8 @@
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING;
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
+import static android.net.wifi.WifiManager.WIFI_FEATURE_ADDITIONAL_STA;
+import static android.net.wifi.WifiManager.WIFI_FEATURE_AP_STA;
 import static android.net.wifi.WifiManager.WIFI_FEATURE_DPP;
 import static android.net.wifi.WifiManager.WIFI_FEATURE_OWE;
 import static android.net.wifi.WifiManager.WIFI_FEATURE_P2P;
@@ -49,6 +51,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.any;
@@ -83,6 +86,7 @@
 import android.net.wifi.WifiManager.SuggestionConnectionStatusListener;
 import android.net.wifi.WifiManager.TrafficStateCallback;
 import android.net.wifi.WifiManager.WifiConnectedNetworkScorer;
+import android.net.wifi.util.SdkLevelUtil;
 import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerExecutor;
@@ -1708,6 +1712,34 @@
     }
 
     /**
+     * Test behavior of isStaApConcurrencySupported
+     */
+    @Test
+    public void testIsStaApConcurrencyOpenSupported() throws Exception {
+        when(mWifiService.getSupportedFeatures())
+                .thenReturn(new Long(WIFI_FEATURE_AP_STA));
+        assertTrue(mWifiManager.isStaApConcurrencySupported());
+        when(mWifiService.getSupportedFeatures())
+                .thenReturn(new Long(~WIFI_FEATURE_AP_STA));
+        assertFalse(mWifiManager.isStaApConcurrencySupported());
+    }
+
+    /**
+     * Test behavior of isMultiStaConcurrencySupported
+     */
+    @Test
+    public void testIsMultiStaConcurrencyOpenSupported() throws Exception {
+        assumeTrue(SdkLevelUtil.isAtLeastS());
+
+        when(mWifiService.getSupportedFeatures())
+                .thenReturn(new Long(WIFI_FEATURE_ADDITIONAL_STA));
+        assertTrue(mWifiManager.isMultiStaConcurrencySupported());
+        when(mWifiService.getSupportedFeatures())
+                .thenReturn(new Long(~WIFI_FEATURE_ADDITIONAL_STA));
+        assertFalse(mWifiManager.isMultiStaConcurrencySupported());
+    }
+
+    /**
      * Test behavior of {@link WifiManager#addNetwork(WifiConfiguration)}
      */
     @Test
@@ -1858,7 +1890,6 @@
         assertFalse(mWifiManager.isDeviceToDeviceRttSupported());
         assertFalse(mWifiManager.isDeviceToApRttSupported());
         assertFalse(mWifiManager.isPreferredNetworkOffloadSupported());
-        assertFalse(mWifiManager.isAdditionalStaSupported());
         assertFalse(mWifiManager.isTdlsSupported());
         assertFalse(mWifiManager.isOffChannelTdlsSupported());
         assertFalse(mWifiManager.isEnhancedPowerReportingSupported());
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
index 668d238..6e08ca4 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
@@ -22,10 +22,12 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import android.net.MacAddress;
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.net.wifi.hotspot2.PasspointTestUtils;
+import android.net.wifi.util.SdkLevelUtil;
 import android.os.Parcel;
 
 import androidx.test.filters.SmallTest;
@@ -192,6 +194,34 @@
 
     /**
      * Validate correctness of WifiNetworkSuggestion object created by
+     * {@link WifiNetworkSuggestion.Builder#build()} for OWE network.
+     */
+    @Test
+    public void testWifiNetworkSuggestionBuilderForOemPaidEnhancedOpenNetworkWithBssid() {
+        assumeTrue(SdkLevelUtil.isAtLeastS());
+
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setBssid(MacAddress.fromString(TEST_BSSID))
+                .setOemPaid(true)
+                .setIsEnhancedOpen(true)
+                .build();
+
+        assertEquals("\"" + TEST_SSID + "\"", suggestion.wifiConfiguration.SSID);
+        assertEquals(TEST_BSSID, suggestion.wifiConfiguration.BSSID);
+        assertTrue(suggestion.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.OWE));
+        assertNull(suggestion.wifiConfiguration.preSharedKey);
+        assertTrue(suggestion.wifiConfiguration.requirePmf);
+        assertTrue(suggestion.wifiConfiguration.oemPaid);
+        assertTrue(suggestion.isOemPaid());
+        assertFalse(suggestion.isUserAllowedToManuallyConnect);
+        assertTrue(suggestion.isInitialAutoJoinEnabled);
+        assertNull(suggestion.getEnterpriseConfig());
+    }
+
+    /**
+     * Validate correctness of WifiNetworkSuggestion object created by
      * {@link WifiNetworkSuggestion.Builder#build()} for SAE network.
      */
     @Test
@@ -1010,6 +1040,41 @@
     }
 
     /**
+     * Validate {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} set the
+     * correct value to the WifiConfiguration.
+     */
+    @Test
+    public void testSetIsNetworkAsOemPaid() {
+        assumeTrue(SdkLevelUtil.isAtLeastS());
+
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa2Passphrase(TEST_PRESHARED_KEY)
+                .setOemPaid(true)
+                .build();
+        assertTrue(suggestion.isOemPaid());
+        assertFalse(suggestion.isUserAllowedToManuallyConnect);
+    }
+
+    /**
+     * Validate {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} set the
+     * correct value to the WifiConfiguration.
+     * Also the {@link WifiNetworkSuggestion#isUserAllowedToManuallyConnect} should be false;
+     */
+    @Test
+    public void testSetIsNetworkAsOemPaidOnPasspointNetwork() {
+        assumeTrue(SdkLevelUtil.isAtLeastS());
+
+        PasspointConfiguration passpointConfiguration = PasspointTestUtils.createConfig();
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setPasspointConfig(passpointConfiguration)
+                .setOemPaid(true)
+                .build();
+        assertTrue(suggestion.isOemPaid());
+        assertFalse(suggestion.isUserAllowedToManuallyConnect);
+    }
+
+    /**
      * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
      * when set {@link WifiNetworkSuggestion.Builder#setUntrusted(boolean)} to true and
      * set {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} to true
@@ -1027,6 +1092,24 @@
 
     /**
      * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
+     * when set {@link WifiNetworkSuggestion.Builder#setOemPaid(boolean)} to true and
+     * set {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} to true
+     * together.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testSetCredentialSharedWithUserWithSetIsNetworkAsOemPaid() {
+        assumeTrue(SdkLevelUtil.isAtLeastS());
+
+        new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa2Passphrase(TEST_PRESHARED_KEY)
+                .setCredentialSharedWithUser(true)
+                .setOemPaid(true)
+                .build();
+    }
+
+    /**
+     * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
      * when set both {@link WifiNetworkSuggestion.Builder#setIsInitialAutojoinEnabled(boolean)}
      * and {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} (boolean)}
      * to false on a passpoint suggestion.