Add prioritized enableCarMode API in UiModeManager

Per design doc (go/android-car-mode-design), added new system API to
enable car mode and specify a priority for the calling app.
Also modified UiModeManager to pass the package name of the caller to
UiModeManagerService.

Bug: 136109592
Test: Added new unit tests and CTS tests.
Test: Added Telecom test app functionality to verify.
Change-Id: I2848039c9ea18ba93e7694e04c4e5dc70759daa3
Merged-In: I2848039c9ea18ba93e7694e04c4e5dc70759daa3
diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl
index f2c9f61..a3e0845 100644
--- a/core/java/android/app/IUiModeManager.aidl
+++ b/core/java/android/app/IUiModeManager.aidl
@@ -25,7 +25,7 @@
      * Enables the car mode. Only the system can do this.
      * @hide
      */
-    void enableCarMode(int flags);
+    void enableCarMode(int flags, int priority, String callingPackage);
 
     /**
      * Disables the car mode.
@@ -34,6 +34,12 @@
     void disableCarMode(int flags);
 
     /**
+     * Disables car mode (the original version is marked unsupported app usage so cannot be changed
+     * for the time being).
+     */
+    void disableCarModeByCallingPackage(int flags, String callingPackage);
+
+    /**
      * Return the current running mode.
      */
     int getCurrentModeType();
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 646f0dc..2cb0794 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -671,7 +671,7 @@
                 new CachedServiceFetcher<UiModeManager>() {
             @Override
             public UiModeManager createService(ContextImpl ctx) throws ServiceNotFoundException {
-                return new UiModeManager();
+                return new UiModeManager(ctx.getOuterContext());
             }});
 
         registerService(Context.USB_SERVICE, UsbManager.class,
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 46316e1..8324787 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -17,6 +17,10 @@
 package android.app;
 
 import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
@@ -68,6 +72,25 @@
      * of the broadcast to {@link Activity#RESULT_CANCELED}.
      */
     public static String ACTION_ENTER_CAR_MODE = "android.app.action.ENTER_CAR_MODE";
+
+    /**
+     * Broadcast sent when the device's UI has switched to car mode, either by being placed in a car
+     * dock or explicit action of the user.
+     * <p>
+     * In addition to the behavior for {@link #ACTION_ENTER_CAR_MODE}, this broadcast includes the
+     * package name of the app which requested to enter car mode in the
+     * {@link #EXTRA_CALLING_PACKAGE}.  If an app requested to enter car mode using
+     * {@link #enableCarMode(int, int)} and specified a priority this will be specified in the
+     * {@link #EXTRA_PRIORITY}.
+     *
+     * This is primarily intended to be received by other components of the Android OS.
+     * <p>
+     * Receiver requires permission: {@link android.Manifest.permission.HANDLE_CAR_MODE_CHANGES}
+     * @hide
+     */
+    @SystemApi
+    public static final String ACTION_ENTER_CAR_MODE_PRIORITIZED =
+            "android.app.action.ENTER_CAR_MODE_PRIORITIZED";
     
     /**
      * Broadcast sent when the device's UI has switch away from car mode back
@@ -75,6 +98,28 @@
      * when the user exits car mode.
      */
     public static String ACTION_EXIT_CAR_MODE = "android.app.action.EXIT_CAR_MODE";
+
+    /**
+     * Broadcast sent when the device's UI has switched away from car mode back to normal mode.
+     * Typically used by a car mode app, to dismiss itself when the user exits car mode.
+     * <p>
+     * In addition to the behavior for {@link #ACTION_EXIT_CAR_MODE}, this broadcast includes the
+     * package name of the app which requested to exit car mode in {@link #EXTRA_CALLING_PACKAGE}.
+     * If an app requested to enter car mode using {@link #enableCarMode(int, int)} and specified a
+     * priority this will be specified in the {@link #EXTRA_PRIORITY} when exiting car mode.
+     * <p>
+     * If {@link #DISABLE_CAR_MODE_ALL_PRIORITIES} is used when disabling car mode (i.e. this is
+     * initiated by the user via the persistent car mode notification), this broadcast is sent once
+     * for each priority level for which car mode is being disabled.
+     * <p>
+     * This is primarily intended to be received by other components of the Android OS.
+     * <p>
+     * Receiver requires permission: {@link android.Manifest.permission.HANDLE_CAR_MODE_CHANGES}
+     * @hide
+     */
+    @SystemApi
+    public static final String ACTION_EXIT_CAR_MODE_PRIORITIZED =
+            "android.app.action.EXIT_CAR_MODE_PRIORITIZED";
     
     /**
      * Broadcast sent when the device's UI has switched to desk mode,
@@ -97,6 +142,24 @@
      */
     public static String ACTION_EXIT_DESK_MODE = "android.app.action.EXIT_DESK_MODE";
 
+    /**
+     * String extra used with {@link #ACTION_ENTER_CAR_MODE_PRIORITIZED} and
+     * {@link #ACTION_EXIT_CAR_MODE_PRIORITIZED} to indicate the package name of the app which
+     * requested to enter or exit car mode.
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_CALLING_PACKAGE = "android.app.extra.CALLING_PACKAGE";
+
+    /**
+     * Integer extra used with {@link #ACTION_ENTER_CAR_MODE_PRIORITIZED} and
+     * {@link #ACTION_EXIT_CAR_MODE_PRIORITIZED} to indicate the priority level at which car mode
+     * is being disabled.
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_PRIORITY = "android.app.extra.PRIORITY";
+
     /** @hide */
     @IntDef(prefix = { "MODE_" }, value = {
             MODE_NIGHT_AUTO,
@@ -126,10 +189,21 @@
 
     private IUiModeManager mService;
 
+    /**
+     * Context required for getting the opPackageName of API caller; maybe be {@code null} if the
+     * old constructor marked with UnSupportedAppUsage is used.
+     */
+    private @Nullable Context mContext;
+
     @UnsupportedAppUsage
     /*package*/ UiModeManager() throws ServiceNotFoundException {
+        this(null /* context */);
+    }
+
+    /*package*/ UiModeManager(Context context) throws ServiceNotFoundException {
         mService = IUiModeManager.Stub.asInterface(
                 ServiceManager.getServiceOrThrow(Context.UI_MODE_SERVICE));
+        mContext = context;
     }
 
     /**
@@ -152,6 +226,14 @@
      */
     public static final int ENABLE_CAR_MODE_ALLOW_SLEEP = 0x0002;
 
+    /** @hide */
+    @IntDef(prefix = { "ENABLE_CAR_MODE_" }, value = {
+            ENABLE_CAR_MODE_GO_CAR_HOME,
+            ENABLE_CAR_MODE_ALLOW_SLEEP
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface EnableCarMode {}
+
     /**
      * Force device into car mode, like it had been placed in the car dock.
      * This will cause the device to switch to the car home UI as part of
@@ -159,9 +241,54 @@
      * @param flags Must be 0.
      */
     public void enableCarMode(int flags) {
+        enableCarMode(DEFAULT_PRIORITY, flags);
+    }
+
+    /**
+     * Force device into car mode, like it had been placed in the car dock.  This will cause the
+     * device to switch to the car home UI as part of the mode switch.
+     * <p>
+     * An app may request to enter car mode when the system is already in car mode.  The app may
+     * specify a "priority" when entering car mode.  The device will remain in car mode
+     * (i.e. {@link #getCurrentModeType()} is {@link Configuration#UI_MODE_TYPE_CAR}) as long as
+     * there is a priority level at which car mode have been enabled.  For example assume app A
+     * enters car mode at priority level 100, and then app B enters car mode at the default priority
+     * (0).  If app A exits car mode, the device will remain in car mode until app B exits car mode.
+     * <p>
+     * Specifying a priority level when entering car mode is important in cases where multiple apps
+     * on a device implement a car-mode {@link android.telecom.InCallService} (see
+     * {@link android.telecom.TelecomManager#METADATA_IN_CALL_SERVICE_CAR_MODE_UI}).  The
+     * {@link android.telecom.InCallService} associated with the highest priority app which entered
+     * car mode will be bound to by Telecom and provided with information about ongoing calls on
+     * the device.
+     * <p>
+     * System apps holding the required permission can enable car mode when the app determines the
+     * correct conditions exist for that app to be in car mode.  The device maker should ensure that
+     * where multiple apps exist on the device which can potentially enter car mode, appropriate
+     * priorities are used to ensure that calls delivered by the
+     * {@link android.telecom.InCallService} API are delivered to the highest priority app.
+     * If app A and app B can both potentially enable car mode, and it is desired that app B is the
+     * one which should receive call information, the priority for app B should be higher than the
+     * one for app A.
+     * <p>
+     * When an app uses a priority to enable car mode, they can disable car mode at the specified
+     * priority level using {@link #disableCarMode(int)}.  An app may only enable car mode at a
+     * single priority.
+     * <p>
+     * Public apps are assumed to enter/exit car mode at {@link #DEFAULT_PRIORITY}.
+     *
+     * @param priority The declared priority for the caller.
+     * @param flags Car mode flags.
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.ENTER_CAR_MODE_PRIORITIZED)
+    public void enableCarMode(@IntRange(from = 0) int priority, @EnableCarMode int flags) {
         if (mService != null) {
             try {
-                mService.enableCarMode(flags);
+                mService.enableCarMode(flags, priority,
+                        mContext == null ? null : mContext.getOpPackageName());
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -176,15 +303,44 @@
      * being in car mode).
      */
     public static final int DISABLE_CAR_MODE_GO_HOME = 0x0001;
+
+    /**
+     * Flag for use with {@link #disableCarMode(int)}: Disables car mode at ALL priority levels.
+     * Primarily intended for use from {@link com.android.internal.app.DisableCarModeActivity} to
+     * provide the user with a means to exit car mode at all priority levels.
+     * @hide
+     */
+    public static final int DISABLE_CAR_MODE_ALL_PRIORITIES = 0x0002;
+
+    /** @hide */
+    @IntDef(prefix = { "DISABLE_CAR_MODE_" }, value = {
+            DISABLE_CAR_MODE_GO_HOME
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DisableCarMode {}
+
+    /**
+     * The default priority used for entering car mode.
+     * <p>
+     * Callers of the {@link UiModeManager#enableCarMode(int)} priority will be assigned the
+     * default priority.
+     * <p>
+     * System apps can specify a priority other than the default priority when using
+     * {@link UiModeManager#enableCarMode(int, int)} to enable car mode.
+     * @hide
+     */
+    @SystemApi
+    public static final int DEFAULT_PRIORITY = 0;
     
     /**
      * Turn off special mode if currently in car mode.
-     * @param flags May be 0 or {@link #DISABLE_CAR_MODE_GO_HOME}.
+     * @param flags One of the disable car mode flags.
      */
-    public void disableCarMode(int flags) {
+    public void disableCarMode(@DisableCarMode int flags) {
         if (mService != null) {
             try {
-                mService.disableCarMode(flags);
+                mService.disableCarModeByCallingPackage(flags,
+                        mContext == null ? null : mContext.getOpPackageName());
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
diff --git a/core/java/com/android/internal/app/DisableCarModeActivity.java b/core/java/com/android/internal/app/DisableCarModeActivity.java
index 7943c61..d44312b 100644
--- a/core/java/com/android/internal/app/DisableCarModeActivity.java
+++ b/core/java/com/android/internal/app/DisableCarModeActivity.java
@@ -33,7 +33,9 @@
         try {
             IUiModeManager uiModeManager = IUiModeManager.Stub.asInterface(
                     ServiceManager.getService("uimode"));
-            uiModeManager.disableCarMode(UiModeManager.DISABLE_CAR_MODE_GO_HOME);
+            uiModeManager.disableCarModeByCallingPackage(UiModeManager.DISABLE_CAR_MODE_GO_HOME
+                            | UiModeManager.DISABLE_CAR_MODE_ALL_PRIORITIES,
+                    getOpPackageName());
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to disable car mode", e);
         }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1f20d7a..c889474 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -108,6 +108,8 @@
 
     <protected-broadcast android:name="android.app.action.ENTER_CAR_MODE" />
     <protected-broadcast android:name="android.app.action.EXIT_CAR_MODE" />
+    <protected-broadcast android:name="android.app.action.ENTER_CAR_MODE_PRIVILEGED" />
+    <protected-broadcast android:name="android.app.action.EXIT_CAR_MODE_PRIVILEGED" />
     <protected-broadcast android:name="android.app.action.ENTER_DESK_MODE" />
     <protected-broadcast android:name="android.app.action.EXIT_DESK_MODE" />
     <protected-broadcast android:name="android.app.action.NEXT_ALARM_CLOCK_CHANGED" />
@@ -4339,6 +4341,21 @@
          it will be ignored.
         @hide -->
     <permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE"
+      android:protectionLevel="signature|privileged" />
+
+    <!-- @SystemApi Allows entering or exiting car mode using a specified priority.
+        This permission is required to use UiModeManager while specifying a priority for the calling
+        app.  A device manufacturer uses this permission to prioritize the apps which can
+        potentially request to enter car-mode on a device to help establish the correct behavior
+        where multiple such apps are active at the same time.
+        @hide -->
+    <permission android:name="android.permission.ENTER_CAR_MODE_PRIORITIZED"
+                android:protectionLevel="signature|privileged" />
+
+    <!-- @SystemApi Required to receive ACTION_ENTER_CAR_MODE_PRIVILEGED or
+        ACTION_EXIT_CAR_MODE_PRIVILEGED.
+        @hide -->
+    <permission android:name="android.permission.HANDLE_CAR_MODE_CHANGES"
                 android:protectionLevel="signature|privileged" />
 
     <!-- The system process is explicitly the only one allowed to launch the