diff options
| author | 2019-09-30 15:12:24 -0700 | |
|---|---|---|
| committer | 2019-11-18 13:03:30 -0800 | |
| commit | daef668a9939fd7725970e268e7148e7fc081ba1 (patch) | |
| tree | 2c70436435a3e4d7b019ceff5c6330afe9637740 | |
| parent | 07a945d97bc49a57b7cc32abcceaadc3363a6e77 (diff) | |
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
| -rw-r--r-- | api/system-current.txt | 11 | ||||
| -rw-r--r-- | api/test-current.txt | 1 | ||||
| -rw-r--r-- | core/java/android/app/IUiModeManager.aidl | 8 | ||||
| -rw-r--r-- | core/java/android/app/SystemServiceRegistry.java | 2 | ||||
| -rw-r--r-- | core/java/android/app/UiModeManager.java | 164 | ||||
| -rw-r--r-- | core/java/com/android/internal/app/DisableCarModeActivity.java | 4 | ||||
| -rw-r--r-- | core/res/AndroidManifest.xml | 17 | ||||
| -rw-r--r-- | data/etc/privapp-permissions-platform.xml | 3 | ||||
| -rw-r--r-- | packages/Shell/AndroidManifest.xml | 3 | ||||
| -rw-r--r-- | services/core/java/com/android/server/UiModeManagerService.java | 212 |
10 files changed, 399 insertions, 26 deletions
diff --git a/api/system-current.txt b/api/system-current.txt index fd8fd8838440..532284dfae70 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -67,6 +67,7 @@ package android { field public static final String CRYPT_KEEPER = "android.permission.CRYPT_KEEPER"; field public static final String DEVICE_POWER = "android.permission.DEVICE_POWER"; field public static final String DISPATCH_PROVISIONING_MESSAGE = "android.permission.DISPATCH_PROVISIONING_MESSAGE"; + field public static final String ENTER_CAR_MODE_PRIORITIZED = "android.permission.ENTER_CAR_MODE_PRIORITIZED"; field public static final String FORCE_BACK = "android.permission.FORCE_BACK"; field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES"; field public static final String GET_APP_OPS_STATS = "android.permission.GET_APP_OPS_STATS"; @@ -76,6 +77,7 @@ package android { field public static final String GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS = "android.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS"; field public static final String GRANT_RUNTIME_PERMISSIONS = "android.permission.GRANT_RUNTIME_PERMISSIONS"; field public static final String GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS = "android.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS"; + field public static final String HANDLE_CAR_MODE_CHANGES = "android.permission.HANDLE_CAR_MODE_CHANGES"; field public static final String HARDWARE_TEST = "android.permission.HARDWARE_TEST"; field public static final String HDMI_CEC = "android.permission.HDMI_CEC"; field public static final String HIDE_NON_SYSTEM_OVERLAY_WINDOWS = "android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"; @@ -637,6 +639,15 @@ package android.app { method public boolean isStatusBarExpansionDisabled(); } + public class UiModeManager { + method @RequiresPermission(android.Manifest.permission.ENTER_CAR_MODE_PRIORITIZED) public void enableCarMode(@IntRange(from=0) int, int); + field public static final String ACTION_ENTER_CAR_MODE_PRIORITIZED = "android.app.action.ENTER_CAR_MODE_PRIORITIZED"; + field public static final String ACTION_EXIT_CAR_MODE_PRIORITIZED = "android.app.action.EXIT_CAR_MODE_PRIORITIZED"; + field public static final int DEFAULT_PRIORITY = 0; // 0x0 + field public static final String EXTRA_CALLING_PACKAGE = "android.app.extra.CALLING_PACKAGE"; + field public static final String EXTRA_PRIORITY = "android.app.extra.PRIORITY"; + } + public final class Vr2dDisplayProperties implements android.os.Parcelable { ctor public Vr2dDisplayProperties(int, int, int); method public int describeContents(); diff --git a/api/test-current.txt b/api/test-current.txt index 495e57af1990..bc6df1777837 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -420,6 +420,7 @@ package android.app { } public class UiModeManager { + method @RequiresPermission("android.permission.ENTER_CAR_MODE_PRIORITIZED") public void enableCarMode(@IntRange(from=0) int, int); method public boolean isNightModeLocked(); method public boolean isUiModeLocked(); } diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl index f2c9f615c03f..a3e0845af0ce 100644 --- a/core/java/android/app/IUiModeManager.aidl +++ b/core/java/android/app/IUiModeManager.aidl @@ -25,7 +25,7 @@ interface IUiModeManager { * 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 @@ interface IUiModeManager { 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 629c2bb19a7c..9895497e15eb 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -663,7 +663,7 @@ public final class SystemServiceRegistry { 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 46316e1a254b..83247875c8a5 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 @@ public class UiModeManager { * 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 @@ public class UiModeManager { * 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 class UiModeManager { */ 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 @@ public class UiModeManager { 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 class UiModeManager { */ 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 @@ public class UiModeManager { * @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 @@ public class UiModeManager { * 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 7943c613b357..d44312b716ba 100644 --- a/core/java/com/android/internal/app/DisableCarModeActivity.java +++ b/core/java/com/android/internal/app/DisableCarModeActivity.java @@ -33,7 +33,9 @@ public class DisableCarModeActivity extends Activity { 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 66e84dc16781..f554c6442e3f 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" /> @@ -4357,6 +4359,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 diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index a0215e185a4c..5ac9b58f1176 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -240,6 +240,7 @@ applications that come with the platform <permission name="android.permission.BIND_CONNECTION_SERVICE"/> <permission name="android.permission.BIND_INCALL_SERVICE"/> <permission name="android.permission.CALL_PRIVILEGED"/> + <permission name="android.permission.HANDLE_CAR_MODE_CHANGES"/> <permission name="android.permission.INTERACT_ACROSS_USERS"/> <permission name="android.permission.MANAGE_USERS"/> <permission name="android.permission.MANAGE_ROLE_HOLDERS"/> @@ -334,6 +335,8 @@ applications that come with the platform <permission name="android.permission.SYSTEM_CAMERA" /> <!-- Permission required to test ExplicitHealthCheckServiceImpl. --> <permission name="android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE"/> + <!-- Permission required for UiModeManager cts test. --> + <permission name="android.permission.ENTER_CAR_MODE_PRIORITIZED"/> </privapp-permissions> <privapp-permissions package="com.android.statementservice"> diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index c59f342d81e1..1a658f49d516 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -215,6 +215,9 @@ <!-- Permission required for CTS test - CrossProfileAppsHostSideTest --> <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/> + <!-- Permission requried for CTS test - UiModeManagerTest --> + <uses-permission android:name="android.permission.ENTER_CAR_MODE_PRIORITIZED"/> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" android:defaultToDeviceProtectedStorage="true" diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index a51746767f0f..74e82bbba785 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -16,6 +16,7 @@ package com.android.server; +import android.annotation.IntRange; import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManager; @@ -41,6 +42,7 @@ import android.os.Handler; import android.os.PowerManager; import android.os.PowerManager.ServiceType; import android.os.PowerManagerInternal; +import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; @@ -52,6 +54,7 @@ import android.provider.Settings.Secure; import android.service.dreams.Sandman; import android.service.vr.IVrManager; import android.service.vr.IVrStateCallbacks; +import android.util.ArraySet; import android.util.Slog; import com.android.internal.R; @@ -65,6 +68,9 @@ import com.android.server.twilight.TwilightState; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; final class UiModeManagerService extends SystemService { private static final String TAG = UiModeManager.class.getSimpleName(); @@ -80,6 +86,7 @@ final class UiModeManagerService extends SystemService { private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED; private int mNightMode = UiModeManager.MODE_NIGHT_NO; + private Map<Integer, String> mCarModePackagePriority = new HashMap<>(); private boolean mCarModeEnabled = false; private boolean mCharging = false; private boolean mPowerSave = false; @@ -349,15 +356,25 @@ final class UiModeManagerService extends SystemService { private final IUiModeManager.Stub mService = new IUiModeManager.Stub() { @Override - public void enableCarMode(int flags) { + public void enableCarMode(@UiModeManager.EnableCarMode int flags, + @IntRange(from = 0) int priority, String callingPackage) { if (isUiModeLocked()) { Slog.e(TAG, "enableCarMode while UI mode is locked"); return; } + + if (priority != UiModeManager.DEFAULT_PRIORITY + && getContext().checkCallingOrSelfPermission( + android.Manifest.permission.ENTER_CAR_MODE_PRIORITIZED) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Enabling car mode with a priority requires " + + "permission ENTER_CAR_MODE_PRIORITIZED"); + } + final long ident = Binder.clearCallingIdentity(); try { synchronized (mLock) { - setCarModeLocked(true, flags); + setCarModeLocked(true, flags, priority, callingPackage); if (mSystemReady) { updateLocked(flags, 0); } @@ -367,16 +384,49 @@ final class UiModeManagerService extends SystemService { } } + /** + * This method is only kept around for the time being; the AIDL has an UnsupportedAppUsage + * tag which means this method is technically considered part of the greylist "API". + * @param flags + */ + @Override + public void disableCarMode(@UiModeManager.DisableCarMode int flags) { + disableCarModeByCallingPackage(flags, null /* callingPackage */); + } + + /** + * Handles requests to disable car mode. + * @param flags Disable car mode flags + * @param callingPackage + */ @Override - public void disableCarMode(int flags) { + public void disableCarModeByCallingPackage(@UiModeManager.DisableCarMode int flags, + String callingPackage) { if (isUiModeLocked()) { Slog.e(TAG, "disableCarMode while UI mode is locked"); return; } + + // If the caller is the system, we will allow the DISABLE_CAR_MODE_ALL_PRIORITIES car + // mode flag to be specified; this is so that the user can disable car mode at all + // priorities using the persistent notification. + boolean isSystemCaller = Binder.getCallingUid() == Process.SYSTEM_UID; + final int carModeFlags = + isSystemCaller ? flags : flags & ~UiModeManager.DISABLE_CAR_MODE_ALL_PRIORITIES; + final long ident = Binder.clearCallingIdentity(); try { synchronized (mLock) { - setCarModeLocked(false, 0); + // Determine if the caller has enabled car mode at a priority other than the + // default one. If they have, then attempt to disable at that priority. + int priority = mCarModePackagePriority.entrySet() + .stream() + .filter(e -> e.getValue().equals(callingPackage)) + .findFirst() + .map(Map.Entry::getKey) + .orElse(UiModeManager.DEFAULT_PRIORITY); + + setCarModeLocked(false, carModeFlags, priority, callingPackage); if (mSystemReady) { updateLocked(0, flags); } @@ -477,19 +527,32 @@ final class UiModeManagerService extends SystemService { synchronized (mLock) { pw.println("Current UI Mode Service state:"); pw.print(" mDockState="); pw.print(mDockState); - pw.print(" mLastBroadcastState="); pw.println(mLastBroadcastState); + pw.print(" mLastBroadcastState="); pw.println(mLastBroadcastState); + pw.print(" mNightMode="); pw.print(mNightMode); pw.print(" ("); - pw.print(Shell.nightModeToStr(mNightMode)); pw.print(") "); - pw.print(" mNightModeLocked="); pw.print(mNightModeLocked); - pw.print(" mCarModeEnabled="); pw.print(mCarModeEnabled); - pw.print(" mComputedNightMode="); pw.print(mComputedNightMode); - pw.print(" mCarModeEnableFlags="); pw.print(mCarModeEnableFlags); - pw.print(" mEnableCarDockLaunch="); pw.println(mEnableCarDockLaunch); + pw.print(Shell.nightModeToStr(mNightMode)); pw.print(") "); + pw.print(" mNightModeLocked="); pw.println(mNightModeLocked); + + pw.print(" mCarModeEnabled="); pw.print(mCarModeEnabled); + pw.print(" (carModeApps="); + for (Map.Entry<Integer, String> entry : mCarModePackagePriority.entrySet()) { + pw.print(entry.getKey()); + pw.print(":"); + pw.print(entry.getValue()); + pw.print(" "); + } + pw.println(""); + pw.print(" mComputedNightMode="); pw.print(mComputedNightMode); + pw.print(" mCarModeEnableFlags="); pw.print(mCarModeEnableFlags); + pw.print(" mEnableCarDockLaunch="); pw.println(mEnableCarDockLaunch); + pw.print(" mCurUiMode=0x"); pw.print(Integer.toHexString(mCurUiMode)); - pw.print(" mUiModeLocked="); pw.print(mUiModeLocked); - pw.print(" mSetUiMode=0x"); pw.println(Integer.toHexString(mSetUiMode)); + pw.print(" mUiModeLocked="); pw.print(mUiModeLocked); + pw.print(" mSetUiMode=0x"); pw.println(Integer.toHexString(mSetUiMode)); + pw.print(" mHoldingConfiguration="); pw.print(mHoldingConfiguration); - pw.print(" mSystemReady="); pw.println(mSystemReady); + pw.print(" mSystemReady="); pw.println(mSystemReady); + if (mTwilightManager != null) { // We may not have a TwilightManager. pw.print(" mTwilightService.getLastTwilightState()="); @@ -512,12 +575,32 @@ final class UiModeManagerService extends SystemService { } } - void setCarModeLocked(boolean enabled, int flags) { - if (mCarModeEnabled != enabled) { - mCarModeEnabled = enabled; + /** + * Updates the global car mode state. + * The device is considered to be in car mode if there exists an app at any priority level which + * has entered car mode. + * + * @param enabled {@code true} if the caller wishes to enable car mode, {@code false} otherwise. + * @param flags Flags used when enabling/disabling car mode. + * @param priority The priority level for entering or exiting car mode; defaults to + * {@link UiModeManager#DEFAULT_PRIORITY} for callers using + * {@link UiModeManager#enableCarMode(int)}. Callers using + * {@link UiModeManager#enableCarMode(int, int)} may specify a priority. + * @param packageName The package name of the app which initiated the request to enable or + * disable car mode. + */ + void setCarModeLocked(boolean enabled, int flags, int priority, String packageName) { + if (enabled) { + enableCarMode(priority, packageName); + } else { + disableCarMode(flags, priority, packageName); + } + boolean isCarModeNowEnabled = isCarModeEnabled(); + if (mCarModeEnabled != isCarModeNowEnabled) { + mCarModeEnabled = isCarModeNowEnabled; // When exiting car mode, restore night mode from settings - if (!mCarModeEnabled) { + if (!isCarModeNowEnabled) { Context context = getContext(); updateNightModeFromSettings(context, context.getResources(), @@ -527,11 +610,102 @@ final class UiModeManagerService extends SystemService { mCarModeEnableFlags = flags; } + /** + * Handles disabling car mode. + * <p> + * Car mode can be disabled at a priority level if any of the following is true: + * 1. The priority being disabled is the {@link UiModeManager#DEFAULT_PRIORITY}. + * 2. The priority level is enabled and the caller is the app who originally enabled it. + * 3. The {@link UiModeManager#DISABLE_CAR_MODE_ALL_PRIORITIES} flag was specified, meaning all + * car mode priorities are disabled. + * + * @param flags Car mode flags. + * @param priority The priority level at which to disable car mode. + * @param packageName The calling package which initiated the request. + */ + private void disableCarMode(int flags, int priority, String packageName) { + boolean isDisableAll = (flags & UiModeManager.DISABLE_CAR_MODE_ALL_PRIORITIES) != 0; + boolean isPriorityTracked = mCarModePackagePriority.keySet().contains(priority); + boolean isDefaultPriority = priority == UiModeManager.DEFAULT_PRIORITY; + boolean isChangeAllowed = + // Anyone can disable the default priority. + isDefaultPriority + // If priority was enabled, only enabling package can disable it. + || isPriorityTracked && mCarModePackagePriority.get(priority).equals(packageName) + // Disable all priorities flag can disable all regardless. + || isDisableAll; + if (isChangeAllowed) { + Slog.d(TAG, "disableCarMode: disabling, priority=" + priority + + ", packageName=" + packageName); + if (isDisableAll) { + Set<Map.Entry<Integer, String>> entries = + new ArraySet<>(mCarModePackagePriority.entrySet()); + mCarModePackagePriority.clear(); + + for (Map.Entry<Integer, String> entry : entries) { + notifyCarModeDisabled(entry.getKey(), entry.getValue()); + } + } else { + mCarModePackagePriority.remove(priority); + notifyCarModeDisabled(priority, packageName); + } + } + } + + /** + * Handles enabling car mode. + * <p> + * Car mode can be enabled at any priority if it has not already been enabled at that priority. + * The calling package is tracked for the first app which enters priority at the + * {@link UiModeManager#DEFAULT_PRIORITY}, though any app can disable it at that priority. + * + * @param priority The priority for enabling car mode. + * @param packageName The calling package which initiated the request. + */ + private void enableCarMode(int priority, String packageName) { + boolean isPriorityTracked = mCarModePackagePriority.containsKey(priority); + boolean isPackagePresent = mCarModePackagePriority.containsValue(packageName); + if (!isPriorityTracked && !isPackagePresent) { + Slog.d(TAG, "enableCarMode: enabled at priority=" + priority + ", packageName=" + + packageName); + mCarModePackagePriority.put(priority, packageName); + notifyCarModeEnabled(priority, packageName); + } else { + Slog.d(TAG, "enableCarMode: car mode at priority " + priority + " already enabled."); + } + + } + + private void notifyCarModeEnabled(int priority, String packageName) { + Intent intent = new Intent(UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED); + intent.putExtra(UiModeManager.EXTRA_CALLING_PACKAGE, packageName); + intent.putExtra(UiModeManager.EXTRA_PRIORITY, priority); + getContext().sendBroadcastAsUser(intent, UserHandle.ALL, + android.Manifest.permission.HANDLE_CAR_MODE_CHANGES); + } + + private void notifyCarModeDisabled(int priority, String packageName) { + Intent intent = new Intent(UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED); + intent.putExtra(UiModeManager.EXTRA_CALLING_PACKAGE, packageName); + intent.putExtra(UiModeManager.EXTRA_PRIORITY, priority); + getContext().sendBroadcastAsUser(intent, UserHandle.ALL, + android.Manifest.permission.HANDLE_CAR_MODE_CHANGES); + } + + /** + * Determines if car mode is enabled at any priority level. + * @return {@code true} if car mode is enabled, {@code false} otherwise. + */ + private boolean isCarModeEnabled() { + return mCarModePackagePriority.size() > 0; + } + private void updateDockState(int newState) { synchronized (mLock) { if (newState != mDockState) { mDockState = newState; - setCarModeLocked(mDockState == Intent.EXTRA_DOCK_STATE_CAR, 0); + setCarModeLocked(mDockState == Intent.EXTRA_DOCK_STATE_CAR, 0, + UiModeManager.DEFAULT_PRIORITY, "" /* packageName */); if (mSystemReady) { updateLocked(UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME, 0); } |