diff options
130 files changed, 3119 insertions, 1906 deletions
diff --git a/Android.bp b/Android.bp index 25238e8c0881..6ae706dad75e 100644 --- a/Android.bp +++ b/Android.bp @@ -418,7 +418,6 @@ java_library { static_libs: [ "app-compat-annotations", "framework-minus-apex", - "framework-appsearch.impl", // TODO(b/146218515): should be removed "framework-updatable-stubs-module_libs_api", ], sdk_version: "core_platform", diff --git a/core/api/current.txt b/core/api/current.txt index 8ca3551eace4..3db168566870 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -3074,6 +3074,7 @@ package android.accessibilityservice { method public void onSystemActionsChanged(); method public final boolean performGlobalAction(int); method public void setAccessibilityFocusAppearance(int, @ColorInt int); + method public void setAnimationScale(float); method public boolean setCacheEnabled(boolean); method public void setGestureDetectionPassthroughRegion(int, @NonNull android.graphics.Region); method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo); @@ -4069,7 +4070,7 @@ package android.app { method public int getMaxNumPictureInPictureActions(); method public final android.media.session.MediaController getMediaController(); method @NonNull public android.view.MenuInflater getMenuInflater(); - method @Nullable public android.view.OnBackInvokedDispatcher getOnBackInvokedDispatcher(); + method @NonNull public android.view.OnBackInvokedDispatcher getOnBackInvokedDispatcher(); method public final android.app.Activity getParent(); method @Nullable public android.content.Intent getParentActivityIntent(); method public android.content.SharedPreferences getPreferences(int); @@ -4875,6 +4876,7 @@ package android.app { field public static final int REASON_DEPENDENCY_DIED = 12; // 0xc field public static final int REASON_EXCESSIVE_RESOURCE_USAGE = 9; // 0x9 field public static final int REASON_EXIT_SELF = 1; // 0x1 + field public static final int REASON_FREEZER = 14; // 0xe field public static final int REASON_INITIALIZATION_FAILURE = 7; // 0x7 field public static final int REASON_LOW_MEMORY = 3; // 0x3 field public static final int REASON_OTHER = 13; // 0xd @@ -4970,7 +4972,7 @@ package android.app { method @NonNull @UiContext public final android.content.Context getContext(); method @Nullable public android.view.View getCurrentFocus(); method @NonNull public android.view.LayoutInflater getLayoutInflater(); - method @Nullable public android.view.OnBackInvokedDispatcher getOnBackInvokedDispatcher(); + method @NonNull public android.view.OnBackInvokedDispatcher getOnBackInvokedDispatcher(); method @Nullable public final android.app.Activity getOwnerActivity(); method @Nullable public final android.view.SearchEvent getSearchEvent(); method public final int getVolumeControlStream(); @@ -6949,6 +6951,7 @@ package android.app { method public boolean performGlobalAction(int); method public void revokeRuntimePermission(String, String); method public void revokeRuntimePermissionAsUser(String, String, android.os.UserHandle); + method public void setAnimationScale(float); method public void setOnAccessibilityEventListener(android.app.UiAutomation.OnAccessibilityEventListener); method public boolean setRotation(int); method public void setRunAsMonkey(boolean); @@ -42940,6 +42943,7 @@ package android.telephony { field public static final int ENCODING_16BIT = 3; // 0x3 field public static final int ENCODING_7BIT = 1; // 0x1 field public static final int ENCODING_8BIT = 2; // 0x2 + field public static final int ENCODING_KSC5601 = 4; // 0x4 field public static final int ENCODING_UNKNOWN = 0; // 0x0 field public static final String FORMAT_3GPP = "3gpp"; field public static final String FORMAT_3GPP2 = "3gpp2"; @@ -48903,7 +48907,7 @@ package android.view { } public interface OnBackInvokedDispatcherOwner { - method @Nullable public android.view.OnBackInvokedDispatcher getOnBackInvokedDispatcher(); + method @NonNull public android.view.OnBackInvokedDispatcher getOnBackInvokedDispatcher(); } public interface OnReceiveContentListener { @@ -49331,7 +49335,7 @@ package android.view { field @NonNull public static final android.os.Parcelable.Creator<android.view.VerifiedMotionEvent> CREATOR; } - @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback android.view.OnBackInvokedDispatcherOwner { + @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback { ctor public View(android.content.Context); ctor public View(android.content.Context, @Nullable android.util.AttributeSet); ctor public View(android.content.Context, @Nullable android.util.AttributeSet, int); @@ -49535,7 +49539,6 @@ package android.view { method @IdRes public int getNextFocusLeftId(); method @IdRes public int getNextFocusRightId(); method @IdRes public int getNextFocusUpId(); - method @Nullable public android.view.OnBackInvokedDispatcher getOnBackInvokedDispatcher(); method public android.view.View.OnFocusChangeListener getOnFocusChangeListener(); method @ColorInt public int getOutlineAmbientShadowColor(); method public android.view.ViewOutlineProvider getOutlineProvider(); diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 3c1e1b715366..9737cde48b4d 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -139,9 +139,14 @@ package android.content { package android.content.pm { + public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable { + method @NonNull public java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraryInfos(); + } + public abstract class PackageManager { method @NonNull public String getPermissionControllerPackageName(); method @NonNull public String getSupplementalProcessPackageName(); + field public static final int MATCH_STATIC_SHARED_AND_SDK_LIBRARIES = 67108864; // 0x4000000 } } diff --git a/core/api/removed.txt b/core/api/removed.txt index 311b110f1997..07639fbf5378 100644 --- a/core/api/removed.txt +++ b/core/api/removed.txt @@ -513,7 +513,7 @@ package android.util { package android.view { - @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback android.view.OnBackInvokedDispatcherOwner { + @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback { method protected void initializeFadingEdge(android.content.res.TypedArray); method protected void initializeScrollbars(android.content.res.TypedArray); } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 6a458fbd30ee..252d579d4939 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -166,6 +166,7 @@ package android { field public static final String MANAGE_CONTENT_SUGGESTIONS = "android.permission.MANAGE_CONTENT_SUGGESTIONS"; field public static final String MANAGE_DEBUGGING = "android.permission.MANAGE_DEBUGGING"; field public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS"; + field public static final String MANAGE_ETHERNET_NETWORKS = "android.permission.MANAGE_ETHERNET_NETWORKS"; field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION"; field public static final String MANAGE_GAME_ACTIVITY = "android.permission.MANAGE_GAME_ACTIVITY"; field public static final String MANAGE_GAME_MODE = "android.permission.MANAGE_GAME_MODE"; @@ -1174,6 +1175,13 @@ package android.app.admin { field public static final String UNDEFINED = "UNDEFINED"; } + public static final class DevicePolicyResources.Strings.Dialer { + field public static final String NOTIFICATION_INCOMING_WORK_CALL_TITLE = "Dialer.NOTIFICATION_INCOMING_WORK_CALL_TITLE"; + field public static final String NOTIFICATION_MISSED_WORK_CALL_TITLE = "Dialer.NOTIFICATION_MISSED_WORK_CALL_TITLE"; + field public static final String NOTIFICATION_ONGOING_WORK_CALL_TITLE = "Dialer.NOTIFICATION_ONGOING_WORK_CALL_TITLE"; + field public static final String NOTIFICATION_WIFI_WORK_CALL_LABEL = "Dialer.NOTIFICATION_WIFI_WORK_CALL_LABEL"; + } + public static final class DevicePolicyResources.Strings.DocumentsUi { field public static final String CANT_SAVE_TO_PERSONAL_MESSAGE = "DocumentsUi.CANT_SAVE_TO_PERSONAL_MESSAGE"; field public static final String CANT_SAVE_TO_PERSONAL_TITLE = "DocumentsUi.CANT_SAVE_TO_PERSONAL_TITLE"; @@ -9947,7 +9955,8 @@ package android.permission { method @IntRange(from=0) @RequiresPermission(anyOf={android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY, android.Manifest.permission.UPGRADE_RUNTIME_PERMISSIONS}) public int getRuntimePermissionsVersion(); method @NonNull public java.util.List<android.permission.PermissionManager.SplitPermissionInfo> getSplitPermissions(); method @RequiresPermission(anyOf={android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY, android.Manifest.permission.UPGRADE_RUNTIME_PERMISSIONS}) public void setRuntimePermissionsVersion(@IntRange(from=0) int); - method @RequiresPermission(android.Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS) public void startOneTimePermissionSession(@NonNull String, long, int, int); + method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS) public void startOneTimePermissionSession(@NonNull String, long, int, int); + method @RequiresPermission(android.Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS) public void startOneTimePermissionSession(@NonNull String, long, long, int, int); method @RequiresPermission(android.Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS) public void stopOneTimePermissionSession(@NonNull String); field @RequiresPermission(android.Manifest.permission.START_REVIEW_PERMISSION_DECISIONS) public static final String ACTION_REVIEW_PERMISSION_DECISIONS = "android.permission.action.REVIEW_PERMISSION_DECISIONS"; field public static final int PERMISSION_GRANTED = 0; // 0x0 @@ -11197,6 +11206,7 @@ package android.service.games { method public void onTransientSystemBarVisibilityFromRevealGestureChanged(boolean); method @RequiresPermission(android.Manifest.permission.MANAGE_GAME_ACTIVITY) public final boolean restartGame(); method public void setTaskOverlayView(@NonNull android.view.View, @NonNull android.view.ViewGroup.LayoutParams); + method @RequiresPermission(android.Manifest.permission.MANAGE_GAME_ACTIVITY) public final void startActivityFromGameSessionForResult(@NonNull android.content.Intent, @Nullable android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.service.games.GameSessionActivityCallback); method public void takeScreenshot(@NonNull java.util.concurrent.Executor, @NonNull android.service.games.GameSession.ScreenshotCallback); } @@ -11206,6 +11216,11 @@ package android.service.games { field public static final int ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR = 0; // 0x0 } + public interface GameSessionActivityCallback { + method public void onActivityResult(int, @Nullable android.content.Intent); + method public default void onActivityStartFailed(@NonNull Throwable); + } + public abstract class GameSessionService extends android.app.Service { ctor public GameSessionService(); method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent); @@ -12234,6 +12249,7 @@ package android.telecom { field public static final int CALL_SOURCE_EMERGENCY_SHORTCUT = 2; // 0x2 field public static final int CALL_SOURCE_UNSPECIFIED = 0; // 0x0 field public static final String EXTRA_CALL_BACK_INTENT = "android.telecom.extra.CALL_BACK_INTENT"; + field public static final String EXTRA_CALL_HAS_IN_BAND_RINGTONE = "android.telecom.extra.CALL_HAS_IN_BAND_RINGTONE"; field public static final String EXTRA_CALL_SOURCE = "android.telecom.extra.CALL_SOURCE"; field public static final String EXTRA_CALL_TECHNOLOGY_TYPE = "android.telecom.extra.CALL_TECHNOLOGY_TYPE"; field public static final String EXTRA_CLEAR_MISSED_CALLS_INTENT = "android.telecom.extra.CLEAR_MISSED_CALLS_INTENT"; diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 947900041090..9d6fc0e67f03 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2816,7 +2816,7 @@ package android.view { method public void setView(@NonNull android.view.View, @NonNull android.view.WindowManager.LayoutParams); } - @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback android.view.OnBackInvokedDispatcherOwner { + @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback { method public android.view.View getTooltipView(); method public boolean isAutofilled(); method public static boolean isDefaultFocusHighlightEnabled(); diff --git a/core/java/Android.bp b/core/java/Android.bp index 5649c5f1f7db..edf9ece4661c 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -176,6 +176,18 @@ filegroup { "com/android/internal/util/IndentingPrintWriter.java", "com/android/internal/util/MessageUtils.java", "com/android/internal/util/WakeupMessage.java", + // TODO: delete as soon as NetworkStatsFactory stops using + "com/android/internal/util/ProcFileReader.java", + ], +} + +// keep these files in sync with the packages/modules/Connectivity jarjar-rules.txt for +// the connectivity module. +filegroup { + name: "framework-connectivity-api-shared-srcs", + srcs: [ + "android/util/IndentingPrintWriter.java", + "com/android/internal/util/FileRotator.java", ], } diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 42d2d28ae928..50473f1d8c84 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -3120,6 +3120,33 @@ public abstract class AccessibilityService extends Service { } } + /** + * Sets the system settings values that control the scaling factor for animations. The scale + * controls the animation playback speed for animations that respect these settings. Animations + * that do not respect the settings values will not be affected by this function. A lower scale + * value results in a faster speed. A value of <code>0</code> disables animations entirely. When + * animations are disabled services receive window change events more quickly which can reduce + * the potential by confusion by reducing the time during which windows are in transition. + * + * @see AccessibilityEvent#TYPE_WINDOWS_CHANGED + * @see AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED + * @see android.provider.Settings.Global#WINDOW_ANIMATION_SCALE + * @see android.provider.Settings.Global#TRANSITION_ANIMATION_SCALE + * @see android.provider.Settings.Global#ANIMATOR_DURATION_SCALE + * @param scale The scaling factor for all animations. + */ + public void setAnimationScale(float scale) { + final IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId); + if (connection != null) { + try { + connection.setAnimationScale(scale); + } catch (RemoteException re) { + throw new RuntimeException(re); + } + } + } + private static class AccessibilityContext extends ContextWrapper { private final int mConnectionId; diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 2cc15b40106b..0d6b19941afe 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -144,4 +144,6 @@ interface IAccessibilityServiceConnection { void onDoubleTap(int displayId); void onDoubleTapAndHold(int displayId); + + void setAnimationScale(float scale); } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index e6b154bd6a2e..8935022639e4 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -5503,6 +5503,17 @@ public class Activity extends ContextThemeWrapper */ public void startActivityAsCaller(Intent intent, @Nullable Bundle options, IBinder permissionToken, boolean ignoreTargetSecurity, int userId) { + startActivityAsCaller(intent, options, permissionToken, ignoreTargetSecurity, userId, -1); + } + + /** + * @see #startActivityAsCaller(Intent, Bundle, IBinder, boolean, int) + * @param requestCode The request code used for returning a result or -1 if no result should be + * returned. + * @hide + */ + public void startActivityAsCaller(Intent intent, @Nullable Bundle options, + IBinder permissionToken, boolean ignoreTargetSecurity, int userId, int requestCode) { if (mParent != null) { throw new RuntimeException("Can't be called from a child"); } @@ -5510,11 +5521,11 @@ public class Activity extends ContextThemeWrapper Instrumentation.ActivityResult ar = mInstrumentation.execStartActivityAsCaller( this, mMainThread.getApplicationThread(), mToken, this, - intent, -1, options, permissionToken, ignoreTargetSecurity, userId); + intent, requestCode, options, permissionToken, ignoreTargetSecurity, + userId); if (ar != null) { mMainThread.sendActivityResult( - mToken, mEmbeddedID, -1, ar.getResultCode(), - ar.getResultData()); + mToken, mEmbeddedID, requestCode, ar.getResultCode(), ar.getResultData()); } cancelInputsAndStartExitTransition(options); } @@ -8746,17 +8757,15 @@ public class Activity extends ContextThemeWrapper * Returns the {@link OnBackInvokedDispatcher} instance associated with the window that this * activity is attached to. * - * Returns null if the activity is not attached to a window with a decor. + * @throws IllegalStateException if this Activity is not visual. */ - @Nullable + @NonNull @Override public OnBackInvokedDispatcher getOnBackInvokedDispatcher() { - if (mWindow != null) { - View decorView = mWindow.getDecorView(); - if (decorView != null) { - return decorView.getOnBackInvokedDispatcher(); - } + if (mWindow == null) { + throw new IllegalStateException("OnBackInvokedDispatcher are not available on " + + "non-visual activities"); } - return null; + return ((OnBackInvokedDispatcherOwner) mWindow).getOnBackInvokedDispatcher(); } } diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java index 9039bbdf62cf..60e22f4ecd12 100644 --- a/core/java/android/app/ApplicationExitInfo.java +++ b/core/java/android/app/ApplicationExitInfo.java @@ -156,6 +156,12 @@ public final class ApplicationExitInfo implements Parcelable { public static final int REASON_OTHER = 13; /** + * Application process was killed by App Freezer, for example, because it receives + * sync binder transactions while being frozen. + */ + public static final int REASON_FREEZER = 14; + + /** * Application process kills subreason is unknown. * * For internal use only. @@ -487,6 +493,7 @@ public final class ApplicationExitInfo implements Parcelable { REASON_USER_STOPPED, REASON_DEPENDENCY_DIED, REASON_OTHER, + REASON_FREEZER, }) @Retention(RetentionPolicy.SOURCE) public @interface Reason {} @@ -1138,6 +1145,8 @@ public final class ApplicationExitInfo implements Parcelable { return "DEPENDENCY DIED"; case REASON_OTHER: return "OTHER KILLS BY SYSTEM"; + case REASON_FREEZER: + return "FREEZER"; default: return "UNKNOWN"; } diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index a7fb83bfcf5e..4b42ddc383b2 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -1449,15 +1449,9 @@ public class Dialog implements DialogInterface, Window.Callback, * * Returns null if the dialog is not attached to a window with a decor. */ - @Nullable + @NonNull @Override public OnBackInvokedDispatcher getOnBackInvokedDispatcher() { - if (mWindow != null) { - View decorView = mWindow.getDecorView(); - if (decorView != null) { - return decorView.getOnBackInvokedDispatcher(); - } - } - return null; + return ((OnBackInvokedDispatcherOwner) mWindow).getOnBackInvokedDispatcher(); } } diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index e4ef12c250ab..7c48a5738e51 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -679,6 +679,10 @@ interface IActivityManager { */ boolean isAppFreezerSupported(); + /** + * Return whether the app freezer is enabled (true) or not (false) by this system. + */ + boolean isAppFreezerEnabled(); /** * Kills uid with the reason of permission change. diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index 0801b2481f0c..c5add66e0a14 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -130,6 +130,9 @@ interface IActivityTaskManager { in ProfilerInfo profilerInfo, in Bundle options, int userId); int startAssistantActivity(in String callingPackage, in String callingFeatureId, int callingPid, int callingUid, in Intent intent, in String resolvedType, in Bundle options, int userId); + int startActivityFromGameSession(IApplicationThread caller, in String callingPackage, + in String callingFeatureId, int callingPid, int callingUid, in Intent intent, + int taskId, int userId); void startRecentsActivity(in Intent intent, in long eventTime, in IRecentsAnimationRunner recentsAnimationRunner); int startActivityFromRecents(int taskId, in Bundle options); diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index 00903a880834..b41b5f005f1f 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -780,6 +780,33 @@ public final class UiAutomation { } /** + * Sets the system settings values that control the scaling factor for animations. The scale + * controls the animation playback speed for animations that respect these settings. Animations + * that do not respect the settings values will not be affected by this function. A lower scale + * value results in a faster speed. A value of <code>0</code> disables animations entirely. When + * animations are disabled services receive window change events more quickly which can reduce + * the potential by confusion by reducing the time during which windows are in transition. + * + * @see AccessibilityEvent#TYPE_WINDOWS_CHANGED + * @see AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED + * @see android.provider.Settings.Global#WINDOW_ANIMATION_SCALE + * @see android.provider.Settings.Global#TRANSITION_ANIMATION_SCALE + * @see android.provider.Settings.Global#ANIMATOR_DURATION_SCALE + * @param scale The scaling factor for all animations. + */ + public void setAnimationScale(float scale) { + final IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().getConnection(mConnectionId); + if (connection != null) { + try { + connection.setAnimationScale(scale); + } catch (RemoteException re) { + throw new RuntimeException(re); + } + } + } + + /** * A request for WindowManagerService to wait until all animations have completed and input * information has been sent from WindowManager to native InputManager. * diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java index ac39cb4f1e23..7f2e5fde70f1 100644 --- a/core/java/android/app/admin/DevicePolicyResources.java +++ b/core/java/android/app/admin/DevicePolicyResources.java @@ -58,6 +58,10 @@ import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_ import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_GENERIC_MESSAGE; import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_ORG_OWNED_MESSAGE; import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Dialer.NOTIFICATION_INCOMING_WORK_CALL_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Dialer.NOTIFICATION_MISSED_WORK_CALL_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Dialer.NOTIFICATION_ONGOING_WORK_CALL_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Dialer.NOTIFICATION_WIFI_WORK_CALL_LABEL; import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SAVE_TO_PERSONAL_MESSAGE; import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SAVE_TO_PERSONAL_TITLE; import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SAVE_TO_WORK_MESSAGE; @@ -512,7 +516,11 @@ public final class DevicePolicyResources { WORK_PROFILE_DEFAULT_APPS_TITLE, HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE, BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE, BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE, BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE, FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE, - LOCATION_AUTO_GRANTED_MESSAGE + LOCATION_AUTO_GRANTED_MESSAGE, + + // Dialer Strings + NOTIFICATION_INCOMING_WORK_CALL_TITLE, NOTIFICATION_ONGOING_WORK_CALL_TITLE, + NOTIFICATION_MISSED_WORK_CALL_TITLE, NOTIFICATION_WIFI_WORK_CALL_LABEL, }) public @interface UpdatableStringId { } @@ -709,6 +717,7 @@ public final class DevicePolicyResources { strings.addAll(DocumentsUi.buildStringsSet()); strings.addAll(MediaProvider.buildStringsSet()); strings.addAll(PermissionController.buildStringsSet()); + strings.addAll(Dialer.buildStringsSet()); return strings; } @@ -2934,5 +2943,54 @@ public final class DevicePolicyResources { return strings; } } + + /** + * Class containing the identifiers used to update device management-related system strings + * in the Dialer app. + */ + public static final class Dialer { + + private Dialer() { + } + + private static final String PREFIX = "Dialer."; + + /** + * The title of the in-call notification for an incoming work call. + */ + public static final String NOTIFICATION_INCOMING_WORK_CALL_TITLE = + PREFIX + "NOTIFICATION_INCOMING_WORK_CALL_TITLE"; + + /** + * The title of the in-call notification for an ongoing work call. + */ + public static final String NOTIFICATION_ONGOING_WORK_CALL_TITLE = + PREFIX + "NOTIFICATION_ONGOING_WORK_CALL_TITLE"; + + /** + * Missed call notification label, used when there's exactly one missed call from work + * contact. + */ + public static final String NOTIFICATION_MISSED_WORK_CALL_TITLE = + PREFIX + "NOTIFICATION_MISSED_WORK_CALL_TITLE"; + + /** + * Label for notification indicating that call is being made over wifi. + */ + public static final String NOTIFICATION_WIFI_WORK_CALL_LABEL = + PREFIX + "NOTIFICATION_WIFI_WORK_CALL_LABEL"; + + /** + * @hide + */ + static Set<String> buildStringsSet() { + Set<String> strings = new HashSet<>(); + strings.add(NOTIFICATION_INCOMING_WORK_CALL_TITLE); + strings.add(NOTIFICATION_ONGOING_WORK_CALL_TITLE); + strings.add(NOTIFICATION_MISSED_WORK_CALL_TITLE); + strings.add(NOTIFICATION_WIFI_WORK_CALL_LABEL); + return strings; + } + } } } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 6ffea3f9217d..207412511198 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -6504,15 +6504,26 @@ public abstract class Context { * <li>Each permission in {@code permissions} must be a runtime permission. * </ul> * <p> - * For every permission in {@code permissions}, the entire permission group it belongs to will - * be revoked. The revocation happens asynchronously and kills all processes running in the - * calling UID. It will be triggered once it is safe to do so. In particular, it will not be - * triggered as long as the package remains in the foreground, or has any active manifest - * components (e.g. when another app is accessing a content provider in the package). + * Background permissions which have no corresponding foreground permission still granted once + * the revocation is effective will also be revoked. + * <p> + * The revocation happens asynchronously and kills all processes running in the calling UID. It + * will be triggered once it is safe to do so. In particular, it will not be triggered as long + * as the package remains in the foreground, or has any active manifest components (e.g. when + * another app is accessing a content provider in the package). * <p> * If you want to revoke the permissions right away, you could call {@code System.exit()}, but * this could affect other apps that are accessing your app at the moment. For example, apps * accessing a content provider in your app will all crash. + * <p> + * Note that the settings UI shows a permission group as granted as long as at least one + * permission in the group is granted. If you want the user to observe the revocation in the + * settings, you should revoke every permission in the target group. To learn the current list + * of permissions in a group, you may use + * {@link PackageManager#getGroupOfPlatformPermission(String, Executor, Consumer)} and + * {@link PackageManager#getPlatformPermissionsForGroup(String, Executor, Consumer)}. This list + * of permissions may evolve over time, so it is recommended to check whether it contains any + * permission you wish to retain before trying to revoke an entire group. * * @param permissions Collection of permissions to be revoked. * @see PackageManager#getGroupOfPlatformPermission(String, Executor, Consumer) diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 3e527f8d5215..28bef566b59c 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -5576,6 +5576,7 @@ public class Intent implements Parcelable, Cloneable { /** * A String[] holding attribution tags when used with * {@link #ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD} + * and ACTION_MANAGE_PERMISSION_USAGE * * E.g. an attribution tag could be location_provider, com.google.android.gms.*, etc. */ @@ -5584,17 +5585,20 @@ public class Intent implements Parcelable, Cloneable { /** * A long representing the start timestamp (epoch time in millis) of the permission usage * when used with {@link #ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD} + * and ACTION_MANAGE_PERMISSION_USAGE */ public static final String EXTRA_START_TIME = "android.intent.extra.START_TIME"; /** * A long representing the end timestamp (epoch time in millis) of the permission usage when * used with {@link #ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD} + * and ACTION_MANAGE_PERMISSION_USAGE */ public static final String EXTRA_END_TIME = "android.intent.extra.END_TIME"; /** - * A boolean extra, when used with {@link #ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD}, + * A boolean extra, when used with {@link #ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD} + * and {@link #ACTION_MANAGE_PERMISSION_USAGE}, * that specifies whether the permission usage system UI is showing attribution information * for the chosen entry. * diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 9e9dd1edd577..567f649ea762 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -19,6 +19,7 @@ package android.content.pm; import static android.os.Build.VERSION_CODES.DONUT; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; @@ -48,6 +49,7 @@ import java.lang.annotation.RetentionPolicy; import java.text.Collator; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Objects; @@ -62,58 +64,58 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { private static ForBoolean sForBoolean = Parcelling.Cache.getOrCreate(ForBoolean.class); /** - * Default task affinity of all activities in this application. See - * {@link ActivityInfo#taskAffinity} for more information. This comes - * from the "taskAffinity" attribute. + * Default task affinity of all activities in this application. See + * {@link ActivityInfo#taskAffinity} for more information. This comes + * from the "taskAffinity" attribute. */ public String taskAffinity; - + /** * Optional name of a permission required to be able to access this * application's components. From the "permission" attribute. */ public String permission; - + /** * The name of the process this application should run in. From the * "process" attribute or, if not set, the same as * <var>packageName</var>. */ public String processName; - + /** * Class implementing the Application object. From the "class" * attribute. */ public String className; - + /** * A style resource identifier (in the package's resources) of the * description of an application. From the "description" attribute * or, if not set, 0. */ - public int descriptionRes; - + public int descriptionRes; + /** * A style resource identifier (in the package's resources) of the * default visual theme of the application. From the "theme" attribute * or, if not set, 0. */ public int theme; - + /** * Class implementing the Application's manage space * functionality. From the "manageSpaceActivity" * attribute. This is an optional attribute and will be null if * applications don't specify it in their manifest */ - public String manageSpaceActivityName; - + public String manageSpaceActivityName; + /** * Class implementing the Application's backup functionality. From * the "backupAgent" attribute. This is an optional attribute and * will be null if the application does not specify it in its manifest. - * + * * <p>If android:allowBackup is set to false, this attribute is ignored. */ public String backupAgentName; @@ -174,7 +176,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * {@code signatureOrSystem}. */ public static final int FLAG_SYSTEM = 1<<0; - + /** * Value for {@link #flags}: set to true if this application would like to * allow debugging of its @@ -183,7 +185,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * android:debuggable} of the <application> tag. */ public static final int FLAG_DEBUGGABLE = 1<<1; - + /** * Value for {@link #flags}: set to true if this application has code * associated with it. Comes @@ -191,7 +193,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * android:hasCode} of the <application> tag. */ public static final int FLAG_HAS_CODE = 1<<2; - + /** * Value for {@link #flags}: set to true if this application is persistent. * Comes from {@link android.R.styleable#AndroidManifestApplication_persistent @@ -212,20 +214,20 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * android:allowTaskReparenting} of the <application> tag. */ public static final int FLAG_ALLOW_TASK_REPARENTING = 1<<5; - + /** * Value for {@link #flags}: default value for the corresponding ActivityInfo flag. * Comes from {@link android.R.styleable#AndroidManifestApplication_allowClearUserData * android:allowClearUserData} of the <application> tag. */ public static final int FLAG_ALLOW_CLEAR_USER_DATA = 1<<6; - + /** * Value for {@link #flags}: this is set if this application has been * installed as an update to a built-in system application. */ public static final int FLAG_UPDATED_SYSTEM_APP = 1<<7; - + /** * Value for {@link #flags}: this is set if the application has specified * {@link android.R.styleable#AndroidManifestApplication_testOnly @@ -240,15 +242,15 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * android:smallScreens}. */ public static final int FLAG_SUPPORTS_SMALL_SCREENS = 1<<9; - + /** * Value for {@link #flags}: true when the application's window can be * displayed on normal screens. Corresponds to * {@link android.R.styleable#AndroidManifestSupportsScreens_normalScreens * android:normalScreens}. */ - public static final int FLAG_SUPPORTS_NORMAL_SCREENS = 1<<10; - + public static final int FLAG_SUPPORTS_NORMAL_SCREENS = 1<<10; + /** * Value for {@link #flags}: true when the application's window can be * increased in size for larger screens. Corresponds to @@ -256,7 +258,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * android:largeScreens}. */ public static final int FLAG_SUPPORTS_LARGE_SCREENS = 1<<11; - + /** * Value for {@link #flags}: true when the application knows how to adjust * its UI for different screen sizes. Corresponds to @@ -264,7 +266,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * android:resizeable}. */ public static final int FLAG_RESIZEABLE_FOR_SCREENS = 1<<12; - + /** * Value for {@link #flags}: true when the application knows how to * accommodate different screen densities. Corresponds to @@ -276,7 +278,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { */ @Deprecated public static final int FLAG_SUPPORTS_SCREEN_DENSITIES = 1<<13; - + /** * Value for {@link #flags}: set to true if this application would like to * request the VM to operate under the safe mode. Comes from @@ -288,7 +290,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { /** * Value for {@link #flags}: set to <code>false</code> if the application does not wish * to permit any OS-driven backups of its data; <code>true</code> otherwise. - * + * * <p>Comes from the * {@link android.R.styleable#AndroidManifestApplication_allowBackup android:allowBackup} * attribute of the <application> tag. @@ -351,7 +353,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * android:xlargeScreens}. */ public static final int FLAG_SUPPORTS_XLARGE_SCREENS = 1<<19; - + /** * Value for {@link #flags}: true when the application has requested a * large heap for its processes. Corresponds to @@ -1114,7 +1116,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * the same uid). */ public int uid; - + /** * The minimum SDK version this application can run on. It will not run * on earlier versions. @@ -1817,7 +1819,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { if (sb == null) { sb = ab.packageName; } - + return sCollator.compare(sa.toString(), sb.toString()); } @@ -1830,7 +1832,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public ApplicationInfo() { createTimestamp = System.currentTimeMillis(); } - + public ApplicationInfo(ApplicationInfo orig) { super(orig); taskAffinity = orig.taskAffinity; @@ -2125,7 +2127,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { /** * Disable compatibility mode - * + * * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) @@ -2346,7 +2348,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { } return pm.getDefaultActivityIcon(); } - + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private boolean isPackageUnavailable(PackageManager pm) { try { @@ -2655,4 +2657,22 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public int getLocaleConfigRes() { return localeConfigRes; } + + + /** + * List of all shared libraries this application is linked against. This + * list will only be set if the {@link PackageManager#GET_SHARED_LIBRARY_FILES + * PackageManager.GET_SHARED_LIBRARY_FILES} flag was used when retrieving the structure. + * + * @hide + */ + @NonNull + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public List<SharedLibraryInfo> getSharedLibraryInfos() { + if (sharedLibraryInfos == null) { + return Collections.EMPTY_LIST; + } + return sharedLibraryInfos; + } + } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index aa647000ee2d..07227c5fe0e1 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1063,6 +1063,7 @@ public abstract class PackageManager { * via this flag. * @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int MATCH_STATIC_SHARED_AND_SDK_LIBRARIES = 0x04000000; /** diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index fc2fbc39dbeb..223b8ccf44c8 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -346,7 +346,7 @@ public class InputMethodService extends AbstractInputMethodService { */ @AnyThread public static boolean canImeRenderGesturalNavButtons() { - return SystemProperties.getBoolean(PROP_CAN_RENDER_GESTURAL_NAV_BUTTONS, true); + return SystemProperties.getBoolean(PROP_CAN_RENDER_GESTURAL_NAV_BUTTONS, false); } /** diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java index e5c22e4de08e..83fc7276eabc 100644 --- a/core/java/android/inputmethodservice/NavigationBarController.java +++ b/core/java/android/inputmethodservice/NavigationBarController.java @@ -546,7 +546,8 @@ final class NavigationBarController { public String toDebugString() { return "{mRenderGesturalNavButtons=" + mRenderGesturalNavButtons + " mNavigationBarFrame=" + mNavigationBarFrame - + " mShouldShowImeSwitcherWhenImeIsShown" + mShouldShowImeSwitcherWhenImeIsShown + + " mShouldShowImeSwitcherWhenImeIsShown=" + + mShouldShowImeSwitcherWhenImeIsShown + " mAppearance=0x" + Integer.toHexString(mAppearance) + " mDarkIntensity=" + mDarkIntensity + " mDrawLegacyNavigationBarBackground=" + mDrawLegacyNavigationBarBackground diff --git a/core/java/android/os/BatteryStatsManager.java b/core/java/android/os/BatteryStatsManager.java index f16bbc66e6cd..071bdea5e3ac 100644 --- a/core/java/android/os/BatteryStatsManager.java +++ b/core/java/android/os/BatteryStatsManager.java @@ -24,8 +24,6 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; import android.content.Context; import android.net.NetworkStack; import android.os.connectivity.CellularBatteryStats; @@ -523,8 +521,6 @@ public final class BatteryStatsManager { * @param reason why Bluetooth has been turned on * @param packageName package responsible for this change */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOn(int uid, int reason, @NonNull String packageName) { try { @@ -541,8 +537,6 @@ public final class BatteryStatsManager { * @param reason why Bluetooth has been turned on * @param packageName package responsible for this change */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOff(int uid, int reason, @NonNull String packageName) { try { diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl index 1c0320e9a86e..619c8705ddae 100644 --- a/core/java/android/permission/IPermissionManager.aidl +++ b/core/java/android/permission/IPermissionManager.aidl @@ -79,7 +79,8 @@ interface IPermissionManager { void revokeOwnPermissionsOnKill(String packageName, in List<String> permissions); void startOneTimePermissionSession(String packageName, int userId, long timeout, - int importanceToResetTimer, int importanceToKeepSessionAlive); + long revokeAfterKilledDelay, int importanceToResetTimer, + int importanceToKeepSessionAlive); void stopOneTimePermissionSession(String packageName, int userId); diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java index 0cf06aa364ec..a005ab4e6ac7 100644 --- a/core/java/android/permission/PermissionControllerManager.java +++ b/core/java/android/permission/PermissionControllerManager.java @@ -907,21 +907,23 @@ public final class PermissionControllerManager { * <li>Each permission in {@code permissions} must be a runtime permission. * </ul> * <p> - * For every permission in {@code permissions}, the entire permission group it belongs to will - * be revoked. This revocation happens asynchronously and kills all processes running in the - * same UID as {@code packageName}. It will be triggered once it is safe to do so. + * Background permissions which have no corresponding foreground permission still granted once + * the revocation is effective will also be revoked. + * <p> + * This revocation happens asynchronously and kills all processes running in the same UID as + * {@code packageName}. It will be triggered once it is safe to do so. * * @param packageName The name of the package for which the permissions will be revoked. * @param permissions List of permissions to be revoked. - * @param callback Callback called when the revocation request has been completed. * - * @see Context#revokeOwnPermissionsOnKill(Collection) + * @see Context#revokeOwnPermissionsOnKill(java.util.Collection) * * @hide */ public void revokeOwnPermissionsOnKill(@NonNull String packageName, - @NonNull List<String> permissions, AndroidFuture<Void> callback) { + @NonNull List<String> permissions) { mRemoteService.postAsync(service -> { + AndroidFuture<Void> callback = new AndroidFuture<>(); service.revokeOwnPermissionsOnKill(packageName, permissions, callback); return callback; }).whenComplete((result, err) -> { diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java index 8d9f82b04b54..3292e7110ee5 100644 --- a/core/java/android/permission/PermissionControllerService.java +++ b/core/java/android/permission/PermissionControllerService.java @@ -291,7 +291,7 @@ public abstract class PermissionControllerService extends Service { /** * Called when a package is considered inactive based on the criteria given by - * {@link PermissionManager#startOneTimePermissionSession(String, long, int, int)}. + * {@link PermissionManager#startOneTimePermissionSession(String, long, long, int, int)}. * This method is called at the end of a one-time permission session * * @param packageName The package that has been inactive @@ -329,9 +329,11 @@ public abstract class PermissionControllerService extends Service { * Triggers the revocation of one or more permissions for a package. This should only be called * at the request of {@code packageName}. * <p> - * For every permission in {@code permissions}, the entire permission group it belongs to will - * be revoked. This revocation happens asynchronously and kills all processes running in the - * same UID as {@code packageName}. It will be triggered once it is safe to do so. + * Background permissions which have no corresponding foreground permission still granted once + * the revocation is effective will also be revoked. + * <p> + * This revocation happens asynchronously and kills all processes running in the same UID as + * {@code packageName}. It will be triggered once it is safe to do so. * * @param packageName The name of the package for which the permissions will be revoked. * @param permissions List of permissions to be revoked. diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 15f13eb89cb9..12fa0ddfc648 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -20,6 +20,7 @@ import static android.os.Build.VERSION_CODES.S; import android.Manifest; import android.annotation.CheckResult; +import android.annotation.DurationMillisLong; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; @@ -1282,6 +1283,22 @@ public final class PermissionManager { } /** + * Starts a one-time permission session for a given package. + * @see #startOneTimePermissionSession(String, long, long, int, int) + * @hide + * @deprecated Use {@link #startOneTimePermissionSession(String, long, long, int, int)} instead + */ + @Deprecated + @SystemApi + @RequiresPermission(Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS) + public void startOneTimePermissionSession(@NonNull String packageName, long timeoutMillis, + @ActivityManager.RunningAppProcessInfo.Importance int importanceToResetTimer, + @ActivityManager.RunningAppProcessInfo.Importance int importanceToKeepSessionAlive) { + startOneTimePermissionSession(packageName, timeoutMillis, -1, + importanceToResetTimer, importanceToKeepSessionAlive); + } + + /** * Starts a one-time permission session for a given package. A one-time permission session is * ended if app becomes inactive. Inactivity is defined as the package's uid importance level * staying > importanceToResetTimer for timeoutMillis milliseconds. If the package's uid @@ -1301,25 +1318,33 @@ public final class PermissionManager { * {@link PermissionControllerService#onOneTimePermissionSessionTimeout(String)} is invoked. * </p> * <p> - * Note that if there is currently an active session for a package a new one isn't created and - * the existing one isn't changed. + * Note that if there is currently an active session for a package a new one isn't created but + * each parameter of the existing one will be updated to the more aggressive of both sessions. + * This means that durations will be set to the shortest parameter and importances will be set + * to the lowest one. * </p> * @param packageName The package to start a one-time permission session for * @param timeoutMillis Number of milliseconds for an app to be in an inactive state + * @param revokeAfterKilledDelayMillis Number of milliseconds to wait before revoking on the + * event an app is terminated. Set to -1 to use default + * value for the device. * @param importanceToResetTimer The least important level to uid must be to reset the timer * @param importanceToKeepSessionAlive The least important level the uid must be to keep the - * session alive + * session alive * * @hide */ @SystemApi @RequiresPermission(Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS) - public void startOneTimePermissionSession(@NonNull String packageName, long timeoutMillis, + public void startOneTimePermissionSession(@NonNull String packageName, + @DurationMillisLong long timeoutMillis, + @DurationMillisLong long revokeAfterKilledDelayMillis, @ActivityManager.RunningAppProcessInfo.Importance int importanceToResetTimer, @ActivityManager.RunningAppProcessInfo.Importance int importanceToKeepSessionAlive) { try { mPermissionManager.startOneTimePermissionSession(packageName, mContext.getUserId(), - timeoutMillis, importanceToResetTimer, importanceToKeepSessionAlive); + timeoutMillis, revokeAfterKilledDelayMillis, importanceToResetTimer, + importanceToKeepSessionAlive); } catch (RemoteException e) { e.rethrowFromSystemServer(); } diff --git a/core/java/android/service/games/GameSession.java b/core/java/android/service/games/GameSession.java index e33f1801129b..468e087c941b 100644 --- a/core/java/android/service/games/GameSession.java +++ b/core/java/android/service/games/GameSession.java @@ -20,15 +20,23 @@ import android.annotation.Hide; import android.annotation.IntDef; import android.annotation.MainThread; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; +import android.app.ActivityTaskManager; +import android.app.Instrumentation; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Rect; +import android.os.Binder; +import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Slog; import android.view.SurfaceControlViewHost; import android.view.View; @@ -41,6 +49,7 @@ import com.android.internal.util.function.pooled.PooledLambda; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Objects; import java.util.concurrent.Executor; /** @@ -120,6 +129,7 @@ public abstract class GameSession { private LifecycleState mLifecycleState = LifecycleState.INITIALIZED; private boolean mAreTransientInsetsVisibleDueToGesture = false; private IGameSessionController mGameSessionController; + private Context mContext; private int mTaskId; private GameSessionRootView mGameSessionRootView; private SurfaceControlViewHost mSurfaceControlViewHost; @@ -137,6 +147,7 @@ public abstract class GameSession { int heightPx) { mGameSessionController = gameSessionController; mTaskId = taskId; + mContext = context; mSurfaceControlViewHost = surfaceControlViewHost; mGameSessionRootView = new GameSessionRootView(context, mSurfaceControlViewHost); surfaceControlViewHost.setView(mGameSessionRootView, widthPx, heightPx); @@ -299,6 +310,8 @@ public abstract class GameSession { * {@code View} may not be cleared once set, but may be replaced by invoking * {@link #setTaskOverlayView(View, ViewGroup.LayoutParams)} again. * + * <p><b>WARNING</b>: Callers <b>must</b> ensure that only trusted views are provided. + * * @param view The desired content to display. * @param layoutParams Layout parameters for the view. */ @@ -456,4 +469,67 @@ public abstract class GameSession { break; } } + + /** + * Launches an activity within the same activity stack as the {@link GameSession}. When the + * target activity exits, {@link GameSessionActivityCallback#onActivityResult(int, Intent)} will + * be invoked with the result code and result data directly from the target activity (in other + * words, the result code and data set via the target activity's + * {@link android.app.Activity#startActivityForResult} call). The caller is expected to handle + * the results that the target activity returns. + * + * <p>Any activity that an app would normally be able to start via {@link + * android.app.Activity#startActivityForResult} will be startable via this method. + * + * <p>Started activities may see a different calling package than the game session's package + * when calling {@link android.app.Activity#getCallingPackage()}. + * + * <p> If an exception is thrown while handling {@code intent}, + * {@link GameSessionActivityCallback#onActivityStartFailed(Throwable)} will be called instead + * of {@link GameSessionActivityCallback#onActivityResult(int, Intent)}. + * + * @param intent The intent to start. + * @param options Additional options for how the Activity should be started. See + * {@link android.app.Activity#startActivityForResult(Intent, int, Bundle)} for + * more details. This value may be null. + * @param executor Executor on which {@code callback} should be invoked. + * @param callback Callback to be invoked once the started activity has finished. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_GAME_ACTIVITY) + public final void startActivityFromGameSessionForResult( + @NonNull Intent intent, @Nullable Bundle options, @NonNull Executor executor, + @NonNull GameSessionActivityCallback callback) { + Objects.requireNonNull(intent); + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + + AndroidFuture<GameSessionActivityResult> future = + new AndroidFuture<GameSessionActivityResult>() + .whenCompleteAsync((result, ex) -> { + if (ex != null) { + callback.onActivityStartFailed(ex); + return; + } + callback.onActivityResult(result.getResultCode(), result.getData()); + }, executor); + + final Intent trampolineIntent = new Intent(); + trampolineIntent.setComponent( + new ComponentName( + "android", "android.service.games.GameSessionTrampolineActivity")); + trampolineIntent.putExtra(GameSessionTrampolineActivity.INTENT_KEY, intent); + trampolineIntent.putExtra(GameSessionTrampolineActivity.OPTIONS_KEY, options); + trampolineIntent.putExtra( + GameSessionTrampolineActivity.FUTURE_KEY, future); + + try { + int result = ActivityTaskManager.getService().startActivityFromGameSession( + mContext.getIApplicationThread(), mContext.getPackageName(), "GameSession", + Binder.getCallingPid(), Binder.getCallingUid(), trampolineIntent, mTaskId, + UserHandle.myUserId()); + Instrumentation.checkStartActivityResult(result, trampolineIntent); + } catch (Throwable t) { + executor.execute(() -> callback.onActivityStartFailed(t)); + } + } } diff --git a/core/java/android/service/games/GameSessionActivityCallback.java b/core/java/android/service/games/GameSessionActivityCallback.java new file mode 100644 index 000000000000..3b11df1fe644 --- /dev/null +++ b/core/java/android/service/games/GameSessionActivityCallback.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.games; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.content.Intent; +import android.os.Bundle; + +import java.util.concurrent.Executor; + +/** + * Callback invoked when an activity launched via + * {@link GameSession#startActivityFromGameSessionForResult(Intent, Bundle, Executor, + * GameSessionActivityCallback)}} has returned a result or failed to start. + * + * @hide + */ +@SystemApi +public interface GameSessionActivityCallback { + /** + * Callback invoked when an activity launched via + * {@link GameSession#startActivityFromGameSessionForResult(Intent, Bundle, Executor, + * GameSessionActivityCallback)}} has returned a result. + * + * @param resultCode The result code of the launched activity. See {@link + * android.app.Activity#setResult(int)}. + * @param data Any data returned by the launched activity. See {@link + * android.app.Activity#setResult(int, Intent)}. + */ + void onActivityResult(int resultCode, @Nullable Intent data); + + /** + * Callback invoked when a throwable was thrown when launching the {@link Intent} in + * {@link GameSession#startActivityFromGameSessionForResult(Intent, Bundle, Executor, + * GameSessionActivityCallback)}}. + */ + default void onActivityStartFailed(@NonNull Throwable t) {} +} diff --git a/core/java/android/service/games/GameSessionActivityResult.java b/core/java/android/service/games/GameSessionActivityResult.java new file mode 100644 index 000000000000..a2ec6ada010c --- /dev/null +++ b/core/java/android/service/games/GameSessionActivityResult.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.games; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Intent; +import android.os.Parcel; +import android.os.Parcelable; + + +final class GameSessionActivityResult implements Parcelable { + + public static final Creator<GameSessionActivityResult> CREATOR = + new Creator<GameSessionActivityResult>() { + @Override + public GameSessionActivityResult createFromParcel(Parcel in) { + int resultCode = in.readInt(); + Intent data = in.readParcelable(Intent.class.getClassLoader(), Intent.class); + return new GameSessionActivityResult(resultCode, data); + } + + @Override + public GameSessionActivityResult[] newArray(int size) { + return new GameSessionActivityResult[size]; + } + }; + + private final int mResultCode; + @Nullable + private final Intent mData; + + GameSessionActivityResult(int resultCode, @Nullable Intent data) { + mResultCode = resultCode; + mData = data; + } + + int getResultCode() { + return mResultCode; + } + + @Nullable + Intent getData() { + return mData; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mResultCode); + dest.writeParcelable(mData, flags); + } +} diff --git a/core/java/android/service/games/GameSessionTrampolineActivity.java b/core/java/android/service/games/GameSessionTrampolineActivity.java new file mode 100644 index 000000000000..ddea098680ea --- /dev/null +++ b/core/java/android/service/games/GameSessionTrampolineActivity.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.games; + +import android.annotation.Nullable; +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.util.Slog; + +import com.android.internal.infra.AndroidFuture; + +import java.util.concurrent.Executor; + +/** + * Trampoline activity that enables the + * {@link GameSession#startActivityFromGameSessionForResult(Intent, Bundle, Executor, + * GameSessionActivityCallback)} API by reusing existing activity result infrastructure in the + * {@link Activity} class. This activity forwards activity results back to the calling + * {@link GameSession} via {@link AndroidFuture}. + * + * @hide + */ +public final class GameSessionTrampolineActivity extends Activity { + private static final String TAG = "GameSessionTrampoline"; + private static final int REQUEST_CODE = 1; + + static final String FUTURE_KEY = "GameSessionTrampolineActivity.future"; + static final String INTENT_KEY = "GameSessionTrampolineActivity.intent"; + static final String OPTIONS_KEY = "GameSessionTrampolineActivity.options"; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + try { + startActivityAsCaller( + getIntent().getParcelableExtra(INTENT_KEY), + getIntent().getBundleExtra(OPTIONS_KEY), + null, + false, + getUserId(), + REQUEST_CODE); + } catch (Exception e) { + Slog.w(TAG, "Unable to launch activity from game session"); + AndroidFuture<GameSessionActivityResult> future = getIntent().getParcelableExtra( + FUTURE_KEY); + future.completeExceptionally(e); + finish(); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode != REQUEST_CODE) { + // Something went very wrong if we hit this code path, and we should bail. + throw new IllegalStateException("Unexpected request code: " + requestCode); + } + + AndroidFuture<GameSessionActivityResult> future = getIntent().getParcelableExtra( + FUTURE_KEY); + future.complete(new GameSessionActivityResult(resultCode, data)); + finish(); + } +} diff --git a/core/java/android/view/OnBackInvokedDispatcher.java b/core/java/android/view/OnBackInvokedDispatcher.java index 05c312b56cc7..f3ca531f2a42 100644 --- a/core/java/android/view/OnBackInvokedDispatcher.java +++ b/core/java/android/view/OnBackInvokedDispatcher.java @@ -19,6 +19,7 @@ package android.view; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SuppressLint; +import android.os.Build; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -32,6 +33,13 @@ import java.lang.annotation.RetentionPolicy; * target (a.k.a. the callback to be invoked next), or its behavior. */ public abstract class OnBackInvokedDispatcher { + + /** @hide */ + public static final String TAG = "OnBackInvokedDispatcher"; + + /** @hide */ + public static final boolean DEBUG = Build.isDebuggable(); + /** @hide */ @IntDef({ PRIORITY_DEFAULT, diff --git a/core/java/android/view/OnBackInvokedDispatcherOwner.java b/core/java/android/view/OnBackInvokedDispatcherOwner.java index 0e14ed4cdb07..e69efe01138c 100644 --- a/core/java/android/view/OnBackInvokedDispatcherOwner.java +++ b/core/java/android/view/OnBackInvokedDispatcherOwner.java @@ -16,7 +16,7 @@ package android.view; -import android.annotation.Nullable; +import android.annotation.NonNull; /** * A class that provides an {@link OnBackInvokedDispatcher} that allows you to register @@ -28,6 +28,6 @@ public interface OnBackInvokedDispatcherOwner { * to its registered {@link OnBackInvokedCallback}s. * Returns null when the root view is not attached to a window or a view tree with a decor. */ - @Nullable + @NonNull OnBackInvokedDispatcher getOnBackInvokedDispatcher(); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 4ff7e2297ea0..22c66dc7aee6 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -834,7 +834,7 @@ import java.util.function.Predicate; */ @UiThread public class View implements Drawable.Callback, KeyEvent.Callback, - AccessibilityEventSource, OnBackInvokedDispatcherOwner { + AccessibilityEventSource { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private static final boolean DBG = false; @@ -12290,7 +12290,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * @return whether this view should have haptic feedback enabled for events - * long presses. + * such as long presses. * * @see #setHapticFeedbackEnabled(boolean) * @see #performHapticFeedback(int) @@ -31447,23 +31447,4 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } return null; } - - /** - * Returns the {@link OnBackInvokedDispatcher} instance of the window this view is attached to. - * - * @return The {@link OnBackInvokedDispatcher} or {@code null} if the view is neither attached - * to a window or a view tree with a decor. - */ - @Nullable - public OnBackInvokedDispatcher getOnBackInvokedDispatcher() { - ViewParent parent = getParent(); - if (parent instanceof View) { - return ((View) parent).getOnBackInvokedDispatcher(); - } else if (parent instanceof ViewRootImpl) { - // Get the fallback dispatcher on {@link ViewRootImpl} if the view tree doesn't have - // a {@link com.android.internal.policy.DecorView}. - return ((ViewRootImpl) parent).getOnBackInvokedDispatcher(); - } - return null; - } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 386b277156e3..3d8653554efd 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -236,7 +236,7 @@ import java.util.function.Consumer; @SuppressWarnings({"EmptyCatchBlock", "PointlessBooleanExpression"}) public final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks, - AttachedSurfaceControl { + AttachedSurfaceControl, OnBackInvokedDispatcherOwner { private static final String TAG = "ViewRootImpl"; private static final boolean DBG = false; private static final boolean LOCAL_LOGV = false; @@ -313,9 +313,9 @@ public final class ViewRootImpl implements ViewParent, private @SurfaceControl.BufferTransform int mPreviousTransformHint = SurfaceControl.BUFFER_TRANSFORM_IDENTITY; /** - * The fallback {@link OnBackInvokedDispatcher} when the window doesn't have a decor view. + * The top level {@link OnBackInvokedDispatcher}. */ - private WindowOnBackInvokedDispatcher mFallbackOnBackInvokedDispatcher = + private final WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher = new WindowOnBackInvokedDispatcher(); /** @@ -893,7 +893,6 @@ public final class ViewRootImpl implements ViewParent, mFastScrollSoundEffectsEnabled = audioManager.areNavigationRepeatSoundEffectsEnabled(); mScrollCaptureRequestTimeout = SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS; - mFallbackOnBackInvokedDispatcher.attachToWindow(mWindowSession, mWindow); } public static void addFirstDrawHandler(Runnable callback) { @@ -1144,9 +1143,6 @@ public final class ViewRootImpl implements ViewParent, if (pendingInsetsController != null) { pendingInsetsController.replayAndAttach(mInsetsController); } - ((RootViewSurfaceTaker) mView) - .provideWindowOnBackInvokedDispatcher() - .attachToWindow(mWindowSession, mWindow); } try { @@ -1193,6 +1189,7 @@ public final class ViewRootImpl implements ViewParent, getAttachedWindowFrame(), 1f /* compactScale */, mTmpFrames.displayFrame, mTempRect2, mTmpFrames.frame); setFrame(mTmpFrames.frame); + registerBackCallbackOnWindow(); if (DEBUG_LAYOUT) Log.v(mTag, "Added window " + mWindow); if (res < WindowManagerGlobal.ADD_OKAY) { mAttachInfo.mRootView = null; @@ -8417,6 +8414,7 @@ public final class ViewRootImpl implements ViewParent, mAdded = false; } + mOnBackInvokedDispatcher.detachFromWindow(); WindowManagerGlobal.getInstance().doRemoveView(this); } @@ -10771,12 +10769,17 @@ public final class ViewRootImpl implements ViewParent, * Returns the {@link OnBackInvokedDispatcher} on the decor view if one exists, or the * fallback {@link OnBackInvokedDispatcher} instance. */ - @Nullable - public OnBackInvokedDispatcher getOnBackInvokedDispatcher() { - if (mView instanceof RootViewSurfaceTaker) { - return ((RootViewSurfaceTaker) mView).provideWindowOnBackInvokedDispatcher(); - } - return mFallbackOnBackInvokedDispatcher; + @NonNull + public WindowOnBackInvokedDispatcher getOnBackInvokedDispatcher() { + return mOnBackInvokedDispatcher; + } + + /** + * When this ViewRootImpl is added to the window manager, transfers the first + * {@link OnBackInvokedCallback} to be called to the server. + */ + private void registerBackCallbackOnWindow() { + mOnBackInvokedDispatcher.attachToWindow(mWindowSession, mWindow); } @Override diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java index 571714cc05d5..18c20e2b1fa5 100644 --- a/core/java/android/window/BackNavigationInfo.java +++ b/core/java/android/window/BackNavigationInfo.java @@ -16,8 +16,6 @@ package android.window; -import static java.util.Objects.requireNonNull; - import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -61,6 +59,12 @@ public final class BackNavigationInfo implements Parcelable { public static final int TYPE_CROSS_TASK = 3; /** + * A {@link android.view.OnBackInvokedCallback} is available and needs to be called. + * <p> + */ + public static final int TYPE_CALLBACK = 4; + + /** * Defines the type of back destinations a back even can lead to. This is used to define the * type of animation that need to be run on SystemUI. */ @@ -84,35 +88,39 @@ public final class BackNavigationInfo implements Parcelable { private final RemoteCallback mRemoteCallback; @Nullable private final WindowConfiguration mTaskWindowConfiguration; + @Nullable + private final IOnBackInvokedCallback mOnBackInvokedCallback; /** * Create a new {@link BackNavigationInfo} instance. * - * @param type The {@link BackTargetType} of the destination (what will be displayed after - * the back action) - * @param topWindowLeash The leash to animate away the current topWindow. The consumer - * of the leash is responsible for removing it. - * @param screenshotSurface The screenshot of the previous activity to be displayed. - * @param screenshotBuffer A buffer containing a screenshot used to display the activity. - * See {@link #getScreenshotHardwareBuffer()} for information - * about nullity. - * @param taskWindowConfiguration The window configuration of the Task being animated - * beneath. - * @param onBackNavigationDone The callback to be called once the client is done with the back - * preview. + * @param type The {@link BackTargetType} of the destination (what will be + * displayed after the back action). + * @param topWindowLeash The leash to animate away the current topWindow. The consumer + * of the leash is responsible for removing it. + * @param screenshotSurface The screenshot of the previous activity to be displayed. + * @param screenshotBuffer A buffer containing a screenshot used to display the activity. + * See {@link #getScreenshotHardwareBuffer()} for information + * about nullity. + * @param taskWindowConfiguration The window configuration of the Task being animated beneath. + * @param onBackNavigationDone The callback to be called once the client is done with the + * back preview. + * @param onBackInvokedCallback The back callback registered by the current top level window. */ public BackNavigationInfo(@BackTargetType int type, @Nullable SurfaceControl topWindowLeash, @Nullable SurfaceControl screenshotSurface, @Nullable HardwareBuffer screenshotBuffer, @Nullable WindowConfiguration taskWindowConfiguration, - @NonNull RemoteCallback onBackNavigationDone) { + @Nullable RemoteCallback onBackNavigationDone, + @Nullable IOnBackInvokedCallback onBackInvokedCallback) { mType = type; mDepartingWindowContainer = topWindowLeash; mScreenshotSurface = screenshotSurface; mScreenshotBuffer = screenshotBuffer; mTaskWindowConfiguration = taskWindowConfiguration; mRemoteCallback = onBackNavigationDone; + mOnBackInvokedCallback = onBackInvokedCallback; } private BackNavigationInfo(@NonNull Parcel in) { @@ -121,7 +129,8 @@ public final class BackNavigationInfo implements Parcelable { mScreenshotSurface = in.readTypedObject(SurfaceControl.CREATOR); mScreenshotBuffer = in.readTypedObject(HardwareBuffer.CREATOR); mTaskWindowConfiguration = in.readTypedObject(WindowConfiguration.CREATOR); - mRemoteCallback = requireNonNull(in.readTypedObject(RemoteCallback.CREATOR)); + mRemoteCallback = in.readTypedObject(RemoteCallback.CREATOR); + mOnBackInvokedCallback = IOnBackInvokedCallback.Stub.asInterface(in.readStrongBinder()); } @Override @@ -132,10 +141,12 @@ public final class BackNavigationInfo implements Parcelable { dest.writeTypedObject(mScreenshotBuffer, flags); dest.writeTypedObject(mTaskWindowConfiguration, flags); dest.writeTypedObject(mRemoteCallback, flags); + dest.writeStrongInterface(mOnBackInvokedCallback); } /** * Returns the type of back navigation that is about to happen. + * * @see BackTargetType */ public @BackTargetType int getType() { @@ -152,8 +163,8 @@ public final class BackNavigationInfo implements Parcelable { } /** - * Returns the {@link SurfaceControl} that should be used to display a screenshot of the - * previous activity. + * Returns the {@link SurfaceControl} that should be used to display a screenshot of the + * previous activity. */ @Nullable public SurfaceControl getScreenshotSurface() { @@ -185,11 +196,27 @@ public final class BackNavigationInfo implements Parcelable { } /** + * Returns the {@link android.view.OnBackInvokedCallback} of the top level window or null if + * the client didn't register a callback. + * <p> + * This is never null when {@link #getType} returns {@link #TYPE_CALLBACK}. + * + * @see android.view.OnBackInvokedCallback + * @see android.view.OnBackInvokedDispatcher + */ + @Nullable + public IOnBackInvokedCallback getOnBackInvokedCallback() { + return mOnBackInvokedCallback; + } + + /** * Callback to be called when the back preview is finished in order to notify the server that * it can clean up the resources created for the animation. */ public void onBackNavigationFinished() { - mRemoteCallback.sendResult(null); + if (mRemoteCallback != null) { + mRemoteCallback.sendResult(null); + } } @Override @@ -218,6 +245,7 @@ public final class BackNavigationInfo implements Parcelable { + ", mTaskWindowConfiguration= " + mTaskWindowConfiguration + ", mScreenshotBuffer=" + mScreenshotBuffer + ", mRemoteCallback=" + mRemoteCallback + + ", mOnBackInvokedCallback=" + mOnBackInvokedCallback + '}'; } @@ -226,7 +254,7 @@ public final class BackNavigationInfo implements Parcelable { */ public static String typeToString(@BackTargetType int type) { switch (type) { - case TYPE_UNDEFINED: + case TYPE_UNDEFINED: return "TYPE_UNDEFINED"; case TYPE_DIALOG_CLOSE: return "TYPE_DIALOG_CLOSE"; diff --git a/core/java/android/window/ProxyOnBackInvokedDispatcher.java b/core/java/android/window/ProxyOnBackInvokedDispatcher.java new file mode 100644 index 000000000000..509bbd4de389 --- /dev/null +++ b/core/java/android/window/ProxyOnBackInvokedDispatcher.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2022 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.NonNull; +import android.annotation.Nullable; +import android.util.Log; +import android.util.Pair; +import android.view.OnBackInvokedCallback; +import android.view.OnBackInvokedDispatcher; +import android.view.OnBackInvokedDispatcherOwner; + +import java.util.ArrayList; +import java.util.List; + +/** + * {@link OnBackInvokedDispatcher} only used to hold callbacks while an actual + * dispatcher becomes available. <b>It does not dispatch the back events</b>. + * <p> + * Once the actual {@link OnBackInvokedDispatcherOwner} becomes available, + * {@link #setActualDispatcherOwner(OnBackInvokedDispatcherOwner)} needs to + * be called and this {@link ProxyOnBackInvokedDispatcher} will pass the callback registrations + * onto it. + * <p> + * This dispatcher will continue to keep track of callback registrations and when a dispatcher is + * removed or set it will unregister the callbacks from the old one and register them on the new + * one unless {@link #reset()} is called before. + * + * @hide + */ +public class ProxyOnBackInvokedDispatcher extends OnBackInvokedDispatcher { + + /** + * List of pair representing an {@link OnBackInvokedCallback} and its associated priority. + * + * @see OnBackInvokedDispatcher#registerOnBackInvokedCallback(OnBackInvokedCallback, int) + */ + private final List<Pair<OnBackInvokedCallback, Integer>> mCallbacks = new ArrayList<>(); + private final Object mLock = new Object(); + private OnBackInvokedDispatcherOwner mActualDispatcherOwner = null; + + @Override + public void registerOnBackInvokedCallback( + @NonNull OnBackInvokedCallback callback, int priority) { + if (DEBUG) { + Log.v(TAG, String.format("Pending register %s. Actual=%s", callback, + mActualDispatcherOwner)); + } + synchronized (mLock) { + mCallbacks.add(Pair.create(callback, priority)); + if (mActualDispatcherOwner != null) { + mActualDispatcherOwner.getOnBackInvokedDispatcher().registerOnBackInvokedCallback( + callback, priority); + } + + } + } + + @Override + public void unregisterOnBackInvokedCallback( + @NonNull OnBackInvokedCallback callback) { + if (DEBUG) { + Log.v(TAG, String.format("Pending unregister %s. Actual=%s", callback, + mActualDispatcherOwner)); + } + synchronized (mLock) { + mCallbacks.removeIf((p) -> p.first.equals(callback)); + } + } + + /** + * Transfers all the pending callbacks to the provided dispatcher. + * <p> + * The callbacks are registered on the dispatcher in the same order as they were added on this + * proxy dispatcher. + */ + private void transferCallbacksToDispatcher() { + if (mActualDispatcherOwner == null) { + return; + } + OnBackInvokedDispatcher dispatcher = + mActualDispatcherOwner.getOnBackInvokedDispatcher(); + if (DEBUG) { + Log.v(TAG, String.format("Pending transferring %d callbacks to %s", mCallbacks.size(), + dispatcher)); + } + for (Pair<OnBackInvokedCallback, Integer> callbackPair : mCallbacks) { + dispatcher.registerOnBackInvokedCallback(callbackPair.first, + callbackPair.second); + } + mCallbacks.clear(); + } + + private void clearCallbacksOnDispatcher() { + if (mActualDispatcherOwner == null) { + return; + } + OnBackInvokedDispatcher onBackInvokedDispatcher = + mActualDispatcherOwner.getOnBackInvokedDispatcher(); + for (Pair<OnBackInvokedCallback, Integer> callback : mCallbacks) { + onBackInvokedDispatcher.unregisterOnBackInvokedCallback(callback.first); + } + } + + /** + * Resets this {@link ProxyOnBackInvokedDispatcher} so it loses track of the currently + * registered callbacks. + * <p> + * Using this method means that when setting a new {@link OnBackInvokedDispatcherOwner}, the + * callbacks registered on the old one won't be removed from it and won't be registered on + * the new one. + */ + public void reset() { + if (DEBUG) { + Log.v(TAG, "Pending reset callbacks"); + } + synchronized (mLock) { + mCallbacks.clear(); + } + } + + /** + * Sets the actual {@link OnBackInvokedDispatcherOwner} that will provides the + * {@link OnBackInvokedDispatcher} onto which the callbacks will be registered. + * <p> + * If any dispatcher owner was already present, all the callbacks that were added via this + * {@link ProxyOnBackInvokedDispatcher} will be unregistered from the old one and registered + * on the new one if it is not null. + * <p> + * If you do not wish for the previously registered callbacks to be reassigned to the new + * dispatcher, {@link #reset} must be called beforehand. + */ + public void setActualDispatcherOwner( + @Nullable OnBackInvokedDispatcherOwner actualDispatcherOwner) { + if (DEBUG) { + Log.v(TAG, String.format("Pending setActual %s. Current %s", + actualDispatcherOwner, mActualDispatcherOwner)); + } + synchronized (mLock) { + if (actualDispatcherOwner == mActualDispatcherOwner) { + return; + } + clearCallbacksOnDispatcher(); + mActualDispatcherOwner = actualDispatcherOwner; + transferCallbacksToDispatcher(); + } + } +} diff --git a/core/java/com/android/internal/app/BlockedAppActivity.java b/core/java/com/android/internal/app/BlockedAppActivity.java index 65526eba3e54..fbdbbfb06b78 100644 --- a/core/java/com/android/internal/app/BlockedAppActivity.java +++ b/core/java/com/android/internal/app/BlockedAppActivity.java @@ -17,7 +17,6 @@ package com.android.internal.app; import android.content.Intent; -import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.os.Bundle; @@ -36,9 +35,6 @@ public class BlockedAppActivity extends AlertActivity { private static final String TAG = "BlockedAppActivity"; private static final String PACKAGE_NAME = "com.android.internal.app"; private static final String EXTRA_BLOCKED_PACKAGE = PACKAGE_NAME + ".extra.BLOCKED_PACKAGE"; - private static final String EXTRA_BLOCKED_ACTIVITY_INFO = - PACKAGE_NAME + ".extra.BLOCKED_ACTIVITY_INFO"; - private static final String EXTRA_STREAMED_DEVICE = PACKAGE_NAME + ".extra.STREAMED_DEVICE"; @Override protected void onCreate(Bundle savedInstanceState) { @@ -52,30 +48,17 @@ public class BlockedAppActivity extends AlertActivity { return; } - CharSequence appLabel = null; String packageName = intent.getStringExtra(EXTRA_BLOCKED_PACKAGE); - ActivityInfo activityInfo = intent.getParcelableExtra(EXTRA_BLOCKED_ACTIVITY_INFO); - if (activityInfo != null) { - appLabel = activityInfo.loadLabel(getPackageManager()); - } else if (!TextUtils.isEmpty(packageName)) { - appLabel = getAppLabel(userId, packageName); - } - - if (TextUtils.isEmpty(appLabel)) { - Slog.wtf(TAG, "Invalid package: " + packageName + " or activity info: " + activityInfo); + if (TextUtils.isEmpty(packageName)) { + Slog.wtf(TAG, "Invalid package: " + packageName); finish(); return; } - CharSequence streamedDeviceName = intent.getCharSequenceExtra(EXTRA_STREAMED_DEVICE); - if (!TextUtils.isEmpty(streamedDeviceName)) { - mAlertParams.mTitle = getString(R.string.app_streaming_blocked_title, appLabel); - mAlertParams.mMessage = - getString(R.string.app_streaming_blocked_message, streamedDeviceName); - } else { - mAlertParams.mTitle = getString(R.string.app_blocked_title); - mAlertParams.mMessage = getString(R.string.app_blocked_message, appLabel); - } + CharSequence appLabel = getAppLabel(userId, packageName); + + mAlertParams.mTitle = getString(R.string.app_blocked_title); + mAlertParams.mMessage = getString(R.string.app_blocked_message, appLabel); mAlertParams.mPositiveButtonText = getString(android.R.string.ok); setupAlert(); } @@ -100,19 +83,4 @@ public class BlockedAppActivity extends AlertActivity { .putExtra(Intent.EXTRA_USER_ID, userId) .putExtra(EXTRA_BLOCKED_PACKAGE, packageName); } - - /** - * Creates an intent that launches {@link BlockedAppActivity} when app streaming is blocked. - * - * Using this method and providing a non-empty {@code streamedDeviceName} will cause the dialog - * to use streaming-specific error messages. - */ - public static Intent createStreamingBlockedIntent(int userId, ActivityInfo activityInfo, - CharSequence streamedDeviceName) { - return new Intent() - .setClassName("android", BlockedAppActivity.class.getName()) - .putExtra(Intent.EXTRA_USER_ID, userId) - .putExtra(EXTRA_BLOCKED_ACTIVITY_INFO, activityInfo) - .putExtra(EXTRA_STREAMED_DEVICE, streamedDeviceName); - } } diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 2925341cd948..40e40856b000 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -87,7 +87,6 @@ import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; -import android.view.OnBackInvokedDispatcher; import android.view.PendingInsetsController; import android.view.ThreadedRenderer; import android.view.View; @@ -109,7 +108,6 @@ import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.widget.FrameLayout; import android.widget.PopupWindow; -import android.window.WindowOnBackInvokedDispatcher; import com.android.internal.R; import com.android.internal.graphics.drawable.BackgroundBlurDrawable; @@ -297,7 +295,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind return true; }; private Consumer<Boolean> mCrossWindowBlurEnabledListener; - private final WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher; DecorView(Context context, int featureId, PhoneWindow window, WindowManager.LayoutParams params) { @@ -326,7 +323,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind initResizingPaints(); mLegacyNavigationBarBackgroundPaint.setColor(Color.BLACK); - mOnBackInvokedDispatcher = new WindowOnBackInvokedDispatcher(); } void setBackgroundFallback(@Nullable Drawable fallbackDrawable) { @@ -1880,7 +1876,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } mPendingInsetsController.detach(); - mOnBackInvokedDispatcher.detachFromWindow(); } @Override @@ -1925,11 +1920,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind return mPendingInsetsController; } - @Override - public WindowOnBackInvokedDispatcher provideWindowOnBackInvokedDispatcher() { - return mOnBackInvokedDispatcher; - } - private ActionMode createActionMode( int type, ActionMode.Callback2 callback, View originatingView) { switch (type) { @@ -2384,7 +2374,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } } } - mOnBackInvokedDispatcher.clear(); } @Override @@ -2666,15 +2655,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } } - /** - * Returns the {@link OnBackInvokedDispatcher} on the decor view. - */ - @Override - @Nullable - public OnBackInvokedDispatcher getOnBackInvokedDispatcher() { - return mOnBackInvokedDispatcher; - } - @Override public String toString() { return "DecorView@" + Integer.toHexString(this.hashCode()) + "[" diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index 7755b694af03..12f38a4d5b9a 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -91,6 +91,8 @@ import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; +import android.view.OnBackInvokedDispatcher; +import android.view.OnBackInvokedDispatcherOwner; import android.view.ScrollCaptureCallback; import android.view.SearchEvent; import android.view.SurfaceHolder.Callback2; @@ -110,6 +112,7 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; +import android.window.ProxyOnBackInvokedDispatcher; import com.android.internal.R; import com.android.internal.view.menu.ContextMenuBuilder; @@ -134,7 +137,8 @@ import java.util.List; * * @hide */ -public class PhoneWindow extends Window implements MenuBuilder.Callback { +public class PhoneWindow extends Window implements MenuBuilder.Callback, + OnBackInvokedDispatcherOwner { private final static String TAG = "PhoneWindow"; @@ -340,6 +344,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { boolean mDecorFitsSystemWindows = true; + private ProxyOnBackInvokedDispatcher mProxyOnBackInvokedDispatcher = + new ProxyOnBackInvokedDispatcher(); + static class WindowManagerHolder { static final IWindowManager sWindowManager = IWindowManager.Stub.asInterface( ServiceManager.getService("window")); @@ -2146,6 +2153,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { /** Notify when decor view is attached to window and {@link ViewRootImpl} is available. */ void onViewRootImplSet(ViewRootImpl viewRoot) { viewRoot.setActivityConfigCallback(mActivityConfigCallback); + mProxyOnBackInvokedDispatcher.setActualDispatcherOwner(viewRoot); applyDecorFitsSystemWindows(); } @@ -3993,4 +4001,10 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { public AttachedSurfaceControl getRootSurfaceControl() { return getViewRootImplOrNull(); } + + @NonNull + @Override + public OnBackInvokedDispatcher getOnBackInvokedDispatcher() { + return mProxyOnBackInvokedDispatcher; + } } diff --git a/core/java/com/android/internal/view/RootViewSurfaceTaker.java b/core/java/com/android/internal/view/RootViewSurfaceTaker.java index 4b89bf5082ba..3ab9a335c8b7 100644 --- a/core/java/com/android/internal/view/RootViewSurfaceTaker.java +++ b/core/java/com/android/internal/view/RootViewSurfaceTaker.java @@ -15,12 +15,10 @@ */ package com.android.internal.view; -import android.annotation.NonNull; import android.annotation.Nullable; import android.view.InputQueue; import android.view.PendingInsetsController; import android.view.SurfaceHolder; -import android.window.WindowOnBackInvokedDispatcher; /** hahahah */ public interface RootViewSurfaceTaker { @@ -31,6 +29,4 @@ public interface RootViewSurfaceTaker { InputQueue.Callback willYouTakeTheInputQueue(); void onRootViewScrollYChanged(int scrollY); @Nullable PendingInsetsController providePendingInsetsController(); - /** @hide */ - @NonNull WindowOnBackInvokedDispatcher provideWindowOnBackInvokedDispatcher(); } diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp index 7c67cbcbd23c..a6fbf094a030 100644 --- a/core/jni/android_util_Process.cpp +++ b/core/jni/android_util_Process.cpp @@ -235,9 +235,7 @@ void android_os_Process_setThreadGroupAndCpuset(JNIEnv* env, jobject clazz, int void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jint grp) { ALOGV("%s pid=%d grp=%" PRId32, __func__, pid, grp); - DIR *d; char proc_path[255]; - struct dirent *de; if (!verifyGroup(env, grp)) { return; @@ -277,84 +275,8 @@ void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jin } } - sprintf(proc_path, "/proc/%d/task", pid); - if (!(d = opendir(proc_path))) { - // If the process exited on us, don't generate an exception - if (errno != ENOENT) - signalExceptionForGroupError(env, errno, pid); - return; - } - - while ((de = readdir(d))) { - int t_pid; - int t_pri; - std::string taskprofile; - - if (de->d_name[0] == '.') - continue; - t_pid = atoi(de->d_name); - - if (!t_pid) { - ALOGE("Error getting pid for '%s'\n", de->d_name); - continue; - } - - t_pri = getpriority(PRIO_PROCESS, t_pid); - - if (t_pri <= ANDROID_PRIORITY_AUDIO) { - int scheduler = sched_getscheduler(t_pid) & ~SCHED_RESET_ON_FORK; - if ((scheduler == SCHED_FIFO) || (scheduler == SCHED_RR)) { - // This task wants to stay in its current audio group so it can keep its budget - // don't update its cpuset or cgroup - continue; - } - } - - errno = 0; - // grp == SP_BACKGROUND. Set background cpuset policy profile for all threads. - if (grp == SP_BACKGROUND) { - if (!SetTaskProfiles(t_pid, {"CPUSET_SP_BACKGROUND"}, true)) { - signalExceptionForGroupError(env, errno ? errno : EPERM, t_pid); - break; - } - continue; - } - - // grp != SP_BACKGROUND. Only change the cpuset cgroup for low priority thread, so it could - // preserve it sched policy profile setting. - if (t_pri >= ANDROID_PRIORITY_BACKGROUND) { - switch (grp) { - case SP_SYSTEM: - taskprofile = "ServiceCapacityLow"; - break; - case SP_RESTRICTED: - taskprofile = "ServiceCapacityRestricted"; - break; - case SP_FOREGROUND: - case SP_AUDIO_APP: - case SP_AUDIO_SYS: - taskprofile = "ProcessCapacityHigh"; - break; - case SP_TOP_APP: - taskprofile = "ProcessCapacityMax"; - break; - default: - taskprofile = "ProcessCapacityNormal"; - break; - } - if (!SetTaskProfiles(t_pid, {taskprofile}, true)) { - signalExceptionForGroupError(env, errno ? errno : EPERM, t_pid); - break; - } - // Change the cpuset policy profile for non-low priority thread according to the grp - } else { - if (!SetTaskProfiles(t_pid, {get_cpuset_policy_profile_name((SchedPolicy)grp)}, true)) { - signalExceptionForGroupError(env, errno ? errno : EPERM, t_pid); - break; - } - } - } - closedir(d); + if (!SetProcessProfilesCached(0, pid, {get_cpuset_policy_profile_name((SchedPolicy)grp)})) + signalExceptionForGroupError(env, errno ? errno : EPERM, pid); } void android_os_Process_setProcessFrozen( diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 9db715bfc4e1..506a0c09b7c9 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -398,6 +398,7 @@ <protected-broadcast android:name="android.net.wifi.WIFI_AP_STATE_CHANGED" /> <protected-broadcast android:name="android.net.wifi.WIFI_CREDENTIAL_CHANGED" /> <protected-broadcast android:name="android.net.wifi.aware.action.WIFI_AWARE_STATE_CHANGED" /> + <protected-broadcast android:name="android.net.wifi.aware.action.WIFI_AWARE_RESOURCE_CHANGED" /> <protected-broadcast android:name="android.net.wifi.rtt.action.WIFI_RTT_STATE_CHANGED" /> <protected-broadcast android:name="android.net.wifi.SCAN_RESULTS" /> <protected-broadcast android:name="android.net.wifi.RSSI_CHANGED" /> @@ -2027,6 +2028,11 @@ <permission android:name="android.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE" android:protectionLevel="signature" /> + <!-- @SystemApi @hide Allows an application to manage ethernet networks. + <p>Not for use by third-party or privileged applications. --> + <permission android:name="android.permission.MANAGE_ETHERNET_NETWORKS" + android:protectionLevel="signature" /> + <!-- ======================================= --> <!-- Permissions for short range, peripheral networks --> <!-- ======================================= --> @@ -6554,6 +6560,13 @@ android:exported="false"> </activity> + <activity android:name="android.service.games.GameSessionTrampolineActivity" + android:excludeFromRecents="true" + android:exported="true" + android:permission="android.permission.MANAGE_GAME_ACTIVITY" + android:theme="@style/Theme.Translucent.NoTitleBar"> + </activity> + <receiver android:name="com.android.server.BootReceiver" android:exported="true" android:systemUserOnly="true"> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 3a2fb6e70ba8..cb40e86f1535 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -2839,6 +2839,14 @@ <attr name="path" /> <attr name="minSdkVersion" /> <attr name="maxSdkVersion" /> + <!-- The order in which the apex system services are initiated. When there are dependencies + among apex system services, setting this attribute for each of them ensures that they are + created in the order required by those dependencies. The apex-system-services that are + started manually within SystemServer ignore the initOrder and are not considered for + automatic starting of the other services. + The value is a simple integer, with higher number being initialized first. If not specified, + the default order is 0. --> + <attr name="initOrder" format="integer" /> </declare-styleable> <!-- The <code>receiver</code> tag declares an diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 0dbd4175b716..d7ebb0f18c4b 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -5439,15 +5439,6 @@ <xliff:g id="app_name" example="Gmail">%1$s</xliff:g> is not available right now. </string> - <!-- Title of the dialog shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] --> - <string name="app_streaming_blocked_title"><xliff:g id="activity" example="Permission dialog">%1$s</xliff:g> unavailable</string> - <!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] --> - <string name="app_streaming_blocked_message" product="tv">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your Android TV device instead.</string> - <!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] --> - <string name="app_streaming_blocked_message" product="tablet">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your tablet instead.</string> - <!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] --> - <string name="app_streaming_blocked_message" product="default">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your phone instead.</string> - <!-- Message displayed in dialog when app is too old to run on this verison of android. [CHAR LIMIT=NONE] --> <string name="deprecated_target_sdk_message">This app was built for an older version of Android and may not work properly. Try checking for updates, or contact the developer.</string> <!-- Title for button to see application detail in app store which it came from - it may allow user to update to newer version. [CHAR LIMIT=50] --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 764b2738bce8..33efbce15eea 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3285,9 +3285,6 @@ <java-symbol type="string" name="app_blocked_title" /> <java-symbol type="string" name="app_blocked_message" /> - <java-symbol type="string" name="app_streaming_blocked_title" /> - <java-symbol type="string" name="app_streaming_blocked_message" /> - <!-- Used internally for assistant to launch activity transitions --> <java-symbol type="id" name="cross_task_transition" /> diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java index 02e5942a3544..fc385a013d00 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java @@ -204,4 +204,6 @@ public class AccessibilityServiceConnectionImpl extends IAccessibilityServiceCon public void logTrace(long timestamp, String where, long loggingTypes, String callingParams, int processId, long threadId, int callingUid, Bundle serializedCallingStackInBundle) {} + + public void setAnimationScale(float scale) {} } diff --git a/core/tests/coretests/src/android/window/BackNavigationTest.java b/core/tests/coretests/src/android/window/BackNavigationTest.java new file mode 100644 index 000000000000..91d853143764 --- /dev/null +++ b/core/tests/coretests/src/android/window/BackNavigationTest.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2022 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 static junit.framework.Assert.fail; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import android.annotation.NonNull; +import android.app.ActivityTaskManager; +import android.app.EmptyActivity; +import android.app.Instrumentation; +import android.os.RemoteException; +import android.support.test.uiautomator.UiDevice; +import android.view.OnBackInvokedCallback; + +import androidx.lifecycle.Lifecycle; +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.rules.ActivityScenarioRule; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * Integration test for back navigation + */ +public class BackNavigationTest { + + @Rule + public final ActivityScenarioRule<EmptyActivity> mScenarioRule = + new ActivityScenarioRule<>(EmptyActivity.class); + private ActivityScenario<EmptyActivity> mScenario; + private Instrumentation mInstrumentation; + + @Before + public void setup() { + mScenario = mScenarioRule.getScenario(); + mInstrumentation = InstrumentationRegistry.getInstrumentation(); + try { + UiDevice.getInstance(mInstrumentation).wakeUp(); + } catch (RemoteException ignored) { + } + mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(); + } + + @Test + public void registerCallback_initialized() { + CountDownLatch latch = registerBackCallback(); + mScenario.moveToState(Lifecycle.State.RESUMED); + assertCallbackIsCalled(latch); + } + + @Test + public void registerCallback_created() { + mScenario.moveToState(Lifecycle.State.CREATED); + CountDownLatch latch = registerBackCallback(); + mScenario.moveToState(Lifecycle.State.STARTED); + mScenario.moveToState(Lifecycle.State.RESUMED); + assertCallbackIsCalled(latch); + } + + @Test + public void registerCallback_resumed() { + mScenario.moveToState(Lifecycle.State.CREATED); + mScenario.moveToState(Lifecycle.State.STARTED); + mScenario.moveToState(Lifecycle.State.RESUMED); + CountDownLatch latch = registerBackCallback(); + assertCallbackIsCalled(latch); + } + + private void assertCallbackIsCalled(CountDownLatch latch) { + try { + mInstrumentation.getUiAutomation().waitForIdle(500, 1000); + BackNavigationInfo info = ActivityTaskManager.getService().startBackNavigation(); + assertNotNull("BackNavigationInfo is null", info); + assertNotNull("OnBackInvokedCallback is null", info.getOnBackInvokedCallback()); + info.getOnBackInvokedCallback().onBackInvoked(); + assertTrue(latch.await(500, TimeUnit.MILLISECONDS)); + } catch (RemoteException ex) { + ex.rethrowFromSystemServer(); + } catch (InterruptedException ex) { + fail("Application died before invoking the callback.\n" + ex.getMessage()); + } catch (TimeoutException ex) { + fail(ex.getMessage()); + } + } + + @NonNull + private CountDownLatch registerBackCallback() { + CountDownLatch backInvokedLatch = new CountDownLatch(1); + CountDownLatch backRegisteredLatch = new CountDownLatch(1); + mScenario.onActivity(activity -> { + activity.getOnBackInvokedDispatcher().registerOnBackInvokedCallback( + new OnBackInvokedCallback() { + @Override + public void onBackInvoked() { + backInvokedLatch.countDown(); + } + }, 0 + ); + backRegisteredLatch.countDown(); + }); + try { + if (!backRegisteredLatch.await(100, TimeUnit.MILLISECONDS)) { + fail("Back callback was not registered on the Activity thread. This might be " + + "an error with the test itself."); + } + } catch (InterruptedException e) { + fail(e.getMessage()); + } + return backInvokedLatch; + } +} diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 15672332a522..f2a875c76f1c 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -727,12 +727,6 @@ "group": "WM_DEBUG_BOOT", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, - "-1343787701": { - "message": "startBackNavigation task=%s, topRunningActivity=%s", - "level": "DEBUG", - "group": "WM_DEBUG_BACK_PREVIEW", - "at": "com\/android\/server\/wm\/BackNavigationController.java" - }, "-1340540100": { "message": "Creating SnapshotStartingData", "level": "VERBOSE", @@ -1765,6 +1759,12 @@ "group": "WM_DEBUG_SYNC_ENGINE", "at": "com\/android\/server\/wm\/BLASTSyncEngine.java" }, + "-228813488": { + "message": "%s: Setting back callback %s", + "level": "DEBUG", + "group": "WM_DEBUG_BACK_PREVIEW", + "at": "com\/android\/server\/wm\/WindowState.java" + }, "-208825711": { "message": "shouldWaitAnimatingExit: isWallpaperTarget: %s", "level": "DEBUG", @@ -3691,6 +3691,12 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/RemoteAnimationController.java" }, + "1898905572": { + "message": "startBackNavigation task=%s, topRunningActivity=%s, topWindow=%s backCallback=%s", + "level": "DEBUG", + "group": "WM_DEBUG_BACK_PREVIEW", + "at": "com\/android\/server\/wm\/BackNavigationController.java" + }, "1903353011": { "message": "notifyAppStopped: %s", "level": "VERBOSE", diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index a477bd7f8295..7ab683513570 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -1790,6 +1790,7 @@ public class BubbleStackView extends FrameLayout /** * Changes the expanded state of the stack. + * Don't call this directly, call mBubbleData#setExpanded. * * @param shouldExpand whether the bubble stack should appear expanded */ @@ -1836,7 +1837,7 @@ public class BubbleStackView extends FrameLayout } else if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) { mManageEduView.hide(); } else { - setExpanded(false); + mBubbleData.setExpanded(false); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java index 72ead0023366..32861b698daa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java @@ -103,7 +103,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis }; context.registerReceiverForAllUsers(closeSystemDialogsBroadcastReceiver, new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), null /* permission */, - mainHandler); + mainHandler, Context.RECEIVER_EXPORTED); pipMediaController.addActionListener(this::onMediaActionsChanged); } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt index 6524182e9082..906123914731 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt @@ -93,7 +93,7 @@ class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransiti fun getParams(): List<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), - repetitions = 20) + repetitions = 5) } } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index 960c7ac4099a..b738c47ef6ff 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -75,7 +75,8 @@ public class BackAnimationControllerTest { screenshotSurface, hardwareBuffer, new WindowConfiguration(), - new RemoteCallback((bundle) -> {})); + new RemoteCallback((bundle) -> {}), + null); try { doReturn(navigationInfo).when(mActivityTaskManager).startBackNavigation(); } catch (RemoteException ex) { diff --git a/media/Android.bp b/media/Android.bp index fcdfd72c91d5..5aedcfbc22e9 100644 --- a/media/Android.bp +++ b/media/Android.bp @@ -108,6 +108,11 @@ aidl_interface { vndk: { enabled: true, }, + min_sdk_version: "29", + apex_available: [ + "//apex_available:platform", + "com.android.bluetooth", + ], }, }, } diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java index e2e48d35a672..5f02a430f384 100644 --- a/media/java/android/media/ImageReader.java +++ b/media/java/android/media/ImageReader.java @@ -43,6 +43,7 @@ import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.StampedLock; /** * <p>The ImageReader class allows direct application access to image data @@ -675,7 +676,8 @@ public class ImageReader implements AutoCloseable { * If no handler specified and the calling thread has no looper. */ public void setOnImageAvailableListener(OnImageAvailableListener listener, Handler handler) { - synchronized (mListenerLock) { + long writeStamp = mListenerLock.writeLock(); + try { if (listener != null) { Looper looper = handler != null ? handler.getLooper() : Looper.myLooper(); if (looper == null) { @@ -691,6 +693,8 @@ public class ImageReader implements AutoCloseable { mListenerExecutor = null; } mListener = listener; + } finally { + mListenerLock.unlockWrite(writeStamp); } } @@ -713,9 +717,12 @@ public class ImageReader implements AutoCloseable { throw new IllegalArgumentException("executor must not be null"); } - synchronized (mListenerLock) { + long writeStamp = mListenerLock.writeLock(); + try { mListenerExecutor = executor; mListener = listener; + } finally { + mListenerLock.unlockWrite(writeStamp); } } @@ -731,6 +738,8 @@ public class ImageReader implements AutoCloseable { /** * Callback that is called when a new image is available from ImageReader. * + * This callback must not modify or close the passed {@code reader}. + * * @param reader the ImageReader the callback is associated with. * @see ImageReader * @see Image @@ -889,28 +898,41 @@ public class ImageReader implements AutoCloseable { return; } + synchronized (ir.mCloseLock) { + if (!ir.mIsReaderValid) { + // It's dangerous to fire onImageAvailable() callback when the ImageReader + // is being closed, as application could acquire next image in the + // onImageAvailable() callback. + return; + } + } + final Executor executor; - final OnImageAvailableListener listener; - synchronized (ir.mListenerLock) { + final long readStamp = ir.mListenerLock.readLock(); + try { executor = ir.mListenerExecutor; - listener = ir.mListener; - } - final boolean isReaderValid; - synchronized (ir.mCloseLock) { - isReaderValid = ir.mIsReaderValid; + if (executor == null) { + return; + } + } finally { + ir.mListenerLock.unlockRead(readStamp); } - // It's dangerous to fire onImageAvailable() callback when the ImageReader - // is being closed, as application could acquire next image in the - // onImageAvailable() callback. - if (executor != null && listener != null && isReaderValid) { - executor.execute(new Runnable() { - @Override - public void run() { - listener.onImageAvailable(ir); + executor.execute(() -> { + // Acquire readlock to ensure that the ImageReader does not change its + // state while a listener is actively processing. + final long rStamp = ir.mListenerLock.readLock(); + try { + // Fire onImageAvailable of the latest non-null listener + // This ensures that if the listener changes while messages are in queue, the + // in-flight messages will call onImageAvailable of the new listener instead + if (ir.mListener != null) { + ir.mListener.onImageAvailable(ir); } - }); - } + } finally { + ir.mListenerLock.unlockRead(rStamp); + } + }); } /** @@ -1070,7 +1092,7 @@ public class ImageReader implements AutoCloseable { private Surface mSurface; private int mEstimatedNativeAllocBytes; - private final Object mListenerLock = new Object(); + private final StampedLock mListenerLock = new StampedLock(); private final Object mCloseLock = new Object(); private boolean mIsReaderValid = false; private OnImageAvailableListener mListener; diff --git a/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java index f472d563c477..e0ce081c0276 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java +++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java @@ -358,6 +358,7 @@ public class EthernetManager { return proxy; } + @RequiresPermission(android.Manifest.permission.MANAGE_ETHERNET_NETWORKS) private void updateConfiguration( @NonNull String iface, @NonNull EthernetNetworkUpdateRequest request, @@ -372,6 +373,7 @@ public class EthernetManager { } } + @RequiresPermission(android.Manifest.permission.MANAGE_ETHERNET_NETWORKS) private void connectNetwork( @NonNull String iface, @Nullable @CallbackExecutor Executor executor, @@ -385,6 +387,7 @@ public class EthernetManager { } } + @RequiresPermission(android.Manifest.permission.MANAGE_ETHERNET_NETWORKS) private void disconnectNetwork( @NonNull String iface, @Nullable @CallbackExecutor Executor executor, diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java index 15ca8cdf75ac..df19c67f00e5 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java @@ -223,7 +223,7 @@ public class A2dpProfile implements LocalBluetoothProfile { } public boolean supportsHighQualityAudio(BluetoothDevice device) { - BluetoothDevice bluetoothDevice = (device != null) ? device : mService.getActiveDevice(); + BluetoothDevice bluetoothDevice = (device != null) ? device : getActiveDevice(); if (bluetoothDevice == null) { return false; } @@ -236,7 +236,7 @@ public class A2dpProfile implements LocalBluetoothProfile { */ @RequiresApi(Build.VERSION_CODES.TIRAMISU) public boolean isHighQualityAudioEnabled(BluetoothDevice device) { - BluetoothDevice bluetoothDevice = (device != null) ? device : mService.getActiveDevice(); + BluetoothDevice bluetoothDevice = (device != null) ? device : getActiveDevice(); if (bluetoothDevice == null) { return false; } @@ -262,7 +262,7 @@ public class A2dpProfile implements LocalBluetoothProfile { } public void setHighQualityAudioEnabled(BluetoothDevice device, boolean enabled) { - BluetoothDevice bluetoothDevice = (device != null) ? device : mService.getActiveDevice(); + BluetoothDevice bluetoothDevice = (device != null) ? device : getActiveDevice(); if (bluetoothDevice == null) { return; } @@ -288,7 +288,7 @@ public class A2dpProfile implements LocalBluetoothProfile { */ @RequiresApi(Build.VERSION_CODES.TIRAMISU) public String getHighQualityAudioOptionLabel(BluetoothDevice device) { - BluetoothDevice bluetoothDevice = (device != null) ? device : mService.getActiveDevice(); + BluetoothDevice bluetoothDevice = (device != null) ? device : getActiveDevice(); int unknownCodecId = R.string.bluetooth_profile_a2dp_high_quality_unknown_codec; if (bluetoothDevice == null || !supportsHighQualityAudio(device) || getConnectionStatus(device) != BluetoothProfile.STATE_CONNECTED) { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java index db6d41ef692d..e203cbaa3da5 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java @@ -144,20 +144,20 @@ public class LeAudioProfile implements LocalBluetoothProfile { * @hide */ public boolean connect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.connect(device); + if (mService == null) { + return false; + } + return mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); } /* * @hide */ public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.disconnect(device); + if (mService == null) { + return false; + } + return mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java index a000c099347d..d179b828d4ab 100644 --- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java +++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java @@ -528,7 +528,18 @@ public class DreamBackend { if (flattenedString.indexOf('/') < 0) { flattenedString = serviceInfo.packageName + "/" + flattenedString; } - return ComponentName.unflattenFromString(flattenedString); + + ComponentName cn = ComponentName.unflattenFromString(flattenedString); + + if (cn == null) return null; + if (!cn.getPackageName().equals(serviceInfo.packageName)) { + Log.w(TAG, + "Inconsistent package name in component: " + cn.getPackageName() + + ", should be: " + serviceInfo.packageName); + return null; + } + + return cn; } private static DreamMetadata getDreamMetadata(PackageManager pm, ResolveInfo resolveInfo) { diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt index 29221aa04699..208825ccc8cf 100644 --- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt +++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt @@ -103,6 +103,20 @@ enum class Style(internal val coreSpec: CoreSpec) { n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0)), n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 32.0)) )), + RAINBOW(CoreSpec( + a1 = TonalSpec(chroma = Chroma(ChromaStrategy.GTE, 48.0)), + a2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0)), + a3 = TonalSpec(Hue(HueStrategy.ADD, 60.0), Chroma(ChromaStrategy.EQ, 24.0)), + n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 0.0)), + n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 0.0)) + )), + FRUIT_SALAD(CoreSpec( + a1 = TonalSpec(Hue(HueStrategy.SUBTRACT, 50.0), Chroma(ChromaStrategy.GTE, 48.0)), + a2 = TonalSpec(Hue(HueStrategy.SUBTRACT, 50.0), Chroma(ChromaStrategy.EQ, 36.0)), + a3 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 36.0)), + n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 10.0)), + n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0)) + )), } class ColorScheme( diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml index 4817d453ba0b..c58c00114262 100644 --- a/packages/SystemUI/res/layout/clipboard_overlay.xml +++ b/packages/SystemUI/res/layout/clipboard_overlay.xml @@ -14,126 +14,137 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<com.android.systemui.clipboardoverlay.DraggableConstraintLayout +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" - android:theme="@style/FloatingOverlay" android:alpha="0" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView - android:id="@+id/actions_container_background" - android:visibility="gone" - android:layout_height="0dp" - android:layout_width="0dp" - android:elevation="1dp" - android:background="@drawable/action_chip_container_background" - android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal" - app:layout_constraintBottom_toBottomOf="@+id/actions_container" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="@+id/actions_container" - app:layout_constraintEnd_toEndOf="@+id/actions_container"/> - <HorizontalScrollView - android:id="@+id/actions_container" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal" - android:paddingEnd="@dimen/overlay_action_container_padding_right" - android:paddingVertical="@dimen/overlay_action_container_padding_vertical" - android:elevation="1dp" - android:scrollbars="none" - app:layout_constraintHorizontal_bias="0" - app:layout_constraintWidth_percent="1.0" - app:layout_constraintWidth_max="wrap" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintStart_toEndOf="@+id/preview_border" - app:layout_constraintEnd_toEndOf="parent"> - <LinearLayout - android:id="@+id/actions" + android:id="@+id/background_protection" + android:layout_height="@dimen/overlay_bg_protection_height" + android:layout_width="match_parent" + android:layout_gravity="bottom" + android:src="@drawable/overlay_actions_background_protection"/> + <com.android.systemui.clipboardoverlay.DraggableConstraintLayout + android:id="@+id/clipboard_ui" + android:theme="@style/FloatingOverlay" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <ImageView + android:id="@+id/actions_container_background" + android:visibility="gone" + android:layout_height="0dp" + android:layout_width="0dp" + android:elevation="1dp" + android:background="@drawable/action_chip_container_background" + android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal" + app:layout_constraintBottom_toBottomOf="@+id/actions_container" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="@+id/actions_container" + app:layout_constraintEnd_toEndOf="@+id/actions_container"/> + <HorizontalScrollView + android:id="@+id/actions_container" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal" + android:paddingEnd="@dimen/overlay_action_container_padding_right" + android:paddingVertical="@dimen/overlay_action_container_padding_vertical" + android:elevation="1dp" + android:scrollbars="none" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintWidth_percent="1.0" + app:layout_constraintWidth_max="wrap" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toEndOf="@+id/preview_border" + app:layout_constraintEnd_toEndOf="parent"> + <LinearLayout + android:id="@+id/actions" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:animateLayoutChanges="true"> + <include layout="@layout/overlay_action_chip" + android:id="@+id/remote_copy_chip"/> + <include layout="@layout/overlay_action_chip" + android:id="@+id/edit_chip"/> + </LinearLayout> + </HorizontalScrollView> + <View + android:id="@+id/preview_border" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_marginStart="@dimen/overlay_offset_x" + android:layout_marginBottom="@dimen/overlay_offset_y" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintBottom_toBottomOf="@id/actions_container_background" + android:elevation="@dimen/overlay_preview_elevation" + app:layout_constraintEnd_toEndOf="@id/clipboard_preview_end" + app:layout_constraintTop_toTopOf="@id/clipboard_preview_top" + android:background="@drawable/overlay_border"/> + <androidx.constraintlayout.widget.Barrier + android:id="@+id/clipboard_preview_end" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:animateLayoutChanges="true"> - <include layout="@layout/overlay_action_chip" - android:id="@+id/remote_copy_chip"/> - <include layout="@layout/overlay_action_chip" - android:id="@+id/edit_chip"/> - </LinearLayout> - </HorizontalScrollView> - <View - android:id="@+id/preview_border" - android:layout_width="0dp" - android:layout_height="0dp" - android:layout_marginStart="@dimen/overlay_offset_x" - android:layout_marginBottom="@dimen/overlay_offset_y" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintBottom_toBottomOf="@id/actions_container_background" - android:elevation="@dimen/overlay_preview_elevation" - app:layout_constraintEnd_toEndOf="@id/clipboard_preview_end" - app:layout_constraintTop_toTopOf="@id/clipboard_preview_top" - android:background="@drawable/overlay_border"/> - <androidx.constraintlayout.widget.Barrier - android:id="@+id/clipboard_preview_end" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:barrierMargin="@dimen/overlay_border_width" - app:barrierDirection="end" - app:constraint_referenced_ids="clipboard_preview"/> - <androidx.constraintlayout.widget.Barrier - android:id="@+id/clipboard_preview_top" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:barrierDirection="top" - app:barrierMargin="@dimen/overlay_border_width_neg" - app:constraint_referenced_ids="clipboard_preview"/> - <FrameLayout - android:id="@+id/clipboard_preview" - android:elevation="@dimen/overlay_preview_elevation" - android:background="@drawable/overlay_preview_background" - android:clipChildren="true" - android:clipToOutline="true" - android:clipToPadding="true" - android:layout_width="@dimen/clipboard_preview_size" - android:layout_margin="@dimen/overlay_border_width" - android:layout_height="wrap_content" - android:layout_gravity="center" - app:layout_constraintBottom_toBottomOf="@id/preview_border" - app:layout_constraintStart_toStartOf="@id/preview_border" - app:layout_constraintEnd_toEndOf="@id/preview_border" - app:layout_constraintTop_toTopOf="@id/preview_border"> - <TextView android:id="@+id/text_preview" - android:textFontWeight="500" - android:padding="8dp" - android:gravity="center|start" - android:ellipsize="end" - android:autoSizeTextType="uniform" - android:autoSizeMinTextSize="10sp" - android:autoSizeMaxTextSize="200sp" - android:textColor="?android:attr/textColorPrimary" - android:layout_width="@dimen/clipboard_preview_size" - android:layout_height="@dimen/clipboard_preview_size"/> - <ImageView - android:id="@+id/image_preview" - android:scaleType="fitCenter" - android:adjustViewBounds="true" - android:layout_width="match_parent" - android:layout_height="wrap_content"/> - </FrameLayout> - <FrameLayout - android:id="@+id/dismiss_button" - android:layout_width="@dimen/overlay_dismiss_button_tappable_size" - android:layout_height="@dimen/overlay_dismiss_button_tappable_size" - android:elevation="@dimen/overlay_dismiss_button_elevation" - android:visibility="gone" - app:layout_constraintStart_toEndOf="@id/clipboard_preview" - app:layout_constraintEnd_toEndOf="@id/clipboard_preview" - app:layout_constraintTop_toTopOf="@id/clipboard_preview" - app:layout_constraintBottom_toTopOf="@id/clipboard_preview" - android:contentDescription="@string/clipboard_dismiss_description"> - <ImageView - android:id="@+id/dismiss_image" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_margin="@dimen/overlay_dismiss_button_margin" - android:src="@drawable/overlay_cancel"/> - </FrameLayout> -</com.android.systemui.clipboardoverlay.DraggableConstraintLayout> + app:barrierMargin="@dimen/overlay_border_width" + app:barrierDirection="end" + app:constraint_referenced_ids="clipboard_preview"/> + <androidx.constraintlayout.widget.Barrier + android:id="@+id/clipboard_preview_top" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:barrierDirection="top" + app:barrierMargin="@dimen/overlay_border_width_neg" + app:constraint_referenced_ids="clipboard_preview"/> + <FrameLayout + android:id="@+id/clipboard_preview" + android:elevation="@dimen/overlay_preview_elevation" + android:background="@drawable/overlay_preview_background" + android:clipChildren="true" + android:clipToOutline="true" + android:clipToPadding="true" + android:layout_width="@dimen/clipboard_preview_size" + android:layout_margin="@dimen/overlay_border_width" + android:layout_height="wrap_content" + android:layout_gravity="center" + app:layout_constraintBottom_toBottomOf="@id/preview_border" + app:layout_constraintStart_toStartOf="@id/preview_border" + app:layout_constraintEnd_toEndOf="@id/preview_border" + app:layout_constraintTop_toTopOf="@id/preview_border"> + <TextView android:id="@+id/text_preview" + android:textFontWeight="500" + android:padding="8dp" + android:gravity="center|start" + android:ellipsize="end" + android:autoSizeTextType="uniform" + android:autoSizeMinTextSize="10sp" + android:autoSizeMaxTextSize="200sp" + android:textColor="?android:attr/textColorPrimary" + android:layout_width="@dimen/clipboard_preview_size" + android:layout_height="@dimen/clipboard_preview_size"/> + <ImageView + android:id="@+id/image_preview" + android:scaleType="fitCenter" + android:adjustViewBounds="true" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + </FrameLayout> + <FrameLayout + android:id="@+id/dismiss_button" + android:layout_width="@dimen/overlay_dismiss_button_tappable_size" + android:layout_height="@dimen/overlay_dismiss_button_tappable_size" + android:elevation="@dimen/overlay_dismiss_button_elevation" + android:visibility="gone" + app:layout_constraintStart_toEndOf="@id/clipboard_preview" + app:layout_constraintEnd_toEndOf="@id/clipboard_preview" + app:layout_constraintTop_toTopOf="@id/clipboard_preview" + app:layout_constraintBottom_toTopOf="@id/clipboard_preview" + android:contentDescription="@string/clipboard_dismiss_description"> + <ImageView + android:id="@+id/dismiss_image" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_margin="@dimen/overlay_dismiss_button_margin" + android:src="@drawable/overlay_cancel"/> + </FrameLayout> + </com.android.systemui.clipboardoverlay.DraggableConstraintLayout> +</FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 4dca0b0076d9..e5cabb0ecac0 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2379,11 +2379,15 @@ <string name="fgs_manager_dialog_title">Active apps</string> <!-- Label of the button to stop an app from running [CHAR LIMIT=12]--> <string name="fgs_manager_app_item_stop_button_label">Stop</string> + <!-- Label of the button to stop an app from running but the app is already stopped and the button is disabled [CHAR LIMIT=12]--> + <string name="fgs_manager_app_item_stop_button_stopped_label">Stopped</string> <!-- Label for button to copy edited text back to the clipboard [CHAR LIMIT=20] --> <string name="clipboard_edit_text_copy">Copy</string> <!-- Text informing user that content has been copied to the system clipboard [CHAR LIMIT=NONE] --> <string name="clipboard_overlay_text_copied">Copied</string> + <!-- Text informing user where text being edited was copied from [CHAR LIMIT=NONE] --> + <string name="clipboard_edit_source">From <xliff:g id="appName" example="Gmail">%1$s</xliff:g></string> <!-- Label for button to dismiss clipboard overlay [CHAR LIMIT=NONE] --> <string name="clipboard_dismiss_description">Dismiss copy UI</string> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java index 72b40d42b7b8..54664f2fdd93 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java @@ -48,7 +48,7 @@ public class ClipboardListener extends CoreStartable @Override public void start() { if (DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED, false)) { + DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED, true)) { mClipboardManager = requireNonNull(mContext.getSystemService(ClipboardManager.class)); mClipboardManager.addPrimaryClipChangedListener(this); } diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java index 8b549b43019f..f6d64643b3cd 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java @@ -103,6 +103,7 @@ public class ClipboardOverlayController { private final AccessibilityManager mAccessibilityManager; private final TextClassifier mTextClassifier; + private final FrameLayout mContainer; private final DraggableConstraintLayout mView; private final ImageView mImagePreview; private final TextView mTextPreview; @@ -147,8 +148,9 @@ public class ClipboardOverlayController { mWindow = FloatingWindowUtil.getFloatingWindow(mContext); mWindow.setWindowManager(mWindowManager, null, null); - mView = (DraggableConstraintLayout) + mContainer = (FrameLayout) LayoutInflater.from(mContext).inflate(R.layout.clipboard_overlay, null); + mView = requireNonNull(mContainer.findViewById(R.id.clipboard_ui)); mActionContainerBackground = requireNonNull(mView.findViewById(R.id.actions_container_background)); mActionContainer = requireNonNull(mView.findViewById(R.id.actions)); @@ -180,7 +182,7 @@ public class ClipboardOverlayController { attachWindow(); withWindowAttached(() -> { - mWindow.setContentView(mView); + mWindow.setContentView(mContainer); updateInsets(mWindowManager.getCurrentWindowMetrics().getWindowInsets()); mView.requestLayout(); mView.post(this::animateIn); @@ -371,7 +373,7 @@ public class ClipboardOverlayController { private ValueAnimator getEnterAnimation() { ValueAnimator anim = ValueAnimator.ofFloat(0, 1); - mView.setAlpha(0); + mContainer.setAlpha(0); mDismissButton.setVisibility(View.GONE); final View previewBorder = requireNonNull(mView.findViewById(R.id.preview_border)); final View actionBackground = requireNonNull( @@ -383,7 +385,7 @@ public class ClipboardOverlayController { } anim.addUpdateListener(animation -> { - mView.setAlpha(animation.getAnimatedFraction()); + mContainer.setAlpha(animation.getAnimatedFraction()); float scale = 0.6f + 0.4f * animation.getAnimatedFraction(); mView.setPivotY(mView.getHeight() - previewBorder.getHeight() / 2f); mView.setPivotX(actionBackground.getWidth() / 2f); @@ -394,7 +396,7 @@ public class ClipboardOverlayController { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); - mView.setAlpha(1); + mContainer.setAlpha(1); mTimeoutHandler.resetTimeout(); } }); @@ -439,7 +441,7 @@ public class ClipboardOverlayController { private void reset() { mView.setTranslationX(0); - mView.setAlpha(0); + mContainer.setAlpha(0); resetActionChips(); mTimeoutHandler.cancelTimeout(); } diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java index be10c359f3c1..a57a1351779f 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java @@ -22,9 +22,12 @@ import android.app.Activity; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Intent; +import android.content.pm.PackageManager; import android.os.Bundle; +import android.util.Log; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; +import android.widget.TextView; import com.android.systemui.R; @@ -32,8 +35,11 @@ import com.android.systemui.R; * Lightweight activity for editing text clipboard contents */ public class EditTextActivity extends Activity { + private static final String TAG = "EditTextActivity"; + private EditText mEditText; private ClipboardManager mClipboardManager; + private TextView mAttribution; @Override protected void onCreate(Bundle savedInstanceState) { @@ -42,6 +48,7 @@ public class EditTextActivity extends Activity { findViewById(R.id.copy_button).setOnClickListener((v) -> saveToClipboard()); findViewById(R.id.share).setOnClickListener((v) -> share()); mEditText = findViewById(R.id.edit_text); + mAttribution = findViewById(R.id.attribution); mClipboardManager = requireNonNull(getSystemService(ClipboardManager.class)); } @@ -53,7 +60,15 @@ public class EditTextActivity extends Activity { finish(); return; } - // TODO: put clip attribution in R.id.attribution TextView + PackageManager pm = getApplicationContext().getPackageManager(); + try { + CharSequence label = pm.getApplicationLabel( + pm.getApplicationInfo(mClipboardManager.getPrimaryClipSource(), + PackageManager.ApplicationInfoFlags.of(0))); + mAttribution.setText(getResources().getString(R.string.clipboard_edit_source, label)); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Package not found: " + mClipboardManager.getPrimaryClipSource(), e); + } mEditText.setText(clip.getItemAt(0).getText()); mEditText.requestFocus(); } diff --git a/packages/SystemUI/src/com/android/systemui/fgsmanager/FgsManagerDialog.kt b/packages/SystemUI/src/com/android/systemui/fgsmanager/FgsManagerDialog.kt deleted file mode 100644 index 42f3512129a3..000000000000 --- a/packages/SystemUI/src/com/android/systemui/fgsmanager/FgsManagerDialog.kt +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2021 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.fgsmanager - -import android.content.Context -import android.os.Bundle -import android.text.format.DateUtils -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.Button -import android.widget.ImageView -import android.widget.TextView -import androidx.annotation.GuardedBy -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import com.android.systemui.R -import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.fgsmanager.FgsManagerDialogController.RunningApp -import com.android.systemui.statusbar.phone.SystemUIDialog -import com.android.systemui.util.time.SystemClock -import java.util.concurrent.Executor - -/** - * Dialog which shows a list of running foreground services and offers controls to them - */ -class FgsManagerDialog( - context: Context, - private val executor: Executor, - @Background private val backgroundExecutor: Executor, - private val systemClock: SystemClock, - private val fgsManagerDialogController: FgsManagerDialogController -) : SystemUIDialog(context, R.style.Theme_SystemUI_Dialog) { - - private val appListRecyclerView: RecyclerView = RecyclerView(this.context) - private val adapter: AppListAdapter = AppListAdapter() - - init { - setTitle(R.string.fgs_manager_dialog_title) - setView(appListRecyclerView) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - appListRecyclerView.layoutManager = LinearLayoutManager(context) - fgsManagerDialogController.registerDialogForChanges( - object : FgsManagerDialogController.FgsManagerDialogCallback { - override fun onRunningAppsChanged(apps: List<RunningApp>) { - executor.execute { - adapter.setData(apps) - } - } - } - ) - appListRecyclerView.adapter = adapter - backgroundExecutor.execute { adapter.setData(fgsManagerDialogController.runningAppList) } - } - - private inner class AppListAdapter : RecyclerView.Adapter<AppItemViewHolder>() { - private val lock = Any() - - @GuardedBy("lock") - private val data: MutableList<RunningApp> = ArrayList() - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppItemViewHolder { - return AppItemViewHolder(LayoutInflater.from(context) - .inflate(R.layout.fgs_manager_app_item, parent, false)) - } - - override fun onBindViewHolder(holder: AppItemViewHolder, position: Int) { - var runningApp: RunningApp - synchronized(lock) { - runningApp = data[position] - } - with(holder) { - iconView.setImageDrawable(runningApp.mIcon) - appLabelView.text = runningApp.mAppLabel - durationView.text = DateUtils.formatDuration( - Math.max(systemClock.elapsedRealtime() - runningApp.mTimeStarted, 60000), - DateUtils.LENGTH_MEDIUM) - stopButton.setOnClickListener { - fgsManagerDialogController - .stopAllFgs(runningApp.mUserId, runningApp.mPackageName) - } - } - } - - override fun getItemCount(): Int { - synchronized(lock) { return data.size } - } - - fun setData(newData: List<RunningApp>) { - var oldData: List<RunningApp> - synchronized(lock) { - oldData = ArrayList(data) - data.clear() - data.addAll(newData) - } - - DiffUtil.calculateDiff(object : DiffUtil.Callback() { - override fun getOldListSize(): Int { - return oldData.size - } - - override fun getNewListSize(): Int { - return newData.size - } - - override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): - Boolean { - return oldData[oldItemPosition] == newData[newItemPosition] - } - - override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): - Boolean { - return true // TODO, look into updating the time subtext - } - }).dispatchUpdatesTo(this) - } - } - - private class AppItemViewHolder(parent: View) : RecyclerView.ViewHolder(parent) { - val appLabelView: TextView = parent.requireViewById(R.id.fgs_manager_app_item_label) - val durationView: TextView = parent.requireViewById(R.id.fgs_manager_app_item_duration) - val iconView: ImageView = parent.requireViewById(R.id.fgs_manager_app_item_icon) - val stopButton: Button = parent.requireViewById(R.id.fgs_manager_app_item_stop_button) - } -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/fgsmanager/FgsManagerDialogController.kt b/packages/SystemUI/src/com/android/systemui/fgsmanager/FgsManagerDialogController.kt deleted file mode 100644 index 159ed39025a1..000000000000 --- a/packages/SystemUI/src/com/android/systemui/fgsmanager/FgsManagerDialogController.kt +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (C) 2021 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.fgsmanager - -import android.content.pm.PackageManager -import android.content.pm.PackageManager.NameNotFoundException -import android.graphics.drawable.Drawable -import android.os.Handler -import android.os.UserHandle -import android.util.ArrayMap -import android.util.Log -import androidx.annotation.GuardedBy -import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.statusbar.policy.RunningFgsController -import com.android.systemui.statusbar.policy.RunningFgsController.UserPackageTime -import javax.inject.Inject - -/** - * Controls events relevant to FgsManagerDialog - */ -class FgsManagerDialogController @Inject constructor( - private val packageManager: PackageManager, - @Background private val backgroundHandler: Handler, - private val runningFgsController: RunningFgsController -) : RunningFgsController.Callback { - private val lock = Any() - private val clearCacheToken = Any() - - @GuardedBy("lock") - private var runningApps: Map<UserPackageTime, RunningApp>? = null - @GuardedBy("lock") - private var listener: FgsManagerDialogCallback? = null - - interface FgsManagerDialogCallback { - fun onRunningAppsChanged(apps: List<RunningApp>) - } - - data class RunningApp( - val mUserId: Int, - val mPackageName: String, - val mAppLabel: CharSequence, - val mIcon: Drawable, - val mTimeStarted: Long - ) - - val runningAppList: List<RunningApp> - get() { - synchronized(lock) { - if (runningApps == null) { - onFgsPackagesChangedLocked(runningFgsController.getPackagesWithFgs()) - } - return convertToRunningAppList(runningApps!!) - } - } - - fun registerDialogForChanges(callback: FgsManagerDialogCallback) { - synchronized(lock) { - runningFgsController.addCallback(this) - listener = callback - backgroundHandler.removeCallbacksAndMessages(clearCacheToken) - } - } - - fun onFinishDialog() { - synchronized(lock) { - listener = null - // Keep data such as icons cached for some time since loading can be slow - backgroundHandler.postDelayed( - { - synchronized(lock) { - runningFgsController.removeCallback(this) - runningApps = null - } - }, clearCacheToken, RUNNING_APP_CACHE_TIMEOUT_MILLIS) - } - } - - private fun onRunningAppsChanged(apps: ArrayMap<UserPackageTime, RunningApp>) { - listener?.let { - backgroundHandler.post { it.onRunningAppsChanged(convertToRunningAppList(apps)) } - } - } - - override fun onFgsPackagesChanged(packages: List<UserPackageTime>) { - backgroundHandler.post { - synchronized(lock) { onFgsPackagesChangedLocked(packages) } - } - } - - /** - * Run on background thread - */ - private fun onFgsPackagesChangedLocked(packages: List<UserPackageTime>) { - val newRunningApps = ArrayMap<UserPackageTime, RunningApp>() - for (packageWithFgs in packages) { - val ra = runningApps?.get(packageWithFgs) - if (ra == null) { - val userId = packageWithFgs.userId - val packageName = packageWithFgs.packageName - try { - val ai = packageManager.getApplicationInfo(packageName, 0) - var icon = packageManager.getApplicationIcon(ai) - icon = packageManager.getUserBadgedIcon(icon, - UserHandle.of(userId)) - val label = packageManager.getApplicationLabel(ai) - newRunningApps[packageWithFgs] = RunningApp(userId, packageName, - label, icon, packageWithFgs.startTimeMillis) - } catch (e: NameNotFoundException) { - Log.e(LOG_TAG, - "Application info not found: $packageName", e) - } - } else { - newRunningApps[packageWithFgs] = ra - } - } - runningApps = newRunningApps - onRunningAppsChanged(newRunningApps) - } - - fun stopAllFgs(userId: Int, packageName: String) { - runningFgsController.stopFgs(userId, packageName) - } - - companion object { - private val LOG_TAG = FgsManagerDialogController::class.java.simpleName - private const val RUNNING_APP_CACHE_TIMEOUT_MILLIS: Long = 20_000 - - private fun convertToRunningAppList(apps: Map<UserPackageTime, RunningApp>): - List<RunningApp> { - val result = mutableListOf<RunningApp>() - result.addAll(apps.values) - result.sortWith { a: RunningApp, b: RunningApp -> - b.mTimeStarted.compareTo(a.mTimeStarted) - } - return result - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/fgsmanager/FgsManagerDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/fgsmanager/FgsManagerDialogFactory.kt deleted file mode 100644 index 28749296c4cf..000000000000 --- a/packages/SystemUI/src/com/android/systemui/fgsmanager/FgsManagerDialogFactory.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2021 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.fgsmanager - -import android.content.Context -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.animation.DialogLaunchAnimator -import android.content.DialogInterface -import android.view.View -import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.util.time.SystemClock -import java.util.concurrent.Executor -import javax.inject.Inject - -/** - * Factory to create [FgsManagerDialog] instances - */ -@SysUISingleton -class FgsManagerDialogFactory -@Inject constructor( - private val context: Context, - @Main private val executor: Executor, - @Background private val backgroundExecutor: Executor, - private val systemClock: SystemClock, - private val dialogLaunchAnimator: DialogLaunchAnimator, - private val fgsManagerDialogController: FgsManagerDialogController -) { - - val lock = Any() - - companion object { - private var fgsManagerDialog: FgsManagerDialog? = null - } - - /** - * Creates the dialog if it doesn't exist - */ - fun create(viewLaunchedFrom: View?) { - if (fgsManagerDialog == null) { - fgsManagerDialog = FgsManagerDialog(context, executor, backgroundExecutor, - systemClock, fgsManagerDialogController) - fgsManagerDialog!!.setOnDismissListener { i: DialogInterface? -> - fgsManagerDialogController.onFinishDialog() - fgsManagerDialog = null - } - dialogLaunchAnimator.showFromView(fgsManagerDialog!!, viewLaunchedFrom!!) - } - } -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java index 86845091d99c..663877ce1388 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java +++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java @@ -21,6 +21,7 @@ import android.content.Context; import android.view.WindowManager; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.media.MediaDataManager; import com.android.systemui.media.MediaHierarchyManager; import com.android.systemui.media.MediaHost; @@ -35,6 +36,7 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.commandline.CommandRegistry; import java.util.Optional; +import java.util.concurrent.Executor; import javax.inject.Named; @@ -99,13 +101,13 @@ public interface MediaModule { @SysUISingleton static Optional<MediaTttChipControllerSender> providesMediaTttChipControllerSender( MediaTttFlags mediaTttFlags, + CommandQueue commandQueue, Context context, - WindowManager windowManager, - CommandQueue commandQueue) { + WindowManager windowManager) { if (!mediaTttFlags.isMediaTttEnabled()) { return Optional.empty(); } - return Optional.of(new MediaTttChipControllerSender(context, windowManager, commandQueue)); + return Optional.of(new MediaTttChipControllerSender(commandQueue, context, windowManager)); } /** */ @@ -128,6 +130,7 @@ public interface MediaModule { MediaTttFlags mediaTttFlags, CommandRegistry commandRegistry, Context context, + @Main Executor mainExecutor, MediaTttChipControllerReceiver mediaTttChipControllerReceiver) { if (!mediaTttFlags.isMediaTttEnabled()) { return Optional.empty(); @@ -136,6 +139,7 @@ public interface MediaModule { new MediaTttCommandLineHelper( commandRegistry, context, + mainExecutor, mediaTttChipControllerReceiver)); } diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt index bbcbfba5bbe7..1ea21ce7728b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt @@ -21,13 +21,15 @@ import android.content.Context import android.graphics.Color import android.graphics.drawable.Icon import android.media.MediaRoute2Info +import android.util.Log import androidx.annotation.VisibleForTesting import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver -import com.android.systemui.media.taptotransfer.sender.MoveCloserToEndCast -import com.android.systemui.media.taptotransfer.sender.MoveCloserToStartCast +import com.android.systemui.media.taptotransfer.sender.AlmostCloseToEndCast +import com.android.systemui.media.taptotransfer.sender.AlmostCloseToStartCast import com.android.systemui.media.taptotransfer.sender.TransferFailed import com.android.systemui.media.taptotransfer.sender.TransferToReceiverTriggered import com.android.systemui.media.taptotransfer.sender.TransferToThisDeviceSucceeded @@ -36,6 +38,7 @@ import com.android.systemui.media.taptotransfer.sender.TransferToReceiverSucceed import com.android.systemui.statusbar.commandline.Command import com.android.systemui.statusbar.commandline.CommandRegistry import java.io.PrintWriter +import java.util.concurrent.Executor import javax.inject.Inject /** @@ -46,13 +49,36 @@ import javax.inject.Inject class MediaTttCommandLineHelper @Inject constructor( commandRegistry: CommandRegistry, private val context: Context, + @Main private val mainExecutor: Executor, private val mediaTttChipControllerReceiver: MediaTttChipControllerReceiver, ) { - private val appIconDrawable = + private val appIconDrawable = Icon.createWithResource(context, R.drawable.ic_avatar_user).loadDrawable(context).also { it.setTint(Color.YELLOW) } + /** + * A map from a display state string typed in the command line to the display int it represents. + */ + private val stateStringToStateInt: Map<String, Int> = mapOf( + AlmostCloseToStartCast::class.simpleName!! + to StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, + AlmostCloseToEndCast::class.simpleName!! + to StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST, + TransferToReceiverTriggered::class.simpleName!! + to StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED, + TransferToThisDeviceTriggered::class.simpleName!! + to StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED, + TransferToReceiverSucceeded::class.simpleName!! + to StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, + TransferToThisDeviceSucceeded::class.simpleName!! + to StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, + TransferFailed::class.simpleName!! + to StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED, + FAR_FROM_RECEIVER_STATE + to StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER + ) + init { commandRegistry.registerCommand(SENDER_COMMAND) { SenderCommand() } commandRegistry.registerCommand( @@ -68,22 +94,59 @@ class MediaTttCommandLineHelper @Inject constructor( .addFeature("feature") .build() + @StatusBarManager.MediaTransferSenderState + val displayState = stateStringToStateInt[args[1]] + if (displayState == null) { + pw.println("Invalid command name") + return + } + val statusBarManager = context.getSystemService(Context.STATUS_BAR_SERVICE) as StatusBarManager statusBarManager.updateMediaTapToTransferSenderDisplay( - StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, + displayState, routeInfo, - /* undoExecutor= */ null, - /* undoCallback= */ null + getUndoExecutor(displayState), + getUndoCallback(displayState) ) - // TODO(b/216318437): Migrate the rest of the callbacks to StatusBarManager. + } + + private fun getUndoExecutor( + @StatusBarManager.MediaTransferSenderState displayState: Int + ): Executor? { + return if (isSucceededState(displayState)) { + mainExecutor + } else { + null + } + } + + private fun getUndoCallback( + @StatusBarManager.MediaTransferSenderState displayState: Int + ): Runnable? { + return if (isSucceededState(displayState)) { + Runnable { Log.i(CLI_TAG, "Undo triggered for $displayState") } + } else { + null + } + } + + private fun isSucceededState( + @StatusBarManager.MediaTransferSenderState displayState: Int + ): Boolean { + return displayState == + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED || + displayState == + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED } override fun help(pw: PrintWriter) { - pw.println("Usage: adb shell cmd statusbar $SENDER_COMMAND <deviceName> <chipStatus>") + pw.println("Usage: adb shell cmd statusbar $SENDER_COMMAND <deviceName> <chipState>") } } + // TODO(b/216318437): Migrate the receiver callbacks to StatusBarManager. + /** A command to DISPLAY the media ttt chip on the RECEIVER device. */ inner class AddChipCommandReceiver : Command { override fun execute(pw: PrintWriter, args: List<String>) { @@ -110,29 +173,15 @@ class MediaTttCommandLineHelper @Inject constructor( @VisibleForTesting const val SENDER_COMMAND = "media-ttt-chip-sender" @VisibleForTesting -const val REMOVE_CHIP_COMMAND_SENDER_TAG = "media-ttt-chip-remove-sender" -@VisibleForTesting const val ADD_CHIP_COMMAND_RECEIVER_TAG = "media-ttt-chip-add-receiver" @VisibleForTesting const val REMOVE_CHIP_COMMAND_RECEIVER_TAG = "media-ttt-chip-remove-receiver" @VisibleForTesting -val MOVE_CLOSER_TO_START_CAST_COMMAND_NAME = MoveCloserToStartCast::class.simpleName!! -@VisibleForTesting -val MOVE_CLOSER_TO_END_CAST_COMMAND_NAME = MoveCloserToEndCast::class.simpleName!! -@VisibleForTesting -val TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME = TransferToReceiverTriggered::class.simpleName!! -@VisibleForTesting -val TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME = - TransferToThisDeviceTriggered::class.simpleName!! -@VisibleForTesting -val TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME = TransferToReceiverSucceeded::class.simpleName!! -@VisibleForTesting -val TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME = - TransferToThisDeviceSucceeded::class.simpleName!! -@VisibleForTesting -val TRANSFER_FAILED_COMMAND_NAME = TransferFailed::class.simpleName!! -@VisibleForTesting -val NO_LONGER_CLOSE_TO_RECEIVER_COMMAND_NAME = "NoLongerCloseToReceiver" +val FAR_FROM_RECEIVER_STATE = "FarFromReceiver" private const val APP_ICON_CONTENT_DESCRIPTION = "Fake media app icon" -private const val TAG = "MediaTapToTransferCli" +private const val CLI_TAG = "MediaTransferCli" + +private val routeInfo = MediaRoute2Info.Builder("id", "Test Name") + .addFeature("feature") + .build()
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt index 118a04ccdcd0..05baf7806bda 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt @@ -59,7 +59,7 @@ sealed class ChipStateSender( * * @property otherDeviceName the name of the other device involved in the transfer. */ -class MoveCloserToStartCast( +class AlmostCloseToStartCast( appIconDrawable: Drawable, appIconContentDescription: String, private val otherDeviceName: String, @@ -76,7 +76,7 @@ class MoveCloserToStartCast( * * @property otherDeviceName the name of the other device involved in the transfer. */ -class MoveCloserToEndCast( +class AlmostCloseToEndCast( appIconDrawable: Drawable, appIconContentDescription: String, private val otherDeviceName: String, diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt index c510e358e4a5..d1790d2fd5e1 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt @@ -21,6 +21,7 @@ import android.content.Context import android.graphics.Color import android.graphics.drawable.Icon import android.media.MediaRoute2Info +import android.util.Log import android.view.View import android.view.ViewGroup import android.view.WindowManager @@ -38,9 +39,9 @@ import javax.inject.Inject */ @SysUISingleton class MediaTttChipControllerSender @Inject constructor( + commandQueue: CommandQueue, context: Context, windowManager: WindowManager, - private val commandQueue: CommandQueue ) : MediaTttChipControllerCommon<ChipStateSender>( context, windowManager, R.layout.media_ttt_chip ) { @@ -50,25 +51,80 @@ class MediaTttChipControllerSender @Inject constructor( it.setTint(Color.YELLOW) } - private val commandQueueCallback = object : CommandQueue.Callbacks { + private val commandQueueCallbacks = object : CommandQueue.Callbacks { override fun updateMediaTapToTransferSenderDisplay( @StatusBarManager.MediaTransferSenderState displayState: Int, routeInfo: MediaRoute2Info, undoCallback: IUndoMediaTransferCallback? ) { - // TODO(b/216318437): Trigger displayChip with the right state based on displayState. - displayChip( - MoveCloserToStartCast( - // TODO(b/217418566): This app icon content description is incorrect -- - // routeInfo.name is the name of the device, not the name of the app. - fakeAppIconDrawable, routeInfo.name.toString(), routeInfo.name.toString() - ) + this@MediaTttChipControllerSender.updateMediaTapToTransferSenderDisplay( + displayState, routeInfo, undoCallback ) } } init { - commandQueue.addCallback(commandQueueCallback) + commandQueue.addCallback(commandQueueCallbacks) + } + + private fun updateMediaTapToTransferSenderDisplay( + @StatusBarManager.MediaTransferSenderState displayState: Int, + routeInfo: MediaRoute2Info, + undoCallback: IUndoMediaTransferCallback? + ) { + // TODO(b/217418566): This app icon content description is incorrect -- + // routeInfo.name is the name of the device, not the name of the app. + val appIconContentDescription = routeInfo.name.toString() + val otherDeviceName = routeInfo.name.toString() + val chipState = when(displayState) { + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST -> + AlmostCloseToStartCast( + fakeAppIconDrawable, appIconContentDescription, otherDeviceName + ) + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST -> + AlmostCloseToEndCast( + fakeAppIconDrawable, appIconContentDescription, otherDeviceName + ) + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED -> + TransferToReceiverTriggered( + fakeAppIconDrawable, appIconContentDescription, otherDeviceName + ) + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED -> + TransferToThisDeviceTriggered( + fakeAppIconDrawable, appIconContentDescription + ) + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED -> + TransferToReceiverSucceeded( + fakeAppIconDrawable, + appIconContentDescription, + otherDeviceName, + undoCallback + ) + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED -> + TransferToThisDeviceSucceeded( + fakeAppIconDrawable, + appIconContentDescription, + otherDeviceName, + undoCallback + ) + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED, + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED -> + TransferFailed( + fakeAppIconDrawable, appIconContentDescription + ) + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER -> { + removeChip() + null + } + else -> { + Log.e(SENDER_TAG, "Unhandled MediaTransferSenderState $displayState") + null + } + } + + chipState?.let { + displayChip(it) + } } /** Displays the chip view for the given state. */ @@ -97,3 +153,5 @@ class MediaTttChipControllerSender @Inject constructor( if (showFailure) { View.VISIBLE } else { View.GONE } } } + +const val SENDER_TAG = "MediaTapToTransferSender" diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt new file mode 100644 index 000000000000..eb3415639db6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt @@ -0,0 +1,430 @@ +/* + * Copyright (C) 2022 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.qs + +import android.app.IActivityManager +import android.app.IForegroundServiceObserver +import android.content.Context +import android.content.pm.PackageManager +import android.graphics.drawable.Drawable +import android.os.IBinder +import android.os.PowerExemptionManager +import android.os.RemoteException +import android.provider.DeviceConfig.NAMESPACE_SYSTEMUI +import android.text.format.DateUtils +import android.util.ArrayMap +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.GuardedBy +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED +import com.android.systemui.R +import com.android.systemui.animation.DialogLaunchAnimator +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.util.DeviceConfigProxy +import com.android.systemui.util.time.SystemClock +import java.util.Objects +import java.util.concurrent.Executor +import javax.inject.Inject +import kotlin.math.max + +class FgsManagerController @Inject constructor( + private val context: Context, + @Main private val mainExecutor: Executor, + @Background private val backgroundExecutor: Executor, + private val systemClock: SystemClock, + private val activityManager: IActivityManager, + private val packageManager: PackageManager, + private val deviceConfigProxy: DeviceConfigProxy, + private val dialogLaunchAnimator: DialogLaunchAnimator +) : IForegroundServiceObserver.Stub() { + + companion object { + private val LOG_TAG = FgsManagerController::class.java.simpleName + } + + private var isAvailable = false + + private val lock = Any() + + @GuardedBy("lock") + var initialized = false + + @GuardedBy("lock") + private val runningServiceTokens = mutableMapOf<UserPackage, StartTimeAndTokens>() + + @GuardedBy("lock") + private var dialog: SystemUIDialog? = null + + @GuardedBy("lock") + private val appListAdapter: AppListAdapter = AppListAdapter() + + @GuardedBy("lock") + private var runningApps: ArrayMap<UserPackage, RunningApp> = ArrayMap() + + interface OnNumberOfPackagesChangedListener { + fun onNumberOfPackagesChanged(numPackages: Int) + } + + interface OnDialogDismissedListener { + fun onDialogDismissed() + } + + fun init() { + synchronized(lock) { + if (initialized) { + return + } + try { + activityManager.registerForegroundServiceObserver(this) + } catch (e: RemoteException) { + e.rethrowFromSystemServer() + } + + deviceConfigProxy.addOnPropertiesChangedListener(NAMESPACE_SYSTEMUI, + backgroundExecutor) { + isAvailable = it.getBoolean(TASK_MANAGER_ENABLED, isAvailable) + } + + isAvailable = deviceConfigProxy + .getBoolean(NAMESPACE_SYSTEMUI, TASK_MANAGER_ENABLED, false) + + initialized = true + } + } + + override fun onForegroundStateChanged( + token: IBinder, + packageName: String, + userId: Int, + isForeground: Boolean + ) { + synchronized(lock) { + val numPackagesBefore = getNumRunningPackagesLocked() + val userPackageKey = UserPackage(userId, packageName) + if (isForeground) { + runningServiceTokens.getOrPut(userPackageKey, { StartTimeAndTokens(systemClock) }) + .addToken(token) + } else { + if (runningServiceTokens[userPackageKey]?.also { + it.removeToken(token) }?.isEmpty() == true) { + runningServiceTokens.remove(userPackageKey) + } + } + + val numPackagesAfter = getNumRunningPackagesLocked() + + if (numPackagesAfter != numPackagesBefore) { + onNumberOfPackagesChangedListeners.forEach { + backgroundExecutor.execute { it.onNumberOfPackagesChanged(numPackagesAfter) } + } + } + + updateAppItemsLocked() + } + } + + @GuardedBy("lock") + val onNumberOfPackagesChangedListeners: MutableSet<OnNumberOfPackagesChangedListener> = + mutableSetOf() + + @GuardedBy("lock") + val onDialogDismissedListeners: MutableSet<OnDialogDismissedListener> = mutableSetOf() + + fun addOnNumberOfPackagesChangedListener(listener: OnNumberOfPackagesChangedListener) { + synchronized(lock) { + onNumberOfPackagesChangedListeners.add(listener) + } + } + + fun removeOnNumberOfPackagesChangedListener(listener: OnNumberOfPackagesChangedListener) { + synchronized(lock) { + onNumberOfPackagesChangedListeners.remove(listener) + } + } + + fun addOnDialogDismissedListener(listener: OnDialogDismissedListener) { + synchronized(lock) { + onDialogDismissedListeners.add(listener) + } + } + + fun removeOnDialogDismissedListener(listener: OnDialogDismissedListener) { + synchronized(lock) { + onDialogDismissedListeners.remove(listener) + } + } + + fun isAvailable(): Boolean { + return isAvailable + } + + fun getNumRunningPackages(): Int { + synchronized(lock) { + return getNumRunningPackagesLocked() + } + } + + private fun getNumRunningPackagesLocked() = + runningServiceTokens.keys.count { it.uiControl != UIControl.HIDE_ENTRY } + + fun shouldUpdateFooterVisibility() = dialog == null + + fun showDialog(viewLaunchedFrom: View?) { + synchronized(lock) { + if (dialog == null) { + + val dialog = SystemUIDialog(context) + dialog.setTitle(R.string.fgs_manager_dialog_title) + + val dialogContext = dialog.context + + val recyclerView = RecyclerView(dialogContext) + recyclerView.layoutManager = LinearLayoutManager(dialogContext) + recyclerView.adapter = appListAdapter + + dialog.setView(recyclerView) + + this.dialog = dialog + + dialog.setOnDismissListener { + synchronized(lock) { + this.dialog = null + updateAppItemsLocked() + } + onDialogDismissedListeners.forEach { + mainExecutor.execute(it::onDialogDismissed) + } + } + + mainExecutor.execute { + viewLaunchedFrom + ?.let { dialogLaunchAnimator.showFromView(dialog, it) } ?: dialog.show() + } + + backgroundExecutor.execute { + synchronized(lock) { + updateAppItemsLocked() + } + } + } + } + } + + @GuardedBy("lock") + private fun updateAppItemsLocked() { + if (dialog == null) { + runningApps.clear() + return + } + + val addedPackages = runningServiceTokens.keys.filter { + it.uiControl != UIControl.HIDE_ENTRY && runningApps[it]?.stopped != true + } + val removedPackages = runningApps.keys.filter { !runningServiceTokens.containsKey(it) } + + addedPackages.forEach { + val ai = packageManager.getApplicationInfoAsUser(it.packageName, 0, it.userId) + runningApps[it] = RunningApp(it.userId, it.packageName, + runningServiceTokens[it]!!.startTime, it.uiControl, + ai.loadLabel(packageManager), ai.loadIcon(packageManager)) + } + + removedPackages.forEach { pkg -> + val ra = runningApps[pkg]!! + val ra2 = ra.copy().also { + it.stopped = true + it.appLabel = ra.appLabel + it.icon = ra.icon + } + runningApps[pkg] = ra2 + } + + mainExecutor.execute { + appListAdapter + .setData(runningApps.values.toList().sortedByDescending { it.timeStarted }) + } + } + + private fun stopPackage(userId: Int, packageName: String) { + activityManager.stopAppForUser(packageName, userId) + } + + private inner class AppListAdapter : RecyclerView.Adapter<AppItemViewHolder>() { + private val lock = Any() + + @GuardedBy("lock") + private var data: List<RunningApp> = listOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppItemViewHolder { + return AppItemViewHolder(LayoutInflater.from(parent.context) + .inflate(R.layout.fgs_manager_app_item, parent, false)) + } + + override fun onBindViewHolder(holder: AppItemViewHolder, position: Int) { + var runningApp: RunningApp + synchronized(lock) { + runningApp = data[position] + } + with(holder) { + iconView.setImageDrawable(runningApp.icon) + appLabelView.text = runningApp.appLabel + durationView.text = DateUtils.formatDuration( + max(systemClock.elapsedRealtime() - runningApp.timeStarted, 60000), + DateUtils.LENGTH_MEDIUM) + stopButton.setOnClickListener { + stopButton.setText(R.string.fgs_manager_app_item_stop_button_stopped_label) + stopPackage(runningApp.userId, runningApp.packageName) + } + if (runningApp.uiControl == UIControl.HIDE_BUTTON) { + stopButton.visibility = View.INVISIBLE + } + if (runningApp.stopped) { + stopButton.isEnabled = false + stopButton.setText(R.string.fgs_manager_app_item_stop_button_stopped_label) + durationView.visibility = View.GONE + } else { + stopButton.isEnabled = true + stopButton.setText(R.string.fgs_manager_app_item_stop_button_label) + durationView.visibility = View.VISIBLE + } + } + } + + override fun getItemCount(): Int { + return data.size + } + + fun setData(newData: List<RunningApp>) { + var oldData = data + data = newData + + DiffUtil.calculateDiff(object : DiffUtil.Callback() { + override fun getOldListSize(): Int { + return oldData.size + } + + override fun getNewListSize(): Int { + return newData.size + } + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): + Boolean { + return oldData[oldItemPosition] == newData[newItemPosition] + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): + Boolean { + return oldData[oldItemPosition].stopped == newData[newItemPosition].stopped + } + }).dispatchUpdatesTo(this) + } + } + + private inner class UserPackage( + val userId: Int, + val packageName: String + ) { + val uiControl: UIControl by lazy { + val uid = packageManager.getPackageUidAsUser(packageName, userId) + + when (activityManager.getBackgroundRestrictionExemptionReason(uid)) { + PowerExemptionManager.REASON_SYSTEM_UID, + PowerExemptionManager.REASON_DEVICE_DEMO_MODE -> UIControl.HIDE_ENTRY + + PowerExemptionManager.REASON_DEVICE_OWNER, + PowerExemptionManager.REASON_PROFILE_OWNER, + PowerExemptionManager.REASON_PROC_STATE_PERSISTENT, + PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI, + PowerExemptionManager.REASON_ROLE_DIALER, + PowerExemptionManager.REASON_SYSTEM_MODULE -> UIControl.HIDE_BUTTON + else -> UIControl.NORMAL + } + } + + override fun equals(other: Any?): Boolean { + if (other !is UserPackage) { + return false + } + return other.packageName == packageName && other.userId == userId + } + + override fun hashCode(): Int = Objects.hash(userId, packageName) + } + + private data class StartTimeAndTokens( + val systemClock: SystemClock + ) { + val startTime = systemClock.elapsedRealtime() + val tokens = mutableSetOf<IBinder>() + + fun addToken(token: IBinder) { + tokens.add(token) + } + + fun removeToken(token: IBinder) { + tokens.remove(token) + } + + fun isEmpty(): Boolean { + return tokens.isEmpty() + } + } + + private class AppItemViewHolder(parent: View) : RecyclerView.ViewHolder(parent) { + val appLabelView: TextView = parent.requireViewById(R.id.fgs_manager_app_item_label) + val durationView: TextView = parent.requireViewById(R.id.fgs_manager_app_item_duration) + val iconView: ImageView = parent.requireViewById(R.id.fgs_manager_app_item_icon) + val stopButton: Button = parent.requireViewById(R.id.fgs_manager_app_item_stop_button) + } + + private data class RunningApp( + val userId: Int, + val packageName: String, + val timeStarted: Long, + val uiControl: UIControl + ) { + constructor( + userId: Int, + packageName: String, + timeStarted: Long, + uiControl: UIControl, + appLabel: CharSequence, + icon: Drawable + ) : this(userId, packageName, timeStarted, uiControl) { + this.appLabel = appLabel + this.icon = icon + } + + // variables to keep out of the generated equals() + var appLabel: CharSequence = "" + var icon: Drawable? = null + var stopped = false + } + + private enum class UIControl { + NORMAL, HIDE_BUTTON, HIDE_ENTRY + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java index 082de1609423..55d4a53ced7b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java @@ -16,13 +16,9 @@ package com.android.systemui.qs; -import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI; - -import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED; import static com.android.systemui.qs.dagger.QSFragmentModule.QS_FGS_MANAGER_FOOTER_VIEW; import android.content.Context; -import android.provider.DeviceConfig; import android.view.View; import android.widget.ImageView; import android.widget.TextView; @@ -30,8 +26,6 @@ import android.widget.TextView; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.fgsmanager.FgsManagerDialogFactory; -import com.android.systemui.statusbar.policy.RunningFgsController; import java.util.concurrent.Executor; @@ -41,24 +35,25 @@ import javax.inject.Named; /** * Footer entry point for the foreground service manager */ -public class QSFgsManagerFooter implements View.OnClickListener { +public class QSFgsManagerFooter implements View.OnClickListener, + FgsManagerController.OnDialogDismissedListener, + FgsManagerController.OnNumberOfPackagesChangedListener { private final View mRootView; private final TextView mFooterText; private final Context mContext; private final Executor mMainExecutor; private final Executor mExecutor; - private final RunningFgsController mRunningFgsController; - private final FgsManagerDialogFactory mFgsManagerDialogFactory; + + private final FgsManagerController mFgsManagerController; private boolean mIsInitialized = false; - private boolean mIsAvailable = false; + private int mNumPackages; @Inject QSFgsManagerFooter(@Named(QS_FGS_MANAGER_FOOTER_VIEW) View rootView, - @Main Executor mainExecutor, RunningFgsController runningFgsController, - @Background Executor executor, - FgsManagerDialogFactory fgsManagerDialogFactory) { + @Main Executor mainExecutor, @Background Executor executor, + FgsManagerController fgsManagerController) { mRootView = rootView; mFooterText = mRootView.findViewById(R.id.footer_text); ImageView icon = mRootView.findViewById(R.id.primary_footer_icon); @@ -66,8 +61,7 @@ public class QSFgsManagerFooter implements View.OnClickListener { mContext = rootView.getContext(); mMainExecutor = mainExecutor; mExecutor = executor; - mRunningFgsController = runningFgsController; - mFgsManagerDialogFactory = fgsManagerDialogFactory; + mFgsManagerController = fgsManagerController; } public void init() { @@ -75,22 +69,28 @@ public class QSFgsManagerFooter implements View.OnClickListener { return; } - mRootView.setOnClickListener(this); - - mRunningFgsController.addCallback(packages -> refreshState()); + mFgsManagerController.init(); - DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_SYSTEMUI, mExecutor, - (DeviceConfig.OnPropertiesChangedListener) properties -> { - mIsAvailable = properties.getBoolean(TASK_MANAGER_ENABLED, mIsAvailable); - }); - mIsAvailable = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, TASK_MANAGER_ENABLED, false); + mRootView.setOnClickListener(this); mIsInitialized = true; } + public void setListening(boolean listening) { + if (listening) { + mFgsManagerController.addOnDialogDismissedListener(this); + mFgsManagerController.addOnNumberOfPackagesChangedListener(this); + mNumPackages = mFgsManagerController.getNumRunningPackages(); + refreshState(); + } else { + mFgsManagerController.removeOnDialogDismissedListener(this); + mFgsManagerController.removeOnNumberOfPackagesChangedListener(this); + } + } + @Override public void onClick(View view) { - mFgsManagerDialogFactory.create(mRootView); + mFgsManagerController.showDialog(mRootView); } public void refreshState() { @@ -101,17 +101,25 @@ public class QSFgsManagerFooter implements View.OnClickListener { return mRootView; } - private boolean isAvailable() { - return mIsAvailable; - } - public void handleRefreshState() { - int numPackages = mRunningFgsController.getPackagesWithFgs().size(); mMainExecutor.execute(() -> { mFooterText.setText(mContext.getResources().getQuantityString( - R.plurals.fgs_manager_footer_label, numPackages, numPackages)); - mRootView.setVisibility(numPackages > 0 && isAvailable() ? View.VISIBLE : View.GONE); + R.plurals.fgs_manager_footer_label, mNumPackages, mNumPackages)); + if (mFgsManagerController.shouldUpdateFooterVisibility()) { + mRootView.setVisibility(mNumPackages > 0 + && mFgsManagerController.isAvailable() ? View.VISIBLE : View.GONE); + } }); } + @Override + public void onDialogDismissed() { + refreshState(); + } + + @Override + public void onNumberOfPackagesChanged(int numPackages) { + mNumPackages = numPackages; + refreshState(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index cbfe944a2c23..8f268b5cffe4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -192,6 +192,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { refreshAllTiles(); } + mQSFgsManagerFooter.setListening(listening); mQsSecurityFooter.setListening(listening); // Set the listening as soon as the QS fragment starts listening regardless of the diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java index 48255b5360fc..6d1bbeed5372 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java @@ -34,8 +34,6 @@ import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.DataSaverController; import com.android.systemui.statusbar.policy.DeviceControlsController; import com.android.systemui.statusbar.policy.HotspotController; -import com.android.systemui.statusbar.policy.RunningFgsController; -import com.android.systemui.statusbar.policy.RunningFgsControllerImpl; import com.android.systemui.statusbar.policy.WalletController; import com.android.systemui.util.settings.SecureSettings; @@ -91,9 +89,4 @@ public interface QSModule { /** */ @Binds QSHost provideQsHost(QSTileHost controllerImpl); - - /** */ - @Binds - RunningFgsController provideRunningFgsController( - RunningFgsControllerImpl runningFgsController); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index 5302188ccb31..4a7606c316e2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -22,6 +22,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; +import android.app.ActivityManager; import android.app.Notification; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -33,7 +34,6 @@ import android.graphics.Color; import android.graphics.ColorMatrixColorFilter; import android.graphics.Paint; import android.graphics.Rect; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.Parcelable; @@ -57,6 +57,7 @@ import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.statusbar.notification.NotificationIconDozeHelper; import com.android.systemui.statusbar.notification.NotificationUtils; +import com.android.systemui.util.drawable.DrawableSize; import java.text.NumberFormat; import java.util.Arrays; @@ -84,16 +85,6 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi public static final int STATE_DOT = 1; public static final int STATE_HIDDEN = 2; - /** - * Maximum allowed byte count for an icon bitmap - * @see android.graphics.RecordingCanvas.MAX_BITMAP_SIZE - */ - private static final int MAX_BITMAP_SIZE = 100 * 1024 * 1024; // 100 MB - /** - * Maximum allowed width or height for an icon drawable, if we can't get byte count - */ - private static final int MAX_IMAGE_SIZE = 5000; - private static final String TAG = "StatusBarIconView"; private static final Property<StatusBarIconView, Float> ICON_APPEAR_AMOUNT = new FloatProperty<StatusBarIconView>("iconAppearAmount") { @@ -390,21 +381,6 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi return false; } - if (drawable instanceof BitmapDrawable && ((BitmapDrawable) drawable).getBitmap() != null) { - // If it's a bitmap we can check the size directly - int byteCount = ((BitmapDrawable) drawable).getBitmap().getByteCount(); - if (byteCount > MAX_BITMAP_SIZE) { - Log.w(TAG, "Drawable is too large (" + byteCount + " bytes) " + mIcon); - return false; - } - } else if (drawable.getIntrinsicWidth() > MAX_IMAGE_SIZE - || drawable.getIntrinsicHeight() > MAX_IMAGE_SIZE) { - // Otherwise, check dimensions - Log.w(TAG, "Drawable is too large (" + drawable.getIntrinsicWidth() + "x" - + drawable.getIntrinsicHeight() + ") " + mIcon); - return false; - } - if (withClear) { setImageDrawable(null); } @@ -432,7 +408,7 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi * @return Drawable for this item, or null if the package or item could not * be found */ - public static Drawable getIcon(Context sysuiContext, + private Drawable getIcon(Context sysuiContext, Context context, StatusBarIcon statusBarIcon) { int userId = statusBarIcon.user.getIdentifier(); if (userId == UserHandle.USER_ALL) { @@ -446,6 +422,16 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi typedValue, true); float scaleFactor = typedValue.getFloat(); + // We downscale the loaded drawable to reasonable size to protect against applications + // using too much memory. The size can be tweaked in config.xml. Drawables + // that are already sized properly won't be touched. + boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic(); + Resources res = sysuiContext.getResources(); + int maxIconSize = res.getDimensionPixelSize(isLowRamDevice + ? com.android.internal.R.dimen.notification_small_icon_size_low_ram + : com.android.internal.R.dimen.notification_small_icon_size); + icon = DrawableSize.downscaleToSize(res, icon, maxIconSize, maxIconSize); + // No need to scale the icon, so return it as is. if (scaleFactor == 1.f) { return icon; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt index 4de8e7a5641c..b76169f111db 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.collection.render import android.view.View +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView import com.android.systemui.statusbar.notification.stack.NotificationListContainer @@ -41,6 +42,7 @@ class RootNodeController( override fun addChildAt(child: NodeController, index: Int) { listContainer.addContainerViewAt(child.view, index) listContainer.onNotificationViewUpdateFinished() + (child.view as? ExpandableNotificationRow)?.isChangingPosition = false } override fun moveChildTo(child: NodeController, index: Int) { @@ -50,6 +52,7 @@ class RootNodeController( override fun removeChild(child: NodeController, isTransfer: Boolean) { if (isTransfer) { listContainer.setChildTransferInProgress(true) + (child.view as? ExpandableNotificationRow)?.isChangingPosition = true } listContainer.removeContainerView(child.view) if (isTransfer) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java index 46efef66de43..2436ffdbeefa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -248,19 +248,25 @@ public class ExpandableNotificationRowController implements NotifViewController mView.addChildNotification((ExpandableNotificationRow) child.getView(), index); mListContainer.notifyGroupChildAdded(childView); + childView.setChangingPosition(false); } @Override public void moveChildTo(NodeController child, int index) { ExpandableNotificationRow childView = (ExpandableNotificationRow) child.getView(); + childView.setChangingPosition(true); mView.removeChildNotification(childView); mView.addChildNotification(childView, index); + childView.setChangingPosition(false); } @Override public void removeChild(NodeController child, boolean isTransfer) { ExpandableNotificationRow childView = (ExpandableNotificationRow) child.getView(); + if (isTransfer) { + childView.setChangingPosition(true); + } mView.removeChildNotification(childView); if (!isTransfer) { mListContainer.notifyGroupChildRemoved(childView, mView); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RunningFgsController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RunningFgsController.kt deleted file mode 100644 index c6dbdb1b09a5..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RunningFgsController.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2021 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.policy - -/** - * Interface for tracking packages with running foreground services and demoting foreground status - */ -interface RunningFgsController : CallbackController<RunningFgsController.Callback> { - - /** - * @return A list of [UserPackageTime]s which have running foreground service(s) - */ - fun getPackagesWithFgs(): List<UserPackageTime> - - /** - * Stops all foreground services running as a package - * @param userId the userId the package is running under - * @param packageName the packageName - */ - fun stopFgs(userId: Int, packageName: String) - - /** - * Returns when the list of packages with foreground services changes - */ - interface Callback { - /** - * The thing that - * @param packages the list of packages - */ - fun onFgsPackagesChanged(packages: List<UserPackageTime>) - } - - /** - * A triplet <user, packageName, timeMillis> where each element is a package running - * under a user that has had at least one foreground service running since timeMillis. - * Time should be derived from [SystemClock.elapsedRealtime]. - */ - data class UserPackageTime(val userId: Int, val packageName: String, val startTimeMillis: Long) -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RunningFgsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RunningFgsControllerImpl.kt deleted file mode 100644 index 6d3345df4f3e..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RunningFgsControllerImpl.kt +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (C) 2021 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.policy - -import android.app.IActivityManager -import android.app.IForegroundServiceObserver -import android.os.IBinder -import android.os.RemoteException -import android.util.Log -import androidx.annotation.GuardedBy -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.statusbar.policy.RunningFgsController.Callback -import com.android.systemui.statusbar.policy.RunningFgsController.UserPackageTime -import com.android.systemui.util.time.SystemClock -import java.util.concurrent.Executor -import javax.inject.Inject - -/** - * Implementation for [RunningFgsController] - */ -@SysUISingleton -class RunningFgsControllerImpl @Inject constructor( - @Background private val executor: Executor, - private val systemClock: SystemClock, - private val activityManager: IActivityManager -) : RunningFgsController, IForegroundServiceObserver.Stub() { - - companion object { - private val LOG_TAG = RunningFgsControllerImpl::class.java.simpleName - } - - private val lock = Any() - - @GuardedBy("lock") - var initialized = false - - @GuardedBy("lock") - private val runningServiceTokens = mutableMapOf<UserPackageKey, StartTimeAndTokensValue>() - - @GuardedBy("lock") - private val callbacks = mutableSetOf<Callback>() - - fun init() { - synchronized(lock) { - if (initialized) { - return - } - try { - activityManager.registerForegroundServiceObserver(this) - } catch (e: RemoteException) { - e.rethrowFromSystemServer() - } - - initialized = true - } - } - - override fun addCallback(listener: Callback) { - init() - synchronized(lock) { callbacks.add(listener) } - } - - override fun removeCallback(listener: Callback) { - init() - synchronized(lock) { - if (!callbacks.remove(listener)) { - Log.e(LOG_TAG, "Callback was not registered.", RuntimeException()) - } - } - } - - override fun observe(lifecycle: Lifecycle?, listener: Callback?): Callback { - init() - return super.observe(lifecycle, listener) - } - - override fun observe(owner: LifecycleOwner?, listener: Callback?): Callback { - init() - return super.observe(owner, listener) - } - - override fun getPackagesWithFgs(): List<UserPackageTime> { - init() - return synchronized(lock) { getPackagesWithFgsLocked() } - } - - private fun getPackagesWithFgsLocked(): List<UserPackageTime> = - runningServiceTokens.map { - UserPackageTime(it.key.userId, it.key.packageName, it.value.fgsStartTime) - } - - override fun stopFgs(userId: Int, packageName: String) { - init() - try { - activityManager.stopAppForUser(packageName, userId) - } catch (e: RemoteException) { - e.rethrowFromSystemServer() - } - } - - private data class UserPackageKey( - val userId: Int, - val packageName: String - ) - - private class StartTimeAndTokensValue(systemClock: SystemClock) { - val fgsStartTime = systemClock.elapsedRealtime() - var tokens = mutableSetOf<IBinder>() - fun addToken(token: IBinder): Boolean { - return tokens.add(token) - } - - fun removeToken(token: IBinder): Boolean { - return tokens.remove(token) - } - - val isEmpty: Boolean - get() = tokens.isEmpty() - } - - override fun onForegroundStateChanged( - token: IBinder, - packageName: String, - userId: Int, - isForeground: Boolean - ) { - val result = synchronized(lock) { - val userPackageKey = UserPackageKey(userId, packageName) - if (isForeground) { - var addedNew = false - runningServiceTokens.getOrPut(userPackageKey) { - addedNew = true - StartTimeAndTokensValue(systemClock) - }.addToken(token) - if (!addedNew) { - return - } - } else { - val startTimeAndTokensValue = runningServiceTokens[userPackageKey] - if (startTimeAndTokensValue?.removeToken(token) == false) { - Log.e(LOG_TAG, - "Stopped foreground service was not known to be running.") - return - } - if (!startTimeAndTokensValue!!.isEmpty) { - return - } - runningServiceTokens.remove(userPackageKey) - } - getPackagesWithFgsLocked().toList() - } - - callbacks.forEach { executor.execute { it.onFgsPackagesChanged(result) } } - } -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/util/drawable/DrawableSize.kt b/packages/SystemUI/src/com/android/systemui/util/drawable/DrawableSize.kt new file mode 100644 index 000000000000..b5068087d905 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/drawable/DrawableSize.kt @@ -0,0 +1,123 @@ +package com.android.systemui.util.drawable + +import android.content.res.Resources +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.drawable.Animatable +import android.graphics.drawable.Animatable2 +import android.graphics.drawable.AnimatedImageDrawable +import android.graphics.drawable.AnimatedRotateDrawable +import android.graphics.drawable.AnimatedStateListDrawable +import android.graphics.drawable.AnimatedVectorDrawable +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import android.util.Log +import androidx.annotation.Px +import com.android.systemui.util.traceSection + +class DrawableSize { + + companion object { + + const val TAG = "SysUiDrawableSize" + + /** + * Downscales passed Drawable to set maximum width and height. This will only + * be done for Drawables that can be downscaled non-destructively - e.g. animated + * and stateful drawables will no be downscaled. + * + * Downscaling will keep the aspect ratio. + * This method will not touch drawables that already fit into size specification. + * + * @param resources Resources on which to base the density of resized drawable. + * @param drawable Drawable to downscale. + * @param maxWidth Maximum width of the downscaled drawable. + * @param maxHeight Maximum height of the downscaled drawable. + * + * @return returns downscaled drawable if it's possible to downscale it or original if it's + * not. + */ + @JvmStatic + fun downscaleToSize( + res: Resources, + drawable: Drawable, + @Px maxWidth: Int, + @Px maxHeight: Int + ): Drawable { + traceSection("DrawableSize#downscaleToSize") { + // Bitmap drawables can contain big bitmaps as their content while sneaking it past + // us using density scaling. Inspect inside the Bitmap drawables for actual bitmap + // size for those. + val originalWidth = (drawable as? BitmapDrawable)?.bitmap?.width + ?: drawable.intrinsicWidth + val originalHeight = (drawable as? BitmapDrawable)?.bitmap?.height + ?: drawable.intrinsicHeight + + // Don't touch drawable if we can't resolve sizes for whatever reason. + if (originalWidth <= 0 || originalHeight <= 0) { + return drawable + } + + // Do not touch drawables that are already within bounds. + if (originalWidth < maxWidth && originalHeight < maxHeight) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Not resizing $originalWidth x $originalHeight" + " " + + "to $maxWidth x $maxHeight") + } + + return drawable + } + + if (!isSimpleBitmap(drawable)) { + return drawable + } + + val scaleWidth = maxWidth.toFloat() / originalWidth.toFloat() + val scaleHeight = maxHeight.toFloat() / originalHeight.toFloat() + val scale = minOf(scaleHeight, scaleWidth) + + val width = (originalWidth * scale).toInt() + val height = (originalHeight * scale).toInt() + + if (width <= 0 || height <= 0) { + Log.w(TAG, "Attempted to resize ${drawable.javaClass.simpleName} " + + "from $originalWidth x $originalHeight to invalid $width x $height.") + return drawable + } + + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Resizing large drawable (${drawable.javaClass.simpleName}) " + + "from $originalWidth x $originalHeight to $width x $height") + } + + // We want to keep existing config if it's more efficient than 32-bit RGB. + val config = (drawable as? BitmapDrawable)?.bitmap?.config + ?: Bitmap.Config.ARGB_8888 + val scaledDrawableBitmap = Bitmap.createBitmap(width, height, config) + val canvas = Canvas(scaledDrawableBitmap) + + val originalBounds = drawable.bounds + drawable.setBounds(0, 0, width, height) + drawable.draw(canvas) + drawable.bounds = originalBounds + + return BitmapDrawable(res, scaledDrawableBitmap) + } + } + + private fun isSimpleBitmap(drawable: Drawable): Boolean { + return !(drawable.isStateful || isAnimated(drawable)) + } + + private fun isAnimated(drawable: Drawable): Boolean { + if (drawable is Animatable || drawable is Animatable2) { + return true + } + + return drawable is AnimatedImageDrawable || + drawable is AnimatedRotateDrawable || + drawable is AnimatedStateListDrawable || + drawable is AnimatedVectorDrawable + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt index 5f800eb80ec2..c3d8d245baed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt @@ -16,24 +16,35 @@ package com.android.systemui.media.taptotransfer -import android.content.ComponentName +import android.app.StatusBarManager +import android.content.Context +import android.media.MediaRoute2Info import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver +import com.android.systemui.media.taptotransfer.sender.AlmostCloseToEndCast +import com.android.systemui.media.taptotransfer.sender.AlmostCloseToStartCast +import com.android.systemui.media.taptotransfer.sender.TransferFailed +import com.android.systemui.media.taptotransfer.sender.TransferToReceiverTriggered +import com.android.systemui.media.taptotransfer.sender.TransferToThisDeviceSucceeded +import com.android.systemui.media.taptotransfer.sender.TransferToThisDeviceTriggered +import com.android.systemui.media.taptotransfer.sender.TransferToReceiverSucceeded import com.android.systemui.statusbar.commandline.Command import com.android.systemui.statusbar.commandline.CommandRegistry +import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.nullable +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Ignore import org.junit.Test import org.mockito.Mock -import org.mockito.Mockito.anyString import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations import java.io.PrintWriter import java.io.StringWriter @@ -50,15 +61,19 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { private lateinit var mediaTttCommandLineHelper: MediaTttCommandLineHelper @Mock + private lateinit var statusBarManager: StatusBarManager + @Mock private lateinit var mediaTttChipControllerReceiver: MediaTttChipControllerReceiver @Before fun setUp() { MockitoAnnotations.initMocks(this) + context.addMockSystemService(Context.STATUS_BAR_SERVICE, statusBarManager) mediaTttCommandLineHelper = MediaTttCommandLineHelper( commandRegistry, context, + FakeExecutor(FakeSystemClock()), mediaTttChipControllerReceiver, ) } @@ -88,91 +103,116 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { ) { EmptyCommand() } } - /* TODO(b/216318437): Revive these tests using the new SystemApis. @Test - fun sender_moveCloserToStartCast_serviceCallbackCalled() { - commandRegistry.onShellCommand(pw, getMoveCloserToStartCastCommand()) - - assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue() + fun sender_almostCloseToStartCast_serviceCallbackCalled() { + commandRegistry.onShellCommand( + pw, getSenderCommand(AlmostCloseToStartCast::class.simpleName!!) + ) - val deviceInfoCaptor = argumentCaptor<DeviceInfo>() - verify(mediaSenderService).closeToReceiverToStartCast(any(), capture(deviceInfoCaptor)) - assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME) + val routeInfoCaptor = argumentCaptor<MediaRoute2Info>() + verify(statusBarManager).updateMediaTapToTransferSenderDisplay( + eq(StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST), + capture(routeInfoCaptor), + nullable(), + nullable()) + assertThat(routeInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME) } @Test - fun sender_moveCloserToEndCast_serviceCallbackCalled() { - commandRegistry.onShellCommand(pw, getMoveCloserToEndCastCommand()) - - assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue() + fun sender_almostCloseToEndCast_serviceCallbackCalled() { + commandRegistry.onShellCommand( + pw, getSenderCommand(AlmostCloseToEndCast::class.simpleName!!) + ) - val deviceInfoCaptor = argumentCaptor<DeviceInfo>() - verify(mediaSenderService).closeToReceiverToEndCast(any(), capture(deviceInfoCaptor)) - assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME) + val routeInfoCaptor = argumentCaptor<MediaRoute2Info>() + verify(statusBarManager).updateMediaTapToTransferSenderDisplay( + eq(StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST), + capture(routeInfoCaptor), + nullable(), + nullable()) + assertThat(routeInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME) } @Test fun sender_transferToReceiverTriggered_chipDisplayWithCorrectState() { - commandRegistry.onShellCommand(pw, getTransferToReceiverTriggeredCommand()) - - assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue() + commandRegistry.onShellCommand( + pw, getSenderCommand(TransferToReceiverTriggered::class.simpleName!!) + ) - val deviceInfoCaptor = argumentCaptor<DeviceInfo>() - verify(mediaSenderService).transferToReceiverTriggered(any(), capture(deviceInfoCaptor)) - assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME) + val routeInfoCaptor = argumentCaptor<MediaRoute2Info>() + verify(statusBarManager).updateMediaTapToTransferSenderDisplay( + eq(StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED), + capture(routeInfoCaptor), + nullable(), + nullable()) + assertThat(routeInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME) } @Test fun sender_transferToThisDeviceTriggered_chipDisplayWithCorrectState() { - commandRegistry.onShellCommand(pw, getTransferToThisDeviceTriggeredCommand()) + commandRegistry.onShellCommand( + pw, getSenderCommand(TransferToThisDeviceTriggered::class.simpleName!!) + ) - assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue() - verify(mediaSenderService).transferToThisDeviceTriggered(any(), any()) + verify(statusBarManager).updateMediaTapToTransferSenderDisplay( + eq(StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED), + any(), + nullable(), + nullable()) } @Test fun sender_transferToReceiverSucceeded_chipDisplayWithCorrectState() { - commandRegistry.onShellCommand(pw, getTransferToReceiverSucceededCommand()) - - assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue() + commandRegistry.onShellCommand( + pw, getSenderCommand(TransferToReceiverSucceeded::class.simpleName!!) + ) - val deviceInfoCaptor = argumentCaptor<DeviceInfo>() - verify(mediaSenderService) - .transferToReceiverSucceeded(any(), capture(deviceInfoCaptor), any()) - assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME) + val routeInfoCaptor = argumentCaptor<MediaRoute2Info>() + verify(statusBarManager).updateMediaTapToTransferSenderDisplay( + eq(StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED), + capture(routeInfoCaptor), + nullable(), + nullable()) + assertThat(routeInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME) } @Test fun sender_transferToThisDeviceSucceeded_chipDisplayWithCorrectState() { - commandRegistry.onShellCommand(pw, getTransferToThisDeviceSucceededCommand()) - - assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue() + commandRegistry.onShellCommand( + pw, getSenderCommand(TransferToThisDeviceSucceeded::class.simpleName!!) + ) - val deviceInfoCaptor = argumentCaptor<DeviceInfo>() - verify(mediaSenderService) - .transferToThisDeviceSucceeded(any(), capture(deviceInfoCaptor), any()) - assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME) + val routeInfoCaptor = argumentCaptor<MediaRoute2Info>() + verify(statusBarManager).updateMediaTapToTransferSenderDisplay( + eq(StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED), + capture(routeInfoCaptor), + nullable(), + nullable()) + assertThat(routeInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME) } @Test fun sender_transferFailed_serviceCallbackCalled() { - commandRegistry.onShellCommand(pw, getTransferFailedCommand()) + commandRegistry.onShellCommand(pw, getSenderCommand(TransferFailed::class.simpleName!!)) - assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue() - verify(mediaSenderService).transferFailed(any(), any()) + verify(statusBarManager).updateMediaTapToTransferSenderDisplay( + eq(StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED), + any(), + nullable(), + nullable()) } @Test - fun sender_noLongerCloseToReceiver_serviceCallbackCalledAndServiceUnbound() { - commandRegistry.onShellCommand(pw, getNoLongerCloseToReceiverCommand()) - - // Once we're no longer close to the receiver, we should unbind the service. - assertThat(context.isBound(mediaSenderServiceComponentName)).isFalse() - verify(mediaSenderService).noLongerCloseToReceiver(any(), any()) + fun sender_farFromReceiver_serviceCallbackCalled() { + commandRegistry.onShellCommand(pw, getSenderCommand(FAR_FROM_RECEIVER_STATE)) + + verify(statusBarManager).updateMediaTapToTransferSenderDisplay( + eq(StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER), + any(), + nullable(), + nullable()) } - */ - @Test fun receiver_addCommand_chipAdded() { commandRegistry.onShellCommand(pw, arrayOf(ADD_CHIP_COMMAND_RECEIVER_TAG)) @@ -187,61 +227,8 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { verify(mediaTttChipControllerReceiver).removeChip() } - private fun getMoveCloserToStartCastCommand(): Array<String> = - arrayOf( - SENDER_COMMAND, - DEVICE_NAME, - MOVE_CLOSER_TO_START_CAST_COMMAND_NAME - ) - - private fun getMoveCloserToEndCastCommand(): Array<String> = - arrayOf( - SENDER_COMMAND, - DEVICE_NAME, - MOVE_CLOSER_TO_END_CAST_COMMAND_NAME - ) - - private fun getTransferToReceiverTriggeredCommand(): Array<String> = - arrayOf( - SENDER_COMMAND, - DEVICE_NAME, - TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME - ) - - private fun getTransferToThisDeviceTriggeredCommand(): Array<String> = - arrayOf( - SENDER_COMMAND, - DEVICE_NAME, - TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME - ) - - private fun getTransferToReceiverSucceededCommand(): Array<String> = - arrayOf( - SENDER_COMMAND, - DEVICE_NAME, - TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME - ) - - private fun getTransferToThisDeviceSucceededCommand(): Array<String> = - arrayOf( - SENDER_COMMAND, - DEVICE_NAME, - TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME - ) - - private fun getTransferFailedCommand(): Array<String> = - arrayOf( - SENDER_COMMAND, - DEVICE_NAME, - TRANSFER_FAILED_COMMAND_NAME - ) - - private fun getNoLongerCloseToReceiverCommand(): Array<String> = - arrayOf( - SENDER_COMMAND, - DEVICE_NAME, - NO_LONGER_CLOSE_TO_RECEIVER_COMMAND_NAME - ) + private fun getSenderCommand(displayState: String): Array<String> = + arrayOf(SENDER_COMMAND, DEVICE_NAME, displayState) class EmptyCommand : Command { override fun execute(pw: PrintWriter, args: List<String>) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt index 58f4818b3de5..c74ac64656ad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt @@ -16,8 +16,10 @@ package com.android.systemui.media.taptotransfer.sender +import android.app.StatusBarManager import android.graphics.drawable.Drawable import android.graphics.drawable.Icon +import android.media.MediaRoute2Info import android.view.View import android.view.WindowManager import android.widget.ImageView @@ -35,6 +37,7 @@ import org.junit.Ignore import org.junit.Test import org.mockito.ArgumentCaptor import org.mockito.Mock +import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -49,17 +52,148 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { private lateinit var windowManager: WindowManager @Mock private lateinit var commandQueue: CommandQueue + private lateinit var commandQueueCallback: CommandQueue.Callbacks @Before fun setUp() { MockitoAnnotations.initMocks(this) appIconDrawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context) - controllerSender = MediaTttChipControllerSender(context, windowManager, commandQueue) + controllerSender = MediaTttChipControllerSender(commandQueue, context, windowManager) + + val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) + verify(commandQueue).addCallback(callbackCaptor.capture()) + commandQueueCallback = callbackCaptor.value!! + } + + @Test + fun commandQueueCallback_almostCloseToStartCast_triggersCorrectChip() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, + routeInfo, + null + ) + + assertThat(getChipView().getChipText()) + .isEqualTo(almostCloseToStartCast().getChipTextString(context)) + } + + @Test + fun commandQueueCallback_almostCloseToEndCast_triggersCorrectChip() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST, + routeInfo, + null + ) + + assertThat(getChipView().getChipText()) + .isEqualTo(almostCloseToEndCast().getChipTextString(context)) + } + + @Test + fun commandQueueCallback_transferToReceiverTriggered_triggersCorrectChip() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED, + routeInfo, + null + ) + + assertThat(getChipView().getChipText()) + .isEqualTo(transferToReceiverTriggered().getChipTextString(context)) + } + + @Test + fun commandQueueCallback_transferToThisDeviceTriggered_triggersCorrectChip() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED, + routeInfo, + null + ) + + assertThat(getChipView().getChipText()) + .isEqualTo(transferToThisDeviceTriggered().getChipTextString(context)) + } + + @Test + fun commandQueueCallback_transferToReceiverSucceeded_triggersCorrectChip() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, + routeInfo, + null + ) + + assertThat(getChipView().getChipText()) + .isEqualTo(transferToReceiverSucceeded().getChipTextString(context)) } @Test - fun moveCloserToStartCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() { - val state = moveCloserToStartCast() + fun commandQueueCallback_transferToThisDeviceSucceeded_triggersCorrectChip() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, + routeInfo, + null + ) + + assertThat(getChipView().getChipText()) + .isEqualTo(transferToThisDeviceSucceeded().getChipTextString(context)) + } + + @Test + fun commandQueueCallback_transferToReceiverFailed_triggersCorrectChip() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED, + routeInfo, + null + ) + + assertThat(getChipView().getChipText()) + .isEqualTo(transferFailed().getChipTextString(context)) + } + + @Test + fun commandQueueCallback_transferToThisDeviceFailed_triggersCorrectChip() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED, + routeInfo, + null + ) + + assertThat(getChipView().getChipText()) + .isEqualTo(transferFailed().getChipTextString(context)) + } + + @Test + fun commandQueueCallback_farFromReceiver_noChipShown() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null + ) + + verify(windowManager, never()).addView(any(), any()) + } + + @Test + fun commandQueueCallback_almostCloseThenFarFromReceiver_chipShownThenHidden() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, + routeInfo, + null + ) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null + ) + + val viewCaptor = ArgumentCaptor.forClass(View::class.java) + verify(windowManager).addView(viewCaptor.capture(), any()) + verify(windowManager).removeView(viewCaptor.value) + } + + @Test + fun almostCloseToStartCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() { + val state = almostCloseToStartCast() controllerSender.displayChip(state) val chipView = getChipView() @@ -72,8 +206,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { } @Test - fun moveCloserToEndCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() { - val state = moveCloserToEndCast() + fun almostCloseToEndCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() { + val state = almostCloseToEndCast() controllerSender.displayChip(state) val chipView = getChipView() @@ -250,8 +384,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { } @Test - fun changeFromCloserToStartToTransferTriggered_loadingIconAppears() { - controllerSender.displayChip(moveCloserToStartCast()) + fun changeFromAlmostCloseToStartToTransferTriggered_loadingIconAppears() { + controllerSender.displayChip(almostCloseToStartCast()) controllerSender.displayChip(transferToReceiverTriggered()) assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.VISIBLE) @@ -280,9 +414,9 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { } @Test - fun changeFromTransferSucceededToMoveCloserToStart_undoButtonDisappears() { + fun changeFromTransferSucceededToAlmostCloseToStart_undoButtonDisappears() { controllerSender.displayChip(transferToReceiverSucceeded()) - controllerSender.displayChip(moveCloserToStartCast()) + controllerSender.displayChip(almostCloseToStartCast()) assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.GONE) } @@ -314,12 +448,12 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { } /** Helper method providing default parameters to not clutter up the tests. */ - private fun moveCloserToStartCast() = - MoveCloserToStartCast(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME) + private fun almostCloseToStartCast() = + AlmostCloseToStartCast(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME) /** Helper method providing default parameters to not clutter up the tests. */ - private fun moveCloserToEndCast() = - MoveCloserToEndCast(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME) + private fun almostCloseToEndCast() = + AlmostCloseToEndCast(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME) /** Helper method providing default parameters to not clutter up the tests. */ private fun transferToReceiverTriggered() = @@ -347,3 +481,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { private const val DEVICE_NAME = "My Tablet" private const val APP_ICON_CONTENT_DESC = "Content description" + +private val routeInfo = MediaRoute2Info.Builder("id", "Test Name") + .addFeature("feature") + .build() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RunningFgsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RunningFgsControllerTest.java deleted file mode 100644 index 6059afe8a70b..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RunningFgsControllerTest.java +++ /dev/null @@ -1,340 +0,0 @@ -/* - * Copyright (C) 2021 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; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.IActivityManager; -import android.app.IForegroundServiceObserver; -import android.os.Binder; -import android.os.IBinder; -import android.os.RemoteException; -import android.testing.AndroidTestingRunner; -import android.util.Pair; - -import androidx.lifecycle.Lifecycle; -import androidx.lifecycle.LifecycleOwner; -import androidx.test.filters.MediumTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.policy.RunningFgsController; -import com.android.systemui.statusbar.policy.RunningFgsController.UserPackageTime; -import com.android.systemui.statusbar.policy.RunningFgsControllerImpl; -import com.android.systemui.util.concurrency.FakeExecutor; -import com.android.systemui.util.time.FakeSystemClock; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.Random; -import java.util.function.Consumer; - -@MediumTest -@RunWith(AndroidTestingRunner.class) -public class RunningFgsControllerTest extends SysuiTestCase { - - private RunningFgsController mController; - - private FakeSystemClock mSystemClock = new FakeSystemClock(); - private FakeExecutor mExecutor = new FakeExecutor(mSystemClock); - private TestCallback mCallback = new TestCallback(); - - @Mock - private IActivityManager mActivityManager; - @Mock - private Lifecycle mLifecycle; - @Mock - private LifecycleOwner mLifecycleOwner; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - when(mLifecycleOwner.getLifecycle()).thenReturn(mLifecycle); - mController = new RunningFgsControllerImpl(mExecutor, mSystemClock, mActivityManager); - } - - @Test - public void testInitRegistersListenerInImpl() throws RemoteException { - ((RunningFgsControllerImpl) mController).init(); - verify(mActivityManager, times(1)).registerForegroundServiceObserver(any()); - } - - @Test - public void testAddCallbackCallsInitInImpl() { - verifyInitIsCalled(controller -> controller.addCallback(mCallback)); - } - - @Test - public void testRemoveCallbackCallsInitInImpl() { - verifyInitIsCalled(controller -> controller.removeCallback(mCallback)); - } - - @Test - public void testObserve1CallsInitInImpl() { - verifyInitIsCalled(controller -> controller.observe(mLifecycle, mCallback)); - } - - @Test - public void testObserve2CallsInitInImpl() { - verifyInitIsCalled(controller -> controller.observe(mLifecycleOwner, mCallback)); - } - - @Test - public void testGetPackagesWithFgsCallsInitInImpl() { - verifyInitIsCalled(controller -> controller.getPackagesWithFgs()); - } - - @Test - public void testStopFgsCallsInitInImpl() { - verifyInitIsCalled(controller -> controller.stopFgs(0, "")); - } - - /** - * Tests that callbacks can be added - */ - @Test - public void testAddCallback() throws RemoteException { - String testPackageName = "testPackageName"; - int testUserId = 0; - - IForegroundServiceObserver observer = prepareObserver(); - mController.addCallback(mCallback); - - observer.onForegroundStateChanged(new Binder(), testPackageName, testUserId, true); - - mExecutor.advanceClockToLast(); - mExecutor.runAllReady(); - - assertEquals("Callback should have been invoked exactly once.", - 1, mCallback.mInvocations.size()); - - List<UserPackageTime> userPackageTimes = mCallback.mInvocations.get(0); - assertEquals("There should have only been one package in callback. packages=" - + userPackageTimes, - 1, userPackageTimes.size()); - - UserPackageTime upt = userPackageTimes.get(0); - assertEquals(testPackageName, upt.getPackageName()); - assertEquals(testUserId, upt.getUserId()); - } - - /** - * Tests that callbacks can be removed. This test is only meaningful if - * {@link #testAddCallback()} can pass. - */ - @Test - public void testRemoveCallback() throws RemoteException { - String testPackageName = "testPackageName"; - int testUserId = 0; - - IForegroundServiceObserver observer = prepareObserver(); - mController.addCallback(mCallback); - mController.removeCallback(mCallback); - - observer.onForegroundStateChanged(new Binder(), testPackageName, testUserId, true); - - mExecutor.advanceClockToLast(); - mExecutor.runAllReady(); - - assertEquals("Callback should not have been invoked.", - 0, mCallback.mInvocations.size()); - } - - /** - * Tests packages are added when the controller receives a callback from activity manager for - * a foreground service start. - */ - @Test - public void testGetPackagesWithFgsAddingPackages() throws RemoteException { - int numPackages = 20; - int numUsers = 3; - - IForegroundServiceObserver observer = prepareObserver(); - - assertEquals("List should be empty", 0, mController.getPackagesWithFgs().size()); - - List<Pair<Integer, String>> addedPackages = new ArrayList<>(); - for (int pkgNumber = 0; pkgNumber < numPackages; pkgNumber++) { - for (int userId = 0; userId < numUsers; userId++) { - String packageName = "package.name." + pkgNumber; - addedPackages.add(new Pair(userId, packageName)); - - observer.onForegroundStateChanged(new Binder(), packageName, userId, true); - - containsAllAddedPackages(addedPackages, mController.getPackagesWithFgs()); - } - } - } - - /** - * Tests packages are removed when the controller receives a callback from activity manager for - * a foreground service ending. - */ - @Test - public void testGetPackagesWithFgsRemovingPackages() throws RemoteException { - int numPackages = 20; - int numUsers = 3; - int arrayLength = numPackages * numUsers; - - String[] packages = new String[arrayLength]; - int[] users = new int[arrayLength]; - IBinder[] tokens = new IBinder[arrayLength]; - for (int pkgNumber = 0; pkgNumber < numPackages; pkgNumber++) { - for (int userId = 0; userId < numUsers; userId++) { - int i = pkgNumber * numUsers + userId; - packages[i] = "package.name." + pkgNumber; - users[i] = userId; - tokens[i] = new Binder(); - } - } - - IForegroundServiceObserver observer = prepareObserver(); - - for (int i = 0; i < packages.length; i++) { - observer.onForegroundStateChanged(tokens[i], packages[i], users[i], true); - } - - assertEquals(packages.length, mController.getPackagesWithFgs().size()); - - List<Integer> removeOrder = new ArrayList<>(); - for (int i = 0; i < packages.length; i++) { - removeOrder.add(i); - } - Collections.shuffle(removeOrder, new Random(12345)); - - for (int idx : removeOrder) { - removePackageAndAssertRemovedFromList(observer, tokens[idx], packages[idx], users[idx]); - } - - assertEquals(0, mController.getPackagesWithFgs().size()); - } - - /** - * Tests a call on stopFgs forwards to activity manager. - */ - @Test - public void testStopFgs() throws RemoteException { - String pkgName = "package.name"; - mController.stopFgs(0, pkgName); - verify(mActivityManager).stopAppForUser(pkgName, 0); - } - - /** - * Tests a package which starts multiple services is only listed once and is only removed once - * all services are stopped. - */ - @Test - public void testSinglePackageWithMultipleServices() throws RemoteException { - String packageName = "package.name"; - int userId = 0; - IBinder serviceToken1 = new Binder(); - IBinder serviceToken2 = new Binder(); - - IForegroundServiceObserver observer = prepareObserver(); - - assertEquals(0, mController.getPackagesWithFgs().size()); - - observer.onForegroundStateChanged(serviceToken1, packageName, userId, true); - assertSinglePackage(packageName, userId); - - observer.onForegroundStateChanged(serviceToken2, packageName, userId, true); - assertSinglePackage(packageName, userId); - - observer.onForegroundStateChanged(serviceToken2, packageName, userId, false); - assertSinglePackage(packageName, userId); - - observer.onForegroundStateChanged(serviceToken1, packageName, userId, false); - assertEquals(0, mController.getPackagesWithFgs().size()); - } - - private IForegroundServiceObserver prepareObserver() - throws RemoteException { - mController.getPackagesWithFgs(); - - ArgumentCaptor<IForegroundServiceObserver> argumentCaptor = - ArgumentCaptor.forClass(IForegroundServiceObserver.class); - verify(mActivityManager).registerForegroundServiceObserver(argumentCaptor.capture()); - - return argumentCaptor.getValue(); - } - - private void verifyInitIsCalled(Consumer<RunningFgsControllerImpl> c) { - RunningFgsControllerImpl spiedController = Mockito.spy( - ((RunningFgsControllerImpl) mController)); - c.accept(spiedController); - verify(spiedController, atLeastOnce()).init(); - } - - private void containsAllAddedPackages(List<Pair<Integer, String>> addedPackages, - List<UserPackageTime> runningFgsPackages) { - for (Pair<Integer, String> userPkg : addedPackages) { - assertTrue(userPkg + " was not found in returned list", - runningFgsPackages.stream().anyMatch( - upt -> userPkg.first == upt.getUserId() - && Objects.equals(upt.getPackageName(), userPkg.second))); - } - for (UserPackageTime upt : runningFgsPackages) { - int userId = upt.getUserId(); - String packageName = upt.getPackageName(); - assertTrue("Unknown <user=" + userId + ", package=" + packageName + ">" - + " in returned list", - addedPackages.stream().anyMatch(userPkg -> userPkg.first == userId - && Objects.equals(packageName, userPkg.second))); - } - } - - private void removePackageAndAssertRemovedFromList(IForegroundServiceObserver observer, - IBinder token, String pkg, int userId) throws RemoteException { - observer.onForegroundStateChanged(token, pkg, userId, false); - List<UserPackageTime> packagesWithFgs = mController.getPackagesWithFgs(); - assertFalse("Package \"" + pkg + "\" was not removed", - packagesWithFgs.stream().anyMatch(upt -> - Objects.equals(upt.getPackageName(), pkg) && upt.getUserId() == userId)); - } - - private void assertSinglePackage(String packageName, int userId) { - assertEquals(1, mController.getPackagesWithFgs().size()); - assertEquals(packageName, mController.getPackagesWithFgs().get(0).getPackageName()); - assertEquals(userId, mController.getPackagesWithFgs().get(0).getUserId()); - } - - private static class TestCallback implements RunningFgsController.Callback { - - private List<List<UserPackageTime>> mInvocations = new ArrayList<>(); - - @Override - public void onFgsPackagesChanged(List<UserPackageTime> packages) { - mInvocations.add(packages); - } - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java index 85ea52b6af6a..d13451dc4769 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar; +import static com.google.common.truth.Truth.assertThat; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNull; @@ -37,6 +39,7 @@ import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Color; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Icon; import android.os.UserHandle; import android.service.notification.StatusBarNotification; @@ -130,7 +133,12 @@ public class StatusBarIconViewTest extends SysuiTestCase { Icon icon = Icon.createWithBitmap(largeBitmap); StatusBarIcon largeIcon = new StatusBarIcon(UserHandle.ALL, "mockPackage", icon, 0, 0, ""); - assertFalse(mIconView.set(largeIcon)); + assertTrue(mIconView.set(largeIcon)); + + // The view should downscale the bitmap. + BitmapDrawable drawable = (BitmapDrawable) mIconView.getDrawable(); + assertThat(drawable.getBitmap().getWidth()).isLessThan(1000); + assertThat(drawable.getBitmap().getHeight()).isLessThan(1000); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt new file mode 100644 index 000000000000..ac357ea34be0 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt @@ -0,0 +1,73 @@ +package com.android.systemui.util.drawable + +import android.content.res.Resources +import android.graphics.Bitmap +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.ShapeDrawable +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class DrawableSizeTest : SysuiTestCase() { + + lateinit var resources: Resources + + @Before + fun setUp() { + resources = context.resources + } + + @Test + fun testDownscaleToSize_drawableZeroSize_unchanged() { + val drawable = ShapeDrawable() + val result = DrawableSize.downscaleToSize(resources, drawable, 100, 100) + assertThat(result).isSameInstanceAs(drawable) + } + + @Test + fun testDownscaleToSize_drawableSmallerThanRequirement_unchanged() { + val drawable = BitmapDrawable(resources, + Bitmap.createBitmap( + resources.displayMetrics, + 150, + 150, + Bitmap.Config.ARGB_8888 + ) + ) + val result = DrawableSize.downscaleToSize(resources, drawable, 300, 300) + assertThat(result).isSameInstanceAs(drawable) + } + + @Test + fun testDownscaleToSize_drawableLargerThanRequirementWithDensity_resized() { + // This bitmap would actually fail to resize if the method doesn't check for + // bitmap dimensions inside drawable. + val drawable = BitmapDrawable(resources, + Bitmap.createBitmap( + resources.displayMetrics, + 150, + 75, + Bitmap.Config.ARGB_8888 + ) + ) + + val result = DrawableSize.downscaleToSize(resources, drawable, 75, 75) + assertThat(result).isNotSameInstanceAs(drawable) + assertThat(result.intrinsicWidth).isEqualTo(75) + assertThat(result.intrinsicHeight).isEqualTo(37) + } + + @Test + fun testDownscaleToSize_drawableAnimated_unchanged() { + val drawable = resources.getDrawable(android.R.drawable.stat_sys_download, + resources.newTheme()) + val result = DrawableSize.downscaleToSize(resources, drawable, 1, 1) + assertThat(result).isSameInstanceAs(drawable) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 9c49e98b9e36..ca37a40474e0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -1339,6 +1339,22 @@ public class BubblesTest extends SysuiTestCase { assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isFalse(); } + @Test + public void testStackViewOnBackPressed_updatesBubbleDataExpandState() { + mBubbleController.updateBubble(mBubbleEntry); + + // Expand the stack + mBubbleData.setExpanded(true); + assertStackExpanded(); + + // Hit back + BubbleStackView stackView = mBubbleController.getStackView(); + stackView.onBackPressed(); + + // Make sure we're collapsed + assertStackCollapsed(); + } + /** Creates a bubble using the userId and package. */ private Bubble createBubble(int userId, String pkg) { final UserHandle userHandle = new UserHandle(userId); diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java index e12a82a1c62b..d82671d6ecb8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java @@ -1158,6 +1158,22 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isFalse(); } + @Test + public void testStackViewOnBackPressed_updatesBubbleDataExpandState() { + mBubbleController.updateBubble(mBubbleEntry); + + // Expand the stack + mBubbleData.setExpanded(true); + assertStackExpanded(); + + // Hit back + BubbleStackView stackView = mBubbleController.getStackView(); + stackView.onBackPressed(); + + // Make sure we're collapsed + assertStackCollapsed(); + } + /** * Sets the bubble metadata flags for this entry. These flags are normally set by * NotificationManagerService when the notification is sent, however, these tests do not diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index e93ac47b1940..8b62a64f57d4 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -66,6 +66,7 @@ import android.os.RemoteCallback; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; +import android.provider.Settings; import android.util.Slog; import android.util.SparseArray; import android.view.Display; @@ -269,6 +270,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ void onDoubleTap(int displayId); void onDoubleTapAndHold(int displayId); + } public AbstractAccessibilityServiceConnection(Context context, ComponentName componentName, @@ -2164,4 +2166,23 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ public void onDoubleTapAndHold(int displayId) { mSystemSupport.onDoubleTapAndHold(displayId); } -} + + /** + * Sets the scaling factor for animations. + */ + public void setAnimationScale(float scale) { + final long identity = Binder.clearCallingIdentity(); + try { + Settings.Global.putFloat( + mContext.getContentResolver(), Settings.Global.WINDOW_ANIMATION_SCALE, scale); + Settings.Global.putFloat( + mContext.getContentResolver(), + Settings.Global.TRANSITION_ANIMATION_SCALE, + scale); + Settings.Global.putFloat( + mContext.getContentResolver(), Settings.Global.ANIMATOR_DURATION_SCALE, scale); + } finally { + Binder.restoreCallingIdentity(identity); + } + } +}
\ No newline at end of file diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java index bb49ba059d23..75acf81a4a3c 100644 --- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java +++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java @@ -39,7 +39,6 @@ import android.window.DisplayWindowPolicyController; import java.util.List; import java.util.Set; -import java.util.function.Consumer; /** @@ -62,7 +61,6 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { private final ArraySet<ComponentName> mAllowedActivities; @Nullable private final ArraySet<ComponentName> mBlockedActivities; - private Consumer<ActivityInfo> mActivityBlockedCallback; @NonNull final ArraySet<Integer> mRunningUids = new ArraySet<>(); @@ -83,12 +81,10 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { @NonNull ArraySet<UserHandle> allowedUsers, @Nullable Set<ComponentName> allowedActivities, @Nullable Set<ComponentName> blockedActivities, - @NonNull ActivityListener activityListener, - @NonNull Consumer<ActivityInfo> activityBlockedCallback) { + @NonNull ActivityListener activityListener) { mAllowedUsers = allowedUsers; mAllowedActivities = allowedActivities == null ? null : new ArraySet<>(allowedActivities); mBlockedActivities = blockedActivities == null ? null : new ArraySet<>(blockedActivities); - mActivityBlockedCallback = activityBlockedCallback; setInterestedWindowFlags(windowFlags, systemWindowFlags); mActivityListener = activityListener; } @@ -100,7 +96,6 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { for (int i = 0; i < activityCount; i++) { final ActivityInfo aInfo = activities.get(i); if (!canContainActivity(aInfo, /* windowFlags= */ 0, /* systemWindowFlags= */ 0)) { - mActivityBlockedCallback.accept(aInfo); return false; } } @@ -110,11 +105,7 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { @Override public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags, int systemWindowFlags) { - if (!canContainActivity(activityInfo, windowFlags, systemWindowFlags)) { - mActivityBlockedCallback.accept(activityInfo); - return false; - } - return true; + return canContainActivity(activityInfo, windowFlags, systemWindowFlags); } @Override diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 98a5ec1c3681..95b9e58a9dfd 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -34,8 +34,6 @@ import android.companion.virtual.VirtualDeviceManager.ActivityListener; import android.companion.virtual.VirtualDeviceParams; import android.content.ComponentName; import android.content.Context; -import android.content.Intent; -import android.content.pm.ActivityInfo; import android.graphics.Point; import android.graphics.PointF; import android.hardware.display.DisplayManager; @@ -59,7 +57,6 @@ import android.util.SparseArray; import android.window.DisplayWindowPolicyController; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.app.BlockedAppActivity; import com.android.server.LocalServices; import java.io.FileDescriptor; @@ -421,8 +418,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub getAllowedUserHandles(), mParams.getAllowedActivities(), mParams.getBlockedActivities(), - createListenerAdapter(displayId), - activityInfo -> onActivityBlocked(displayId, activityInfo)); + createListenerAdapter(displayId)); mWindowPolicyControllers.put(displayId, dwpc); return dwpc; } @@ -445,16 +441,6 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } } - private void onActivityBlocked(int displayId, ActivityInfo activityInfo) { - Intent intent = BlockedAppActivity.createStreamingBlockedIntent( - UserHandle.getUserId(activityInfo.applicationInfo.uid), activityInfo, - mAssociationInfo.getDisplayName()); - mContext.startActivityAsUser( - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), - ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle(), - mContext.getUser()); - } - private ArraySet<UserHandle> getAllowedUserHandles() { ArraySet<UserHandle> result = new ArraySet<>(); DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index cbb40aa9a671..e2204e2efaca 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -17905,6 +17905,11 @@ public class ActivityManagerService extends IActivityManager.Stub } } + @Override + public boolean isAppFreezerEnabled() { + return mOomAdjuster.mCachedAppOptimizer.useFreezer(); + } + /** * Resets the state of the {@link com.android.server.am.AppErrors} instance. * This is intended for testing within the CTS only and is protected by diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index 6f22c61d2b2a..7af73ebda0c0 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -977,7 +977,7 @@ public final class CachedAppOptimizer { Slog.d(TAG_AM, "pid " + pid + " " + app.processName + " received sync transactions while frozen, killing"); app.killLocked("Sync transaction while in frozen state", - ApplicationExitInfo.REASON_OTHER, + ApplicationExitInfo.REASON_FREEZER, ApplicationExitInfo.SUBREASON_FREEZER_BINDER_TRANSACTION, true); processKilled = true; } @@ -990,7 +990,7 @@ public final class CachedAppOptimizer { Slog.d(TAG_AM, "Unable to query binder frozen info for pid " + pid + " " + app.processName + ". Killing it. Exception: " + e); app.killLocked("Unable to query binder frozen stats", - ApplicationExitInfo.REASON_OTHER, + ApplicationExitInfo.REASON_FREEZER, ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true); processKilled = true; } @@ -1007,7 +1007,7 @@ public final class CachedAppOptimizer { Slog.e(TAG_AM, "Unable to unfreeze binder for " + pid + " " + app.processName + ". Killing it"); app.killLocked("Unable to unfreeze", - ApplicationExitInfo.REASON_OTHER, + ApplicationExitInfo.REASON_FREEZER, ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true); return; } @@ -1423,7 +1423,7 @@ public final class CachedAppOptimizer { mFreezeHandler.post(() -> { synchronized (mAm) { proc.killLocked("Unable to freeze binder interface", - ApplicationExitInfo.REASON_OTHER, + ApplicationExitInfo.REASON_FREEZER, ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true); } }); @@ -1477,7 +1477,7 @@ public final class CachedAppOptimizer { mFreezeHandler.post(() -> { synchronized (mAm) { proc.killLocked("Unable to freeze binder interface", - ApplicationExitInfo.REASON_OTHER, + ApplicationExitInfo.REASON_FREEZER, ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true); } }); diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index d47f0c44d720..6866137e294e 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -30,6 +30,8 @@ import android.util.Slog; import android.util.Spline; import android.view.DisplayAddress; +import com.android.internal.annotations.VisibleForTesting; + import com.android.internal.R; import com.android.internal.display.BrightnessSynchronizer; import com.android.server.display.config.BrightnessThresholds; @@ -40,9 +42,12 @@ import com.android.server.display.config.DisplayConfiguration; import com.android.server.display.config.DisplayQuirks; import com.android.server.display.config.HbmTiming; import com.android.server.display.config.HighBrightnessMode; +import com.android.server.display.config.Interpolation; import com.android.server.display.config.NitsMap; import com.android.server.display.config.Point; import com.android.server.display.config.RefreshRateRange; +import com.android.server.display.config.SdrHdrRatioMap; +import com.android.server.display.config.SdrHdrRatioPoint; import com.android.server.display.config.SensorDetails; import com.android.server.display.config.ThermalStatus; import com.android.server.display.config.ThermalThrottling; @@ -185,6 +190,7 @@ import javax.xml.datatype.DatatypeConfigurationException; */ public class DisplayDeviceConfig { private static final String TAG = "DisplayDeviceConfig"; + private static final boolean DEBUG = false; public static final float HIGH_BRIGHTNESS_MODE_UNSUPPORTED = Float.NaN; @@ -201,6 +207,9 @@ public class DisplayDeviceConfig { private static final String NO_SUFFIX_FORMAT = "%d"; private static final long STABLE_FLAG = 1L << 62; + private static final int INTERPOLATION_DEFAULT = 0; + private static final int INTERPOLATION_LINEAR = 1; + // Float.NaN (used as invalid for brightness) cannot be stored in config.xml // so -2 is used instead private static final float INVALID_BRIGHTNESS_IN_CONFIG = -2f; @@ -214,6 +223,9 @@ public class DisplayDeviceConfig { // Length of the ambient light horizon used to calculate short-term estimate of ambient light. private static final int AMBIENT_LIGHT_SHORT_HORIZON_MILLIS = 2000; + @VisibleForTesting + static final float HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT = 0.5f; + private final Context mContext; // The details of the ambient light sensor associated with this display. @@ -229,6 +241,7 @@ public class DisplayDeviceConfig { // config.xml. These are the raw values and just used for the dumpsys private float[] mRawNits; private float[] mRawBacklight; + private int mInterpolationType; // These arrays are calculated from the raw arrays, but clamped to contain values equal to and // between mBacklightMinimum and mBacklightMaximum. These three arrays should all be the same @@ -257,6 +270,7 @@ public class DisplayDeviceConfig { private Spline mBrightnessToBacklightSpline; private Spline mBacklightToBrightnessSpline; private Spline mBacklightToNitsSpline; + private Spline mNitsToBacklightSpline; private List<String> mQuirks; private boolean mIsHighBrightnessModeEnabled = false; private HighBrightnessModeData mHbmData; @@ -264,6 +278,7 @@ public class DisplayDeviceConfig { private String mLoadedFrom = null; private BrightnessThrottlingData mBrightnessThrottlingData; + private Spline mSdrToHdrRatioSpline; private DisplayDeviceConfig(Context context) { mContext = context; @@ -448,6 +463,45 @@ public class DisplayDeviceConfig { } /** + * Calculate the HDR brightness for the specified SDR brightenss. + * + * @return the HDR brightness or BRIGHTNESS_INVALID when no mapping exists. + */ + public float getHdrBrightnessFromSdr(float brightness) { + if (mSdrToHdrRatioSpline == null) { + return PowerManager.BRIGHTNESS_INVALID; + } + + float backlight = getBacklightFromBrightness(brightness); + float nits = getNitsFromBacklight(backlight); + if (nits == NITS_INVALID) { + return PowerManager.BRIGHTNESS_INVALID; + } + + float ratio = mSdrToHdrRatioSpline.interpolate(nits); + float hdrNits = nits * ratio; + if (mNitsToBacklightSpline == null) { + return PowerManager.BRIGHTNESS_INVALID; + } + + float hdrBacklight = mNitsToBacklightSpline.interpolate(hdrNits); + hdrBacklight = Math.max(mBacklightMinimum, Math.min(mBacklightMaximum, hdrBacklight)); + float hdrBrightness = mBacklightToBrightnessSpline.interpolate(hdrBacklight); + + if (DEBUG) { + Slog.d(TAG, "getHdrBrightnessFromSdr: sdr brightness " + brightness + + " backlight " + backlight + + " nits " + nits + + " ratio " + ratio + + " hdrNits " + hdrNits + + " hdrBacklight " + hdrBacklight + + " hdrBrightness " + hdrBrightness + ); + } + return hdrBrightness; + } + + /** * Return an array of equal length to backlight and nits, that covers the entire system * brightness range of 0.0-1.0. * @@ -559,15 +613,18 @@ public class DisplayDeviceConfig { + ", mNits=" + Arrays.toString(mNits) + ", mRawBacklight=" + Arrays.toString(mRawBacklight) + ", mRawNits=" + Arrays.toString(mRawNits) + + ", mInterpolationType=" + mInterpolationType + ", mBrightness=" + Arrays.toString(mBrightness) + ", mBrightnessToBacklightSpline=" + mBrightnessToBacklightSpline + ", mBacklightToBrightnessSpline=" + mBacklightToBrightnessSpline + + ", mNitsToBacklightSpline=" + mNitsToBacklightSpline + ", mBacklightMinimum=" + mBacklightMinimum + ", mBacklightMaximum=" + mBacklightMaximum + ", mBrightnessDefault=" + mBrightnessDefault + ", mQuirks=" + mQuirks + ", isHbmEnabled=" + mIsHighBrightnessModeEnabled + ", mHbmData=" + mHbmData + + ", mSdrToHdrRatioSpline=" + mSdrToHdrRatioSpline + ", mBrightnessThrottlingData=" + mBrightnessThrottlingData + ", mBrightnessRampFastDecrease=" + mBrightnessRampFastDecrease + ", mBrightnessRampFastIncrease=" + mBrightnessRampFastIncrease @@ -768,6 +825,7 @@ public class DisplayDeviceConfig { float[] nits = new float[size]; float[] backlight = new float[size]; + mInterpolationType = convertInterpolationType(map.getInterpolation()); int i = 0; for (Point point : points) { nits[i] = point.getNits().floatValue(); @@ -793,6 +851,40 @@ public class DisplayDeviceConfig { constrainNitsAndBacklightArrays(); } + private Spline loadSdrHdrRatioMap(HighBrightnessMode hbmConfig) { + final SdrHdrRatioMap sdrHdrRatioMap = hbmConfig.getSdrHdrRatioMap_all(); + + if (sdrHdrRatioMap == null) { + return null; + } + + final List<SdrHdrRatioPoint> points = sdrHdrRatioMap.getPoint(); + final int size = points.size(); + if (size <= 0) { + return null; + } + + float[] nits = new float[size]; + float[] ratios = new float[size]; + + int i = 0; + for (SdrHdrRatioPoint point : points) { + nits[i] = point.getSdrNits().floatValue(); + if (i > 0) { + if (nits[i] < nits[i - 1]) { + Slog.e(TAG, "sdrHdrRatioMap must be non-decreasing, ignoring rest " + + " of configuration. nits: " + nits[i] + " < " + + nits[i - 1]); + return null; + } + } + ratios[i] = point.getHdrRatio().floatValue(); + ++i; + } + + return Spline.createSpline(nits, ratios); + } + private void loadBrightnessThrottlingMap(DisplayConfiguration config) { final ThermalThrottling throttlingConfig = config.getThermalThrottling(); if (throttlingConfig == null) { @@ -938,9 +1030,18 @@ public class DisplayDeviceConfig { mBacklight[mBacklight.length - 1], PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, mBacklight[i]); } - mBrightnessToBacklightSpline = Spline.createSpline(mBrightness, mBacklight); - mBacklightToBrightnessSpline = Spline.createSpline(mBacklight, mBrightness); - mBacklightToNitsSpline = Spline.createSpline(mBacklight, mNits); + mBrightnessToBacklightSpline = mInterpolationType == INTERPOLATION_LINEAR + ? Spline.createLinearSpline(mBrightness, mBacklight) + : Spline.createSpline(mBrightness, mBacklight); + mBacklightToBrightnessSpline = mInterpolationType == INTERPOLATION_LINEAR + ? Spline.createLinearSpline(mBacklight, mBrightness) + : Spline.createSpline(mBacklight, mBrightness); + mBacklightToNitsSpline = mInterpolationType == INTERPOLATION_LINEAR + ? Spline.createLinearSpline(mBacklight, mNits) + : Spline.createSpline(mBacklight, mNits); + mNitsToBacklightSpline = mInterpolationType == INTERPOLATION_LINEAR + ? Spline.createLinearSpline(mNits, mBacklight) + : Spline.createSpline(mNits, mBacklight); } private void loadQuirks(DisplayConfiguration config) { @@ -977,6 +1078,20 @@ public class DisplayDeviceConfig { mRefreshRateLimitations.add(new RefreshRateLimitation( DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE, min, max)); } + BigDecimal minHdrPctOfScreen = hbm.getMinimumHdrPercentOfScreen_all(); + if (minHdrPctOfScreen != null) { + mHbmData.minimumHdrPercentOfScreen = minHdrPctOfScreen.floatValue(); + if (mHbmData.minimumHdrPercentOfScreen > 1 + || mHbmData.minimumHdrPercentOfScreen < 0) { + Slog.w(TAG, "Invalid minimum HDR percent of screen: " + + String.valueOf(mHbmData.minimumHdrPercentOfScreen)); + mHbmData.minimumHdrPercentOfScreen = HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT; + } + } else { + mHbmData.minimumHdrPercentOfScreen = HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT; + } + + mSdrToHdrRatioSpline = loadSdrHdrRatioMap(hbm); } } @@ -1139,6 +1254,21 @@ public class DisplayDeviceConfig { } } + private int convertInterpolationType(Interpolation value) { + if (value == null) { + return INTERPOLATION_DEFAULT; + } + switch (value) { + case _default: + return INTERPOLATION_DEFAULT; + case linear: + return INTERPOLATION_LINEAR; + default: + Slog.wtf(TAG, "Unexpected Interpolation Type: " + value); + return INTERPOLATION_DEFAULT; + } + } + private void loadAmbientHorizonFromDdc(DisplayConfiguration config) { final BigInteger configLongHorizon = config.getAmbientLightHorizonLong(); if (configLongHorizon != null) { @@ -1203,11 +1333,15 @@ public class DisplayDeviceConfig { /** Minimum time that HBM can be on before being enabled. */ public long timeMinMillis; + /** Minimum HDR video size to enter high brightness mode */ + public float minimumHdrPercentOfScreen; + HighBrightnessModeData() {} HighBrightnessModeData(float minimumLux, float transitionPoint, long timeWindowMillis, long timeMaxMillis, long timeMinMillis, - @PowerManager.ThermalStatus int thermalStatusLimit, boolean allowInLowPowerMode) { + @PowerManager.ThermalStatus int thermalStatusLimit, boolean allowInLowPowerMode, + float minimumHdrPercentOfScreen) { this.minimumLux = minimumLux; this.transitionPoint = transitionPoint; this.timeWindowMillis = timeWindowMillis; @@ -1215,6 +1349,7 @@ public class DisplayDeviceConfig { this.timeMinMillis = timeMinMillis; this.thermalStatusLimit = thermalStatusLimit; this.allowInLowPowerMode = allowInLowPowerMode; + this.minimumHdrPercentOfScreen = minimumHdrPercentOfScreen; } /** @@ -1229,6 +1364,7 @@ public class DisplayDeviceConfig { other.transitionPoint = transitionPoint; other.thermalStatusLimit = thermalStatusLimit; other.allowInLowPowerMode = allowInLowPowerMode; + other.minimumHdrPercentOfScreen = minimumHdrPercentOfScreen; } @Override @@ -1241,6 +1377,7 @@ public class DisplayDeviceConfig { + ", timeMin: " + timeMinMillis + "ms" + ", thermalStatusLimit: " + thermalStatusLimit + ", allowInLowPowerMode: " + allowInLowPowerMode + + ", minimumHdrPercentOfScreen: " + minimumHdrPercentOfScreen + "} "; } } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index eeeaa26c612b..d71e07ab573f 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -832,7 +832,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call setUpAutoBrightness(mContext.getResources(), mHandler); reloadReduceBrightColours(); mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId, - mDisplayDeviceConfig.getHighBrightnessModeData()); + mDisplayDeviceConfig.getHighBrightnessModeData(), + new HighBrightnessModeController.HdrBrightnessDeviceConfig() { + @Override + public float getHdrBrightnessFromSdr(float sdrBrightness) { + return mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness); + } + }); mBrightnessThrottler.resetThrottlingData( mDisplayDeviceConfig.getBrightnessThrottlingData()); } @@ -1714,6 +1720,12 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); return new HighBrightnessModeController(mHandler, info.width, info.height, displayToken, displayUniqueId, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData, + new HighBrightnessModeController.HdrBrightnessDeviceConfig() { + @Override + public float getHdrBrightnessFromSdr(float sdrBrightness) { + return mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness); + } + }, () -> { sendUpdatePowerStateLocked(); postBrightnessChangeRunnable(); diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java index 23c17f5af10d..0b9d4debd16f 100644 --- a/services/core/java/com/android/server/display/HighBrightnessModeController.java +++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java @@ -58,11 +58,13 @@ class HighBrightnessModeController { private static final boolean DEBUG = false; - private static final float HDR_PERCENT_OF_SCREEN_REQUIRED = 0.50f; - @VisibleForTesting static final float HBM_TRANSITION_POINT_INVALID = Float.POSITIVE_INFINITY; + public interface HdrBrightnessDeviceConfig { + float getHdrBrightnessFromSdr(float sdrBrightness); + } + private final float mBrightnessMin; private final float mBrightnessMax; private final Handler mHandler; @@ -76,6 +78,7 @@ class HighBrightnessModeController { private HdrListener mHdrListener; private HighBrightnessModeData mHbmData; + private HdrBrightnessDeviceConfig mHdrBrightnessCfg; private IBinder mRegisteredDisplayToken; private boolean mIsInAllowedAmbientRange = false; @@ -115,16 +118,17 @@ class HighBrightnessModeController { HighBrightnessModeController(Handler handler, int width, int height, IBinder displayToken, String displayUniqueId, float brightnessMin, float brightnessMax, - HighBrightnessModeData hbmData, Runnable hbmChangeCallback, Context context) { + HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg, + Runnable hbmChangeCallback, Context context) { this(new Injector(), handler, width, height, displayToken, displayUniqueId, brightnessMin, - brightnessMax, hbmData, hbmChangeCallback, context); + brightnessMax, hbmData, hdrBrightnessCfg, hbmChangeCallback, context); } @VisibleForTesting HighBrightnessModeController(Injector injector, Handler handler, int width, int height, IBinder displayToken, String displayUniqueId, float brightnessMin, float brightnessMax, - HighBrightnessModeData hbmData, Runnable hbmChangeCallback, - Context context) { + HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg, + Runnable hbmChangeCallback, Context context) { mInjector = injector; mContext = context; mClock = injector.getClock(); @@ -138,7 +142,7 @@ class HighBrightnessModeController { mRecalcRunnable = this::recalculateTimeAllowance; mHdrListener = new HdrListener(); - resetHbmData(width, height, displayToken, displayUniqueId, hbmData); + resetHbmData(width, height, displayToken, displayUniqueId, hbmData, hdrBrightnessCfg); } void setAutoBrightnessEnabled(int state) { @@ -178,6 +182,13 @@ class HighBrightnessModeController { } float getHdrBrightnessValue() { + if (mHdrBrightnessCfg != null) { + float hdrBrightness = mHdrBrightnessCfg.getHdrBrightnessFromSdr(mBrightness); + if (hdrBrightness != PowerManager.BRIGHTNESS_INVALID) { + return hdrBrightness; + } + } + // For HDR brightness, we take the current brightness and scale it to the max. The reason // we do this is because we want brightness to go to HBM max when it would normally go // to normal max, meaning it should not wait to go to 10000 lux (or whatever the transition @@ -250,10 +261,11 @@ class HighBrightnessModeController { } void resetHbmData(int width, int height, IBinder displayToken, String displayUniqueId, - HighBrightnessModeData hbmData) { + HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg) { mWidth = width; mHeight = height; mHbmData = hbmData; + mHdrBrightnessCfg = hdrBrightnessCfg; mDisplayStatsId = displayUniqueId.hashCode(); unregisterHdrListener(); @@ -602,8 +614,8 @@ class HighBrightnessModeController { int maxW, int maxH, int flags) { mHandler.post(() -> { mIsHdrLayerPresent = numberOfHdrLayers > 0 - && (float) (maxW * maxH) - >= ((float) (mWidth * mHeight) * HDR_PERCENT_OF_SCREEN_REQUIRED); + && (float) (maxW * maxH) >= ((float) (mWidth * mHeight) + * mHbmData.minimumHdrPercentOfScreen); // Calling the brightness update so that we can recalculate // brightness with HDR in mind. onBrightnessChanged(mBrightness, mUnthrottledBrightness, mThrottlingReason); diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index 2e9ad50f23f6..2d870997bed7 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -32,9 +32,6 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.SigningDetails; -import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; -import com.android.server.pm.pkg.component.ParsedApexSystemService; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.os.Binder; @@ -59,6 +56,9 @@ import com.android.modules.utils.build.UnboundedSdkLevel; import com.android.server.pm.parsing.PackageParser2; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.ParsedPackage; +import com.android.server.pm.pkg.component.ParsedApexSystemService; +import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.utils.TimingsTraceAndSlog; import com.google.android.collect.Lists; @@ -414,9 +414,11 @@ public abstract class ApexManager { throws PackageManagerException; /** - * Get a map of system services defined in an apex mapped to the jar files they reside in. + * Get a list of apex system services implemented in an apex. + * + * <p>The list is sorted by initOrder for consistency. */ - public abstract Map<String, String> getApexSystemServices(); + public abstract List<ApexSystemServiceInfo> getApexSystemServices(); /** * Dumps various state information to the provided {@link PrintWriter} object. @@ -449,7 +451,7 @@ public abstract class ApexManager { * Map of all apex system services to the jar files they are contained in. */ @GuardedBy("mLock") - private Map<String, String> mApexSystemServices = new ArrayMap<>(); + private List<ApexSystemServiceInfo> mApexSystemServices = new ArrayList<>(); /** * Contains the list of {@code packageName}s of apks-in-apex for given @@ -605,14 +607,19 @@ public abstract class ApexManager { } String name = service.getName(); - if (mApexSystemServices.containsKey(name)) { - throw new IllegalStateException(String.format( - "Duplicate apex-system-service %s from %s, %s", - name, mApexSystemServices.get(name), service.getJarPath())); + for (ApexSystemServiceInfo info : mApexSystemServices) { + if (info.getName().equals(name)) { + throw new IllegalStateException(String.format( + "Duplicate apex-system-service %s from %s, %s", + name, info.mJarPath, service.getJarPath())); + } } - mApexSystemServices.put(name, service.getJarPath()); + ApexSystemServiceInfo info = new ApexSystemServiceInfo( + service.getName(), service.getJarPath(), service.getInitOrder()); + mApexSystemServices.add(info); } + Collections.sort(mApexSystemServices); mPackageNameToApexModuleName.put(packageInfo.packageName, ai.moduleName); if (ai.isActive) { if (activePackagesSet.contains(packageInfo.packageName)) { @@ -1133,7 +1140,7 @@ public abstract class ApexManager { } @Override - public Map<String, String> getApexSystemServices() { + public List<ApexSystemServiceInfo> getApexSystemServices() { synchronized (mLock) { Preconditions.checkState(mApexSystemServices != null, "APEX packages have not been scanned"); @@ -1423,10 +1430,10 @@ public abstract class ApexManager { } @Override - public Map<String, String> getApexSystemServices() { + public List<ApexSystemServiceInfo> getApexSystemServices() { // TODO(satayev): we can't really support flattened apex use case, and need to migrate // the manifest entries into system's manifest asap. - return Collections.emptyMap(); + return Collections.emptyList(); } @Override diff --git a/services/core/java/com/android/server/pm/ApexSystemServiceInfo.java b/services/core/java/com/android/server/pm/ApexSystemServiceInfo.java new file mode 100644 index 000000000000..f75ba6d165c9 --- /dev/null +++ b/services/core/java/com/android/server/pm/ApexSystemServiceInfo.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2022 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.pm; + +import android.annotation.Nullable; + +/** + * A helper class that contains information about apex-system-service to be used within system + * server process. + */ +public final class ApexSystemServiceInfo implements Comparable<ApexSystemServiceInfo> { + + final String mName; + @Nullable + final String mJarPath; + final int mInitOrder; + + public ApexSystemServiceInfo(String name, String jarPath, int initOrder) { + this.mName = name; + this.mJarPath = jarPath; + this.mInitOrder = initOrder; + } + + public String getName() { + return mName; + } + + public String getJarPath() { + return mJarPath; + } + + public int getInitOrder() { + return mInitOrder; + } + + @Override + public int compareTo(ApexSystemServiceInfo other) { + if (mInitOrder == other.mInitOrder) { + return mName.compareTo(other.mName); + } + // higher initOrder values take precedence + return -Integer.compare(mInitOrder, other.mInitOrder); + } +} diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java index 9f086e6e4a61..d745a2325b22 100644 --- a/services/core/java/com/android/server/pm/AppDataHelper.java +++ b/services/core/java/com/android/server/pm/AppDataHelper.java @@ -48,8 +48,8 @@ import com.android.server.SystemServerInitThreadPool; import com.android.server.pm.dex.ArtManagerService; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; -import com.android.server.pm.pkg.SELinuxUtil; import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.SELinuxUtil; import dalvik.system.VMRuntime; @@ -549,6 +549,10 @@ final class AppDataHelper { } public void migrateKeyStoreData(int previousAppId, int appId) { + // If previous UID is system UID, declaring inheritKeyStoreKeys is not supported. + // Silently ignore the request to migrate keys. + if (previousAppId == Process.SYSTEM_UID) return; + for (int userId : mPm.resolveUserIds(UserHandle.USER_ALL)) { int srcUid = UserHandle.getUid(userId, previousAppId); int destUid = UserHandle.getUid(userId, appId); diff --git a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java index d455be7e4a69..46fde4b59d8b 100644 --- a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java +++ b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java @@ -108,14 +108,18 @@ public class OneTimePermissionUserManager { * </p> * @param packageName The package to start a one-time permission session for * @param timeoutMillis Number of milliseconds for an app to be in an inactive state + * @param revokeAfterKilledDelayMillis Number of milliseconds to wait after the process dies + * before ending the session. Set to -1 to use default value + * for the device. * @param importanceToResetTimer The least important level to uid must be to reset the timer * @param importanceToKeepSessionAlive The least important level the uid must be to keep the - * session alive + * session alive * * @hide */ void startPackageOneTimeSession(@NonNull String packageName, long timeoutMillis, - int importanceToResetTimer, int importanceToKeepSessionAlive) { + long revokeAfterKilledDelayMillis, int importanceToResetTimer, + int importanceToKeepSessionAlive) { int uid; try { uid = mContext.getPackageManager().getPackageUid(packageName, 0); @@ -126,11 +130,15 @@ public class OneTimePermissionUserManager { synchronized (mLock) { PackageInactivityListener listener = mListeners.get(uid); - if (listener == null) { - listener = new PackageInactivityListener(uid, packageName, timeoutMillis, + if (listener != null) { + listener.updateSessionParameters(timeoutMillis, revokeAfterKilledDelayMillis, importanceToResetTimer, importanceToKeepSessionAlive); - mListeners.put(uid, listener); + return; } + listener = new PackageInactivityListener(uid, packageName, timeoutMillis, + revokeAfterKilledDelayMillis, importanceToResetTimer, + importanceToKeepSessionAlive); + mListeners.put(uid, listener); } } @@ -159,18 +167,6 @@ public class OneTimePermissionUserManager { } /** - * The delay to wait before revoking on the event an app is terminated. Recommended to be long - * enough so that apps don't lose permission on an immediate restart - */ - private long getKilledDelayMillis(boolean isSelfRevokedPermissionSession) { - if (isSelfRevokedPermissionSession) { - return 0; - } - return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS, - PROPERTY_KILLED_DELAY_CONFIG_KEY, DEFAULT_KILLED_DELAY_MILLIS); - } - - /** * Register to listen for Uids being uninstalled. This must be done outside of the * PermissionManagerService lock. */ @@ -178,18 +174,6 @@ public class OneTimePermissionUserManager { mContext.registerReceiver(mUninstallListener, new IntentFilter(Intent.ACTION_UID_REMOVED)); } - void setSelfRevokedPermissionSession(int uid) { - synchronized (mLock) { - PackageInactivityListener listener = mListeners.get(uid); - if (listener == null) { - Log.e(LOG_TAG, "Could not set session for uid " + uid - + " as self-revoke session: session not found"); - return; - } - listener.setSelfRevokedPermissionSession(); - } - } - /** * A class which watches a package for inactivity and notifies the permission controller when * the package becomes inactive @@ -200,11 +184,11 @@ public class OneTimePermissionUserManager { private final int mUid; private final @NonNull String mPackageName; - private final long mTimeout; - private final int mImportanceToResetTimer; - private final int mImportanceToKeepSessionAlive; + private long mTimeout; + private long mRevokeAfterKilledDelay; + private int mImportanceToResetTimer; + private int mImportanceToKeepSessionAlive; - private boolean mIsSelfRevokedPermissionSession; private boolean mIsAlarmSet; private boolean mIsFinished; @@ -218,16 +202,23 @@ public class OneTimePermissionUserManager { private final Object mToken = new Object(); private PackageInactivityListener(int uid, @NonNull String packageName, long timeout, - int importanceToResetTimer, int importanceToKeepSessionAlive) { + long revokeAfterkilledDelay, int importanceToResetTimer, + int importanceToKeepSessionAlive) { Log.i(LOG_TAG, "Start tracking " + packageName + ". uid=" + uid + " timeout=" + timeout + + " killedDelay=" + revokeAfterkilledDelay + " importanceToResetTimer=" + importanceToResetTimer + " importanceToKeepSessionAlive=" + importanceToKeepSessionAlive); mUid = uid; mPackageName = packageName; mTimeout = timeout; + mRevokeAfterKilledDelay = revokeAfterkilledDelay == -1 + ? DeviceConfig.getLong( + DeviceConfig.NAMESPACE_PERMISSIONS, PROPERTY_KILLED_DELAY_CONFIG_KEY, + DEFAULT_KILLED_DELAY_MILLIS) + : revokeAfterkilledDelay; mImportanceToResetTimer = importanceToResetTimer; mImportanceToKeepSessionAlive = importanceToKeepSessionAlive; @@ -247,6 +238,28 @@ public class OneTimePermissionUserManager { onImportanceChanged(mUid, mActivityManager.getPackageImportance(packageName)); } + public void updateSessionParameters(long timeoutMillis, long revokeAfterKilledDelayMillis, + int importanceToResetTimer, int importanceToKeepSessionAlive) { + synchronized (mInnerLock) { + mTimeout = Math.min(mTimeout, timeoutMillis); + mRevokeAfterKilledDelay = Math.min(mRevokeAfterKilledDelay, + revokeAfterKilledDelayMillis == -1 + ? DeviceConfig.getLong( + DeviceConfig.NAMESPACE_PERMISSIONS, + PROPERTY_KILLED_DELAY_CONFIG_KEY, DEFAULT_KILLED_DELAY_MILLIS) + : revokeAfterKilledDelayMillis); + mImportanceToResetTimer = Math.min(importanceToResetTimer, mImportanceToResetTimer); + mImportanceToKeepSessionAlive = Math.min(importanceToKeepSessionAlive, + mImportanceToKeepSessionAlive); + Log.v(LOG_TAG, + "Updated params for " + mPackageName + ". timeout=" + mTimeout + + " killedDelay=" + mRevokeAfterKilledDelay + + " importanceToResetTimer=" + mImportanceToResetTimer + + " importanceToKeepSessionAlive=" + mImportanceToKeepSessionAlive); + onImportanceChanged(mUid, mActivityManager.getPackageImportance(mPackageName)); + } + } + private void onImportanceChanged(int uid, int importance) { if (uid != mUid) { return; @@ -271,7 +284,7 @@ public class OneTimePermissionUserManager { } onImportanceChanged(mUid, imp); } - }, mToken, getKilledDelayMillis(mIsSelfRevokedPermissionSession)); + }, mToken, mRevokeAfterKilledDelay); return; } if (importance > mImportanceToResetTimer) { @@ -307,14 +320,6 @@ public class OneTimePermissionUserManager { } /** - * Marks the session as a self-revoke session, which does not delay the revocation when - * the app is restarting. - */ - public void setSelfRevokedPermissionSession() { - mIsSelfRevokedPermissionSession = true; - } - - /** * Set the alarm which will callback when the package is inactive */ @GuardedBy("mInnerLock") 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 edc0e3d64c42..695d6dd0bc76 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -68,7 +68,6 @@ import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; -import com.android.internal.infra.AndroidFuture; import com.android.internal.util.Preconditions; import com.android.internal.util.function.TriFunction; import com.android.server.LocalServices; @@ -388,7 +387,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { @Override public void startOneTimePermissionSession(String packageName, @UserIdInt int userId, - long timeoutMillis, int importanceToResetTimer, int importanceToKeepSessionAlive) { + long timeoutMillis, long revokeAfterKilledDelayMillis, int importanceToResetTimer, + int importanceToKeepSessionAlive) { mContext.enforceCallingOrSelfPermission( Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS, "Must hold " + Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS @@ -398,7 +398,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { final long token = Binder.clearCallingIdentity(); try { getOneTimePermissionUserManager(userId).startPackageOneTimeSession(packageName, - timeoutMillis, importanceToResetTimer, importanceToKeepSessionAlive); + timeoutMillis, revokeAfterKilledDelayMillis, importanceToResetTimer, + importanceToKeepSessionAlive); } finally { Binder.restoreCallingIdentity(token); } @@ -563,16 +564,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { @Override public void revokeOwnPermissionsOnKill(@NonNull String packageName, @NonNull List<String> permissions) { - final int callingUid = Binder.getCallingUid(); - final int callingUserId = UserHandle.getUserId(callingUid); - AndroidFuture<Void> future = new AndroidFuture<>(); - future.whenComplete((result, err) -> { - if (err == null) { - getOneTimePermissionUserManager(callingUserId) - .setSelfRevokedPermissionSession(callingUid); - } - }); - mPermissionManagerServiceImpl.revokeOwnPermissionsOnKill(packageName, permissions, future); + mPermissionManagerServiceImpl.revokeOwnPermissionsOnKill(packageName, permissions); } @Override diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index db9c1b56e4d8..ed351fd4aef9 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -108,7 +108,6 @@ import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.compat.IPlatformCompat; -import com.android.internal.infra.AndroidFuture; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.internal.os.RoSystemProperties; @@ -1592,8 +1591,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } @Override - public void revokeOwnPermissionsOnKill(String packageName, List<String> permissions, - AndroidFuture<Void> callback) { + public void revokeOwnPermissionsOnKill(String packageName, List<String> permissions) { final int callingUid = Binder.getCallingUid(); int callingUserId = UserHandle.getUserId(callingUid); int targetPackageUid = mPackageManagerInt.getPackageUid(packageName, 0, callingUserId); @@ -1608,8 +1606,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt + permName + " because it does not hold that permission"); } } - mPermissionControllerManager.revokeOwnPermissionsOnKill(packageName, permissions, - callback); + mPermissionControllerManager.revokeOwnPermissionsOnKill(packageName, permissions); } private boolean mayManageRolePermission(int uid) { diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java index 91c558b2f35e..3e28320a2130 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java @@ -27,7 +27,6 @@ import android.content.pm.permission.SplitPermissionInfoParcelable; import android.permission.IOnPermissionsChangeListener; import android.permission.PermissionManagerInternal; -import com.android.internal.infra.AndroidFuture; import com.android.server.pm.parsing.pkg.AndroidPackage; import java.io.FileDescriptor; @@ -338,16 +337,16 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte * <li>Each permission in {@code permissions} must be a runtime permission. * </ul> * <p> - * For every permission in {@code permissions}, the entire permission group it belongs to will - * be revoked. This revocation happens asynchronously and kills all processes running in the - * same UID as {@code packageName}. It will be triggered once it is safe to do so. + * Background permissions which have no corresponding foreground permission still granted once + * the revocation is effective will also be revoked. + * <p> + * This revocation happens asynchronously and kills all processes running in the same UID as + * {@code packageName}. It will be triggered once it is safe to do so. * * @param packageName The name of the package for which the permissions will be revoked. * @param permissions List of permissions to be revoked. - * @param callback Callback called when the revocation request has been completed. */ - void revokeOwnPermissionsOnKill(String packageName, List<String> permissions, - AndroidFuture<Void> callback); + void revokeOwnPermissionsOnKill(String packageName, List<String> permissions); /** * Get whether you should show UI with rationale for requesting a permission. You should do this diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java index 586d2c48d27d..cf478b1da2e4 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java @@ -34,4 +34,7 @@ public interface ParsedApexSystemService { @Nullable String getMaxSdkVersion(); + + int getInitOrder(); + } diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java index 1e427d015cc1..167aba301f35 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java @@ -48,18 +48,18 @@ public class ParsedApexSystemServiceImpl implements ParsedApexSystemService, Par @Nullable private String maxSdkVersion; + private int initOrder; + public ParsedApexSystemServiceImpl() { } - - // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceImpl.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -71,13 +71,15 @@ public class ParsedApexSystemServiceImpl implements ParsedApexSystemService, Par @NonNull String name, @Nullable String jarPath, @Nullable String minSdkVersion, - @Nullable String maxSdkVersion) { + @Nullable String maxSdkVersion, + int initOrder) { this.name = name; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, name); this.jarPath = jarPath; this.minSdkVersion = minSdkVersion; this.maxSdkVersion = maxSdkVersion; + this.initOrder = initOrder; // onConstructed(); // You can define this method to get a callback } @@ -103,6 +105,11 @@ public class ParsedApexSystemServiceImpl implements ParsedApexSystemService, Par } @DataClass.Generated.Member + public int getInitOrder() { + return initOrder; + } + + @DataClass.Generated.Member public @NonNull ParsedApexSystemServiceImpl setName(@NonNull String value) { name = value; com.android.internal.util.AnnotationValidations.validate( @@ -129,6 +136,12 @@ public class ParsedApexSystemServiceImpl implements ParsedApexSystemService, Par } @DataClass.Generated.Member + public @NonNull ParsedApexSystemServiceImpl setInitOrder( int value) { + initOrder = value; + return this; + } + + @DataClass.Generated.Member static Parcelling<String> sParcellingForName = Parcelling.Cache.get( Parcelling.BuiltIn.ForInternedString.class); @@ -187,6 +200,7 @@ public class ParsedApexSystemServiceImpl implements ParsedApexSystemService, Par sParcellingForJarPath.parcel(jarPath, dest, flags); sParcellingForMinSdkVersion.parcel(minSdkVersion, dest, flags); sParcellingForMaxSdkVersion.parcel(maxSdkVersion, dest, flags); + dest.writeInt(initOrder); } @Override @@ -205,6 +219,7 @@ public class ParsedApexSystemServiceImpl implements ParsedApexSystemService, Par String _jarPath = sParcellingForJarPath.unparcel(in); String _minSdkVersion = sParcellingForMinSdkVersion.unparcel(in); String _maxSdkVersion = sParcellingForMaxSdkVersion.unparcel(in); + int _initOrder = in.readInt(); this.name = _name; com.android.internal.util.AnnotationValidations.validate( @@ -212,6 +227,7 @@ public class ParsedApexSystemServiceImpl implements ParsedApexSystemService, Par this.jarPath = _jarPath; this.minSdkVersion = _minSdkVersion; this.maxSdkVersion = _maxSdkVersion; + this.initOrder = _initOrder; // onConstructed(); // You can define this method to get a callback } @@ -231,10 +247,10 @@ public class ParsedApexSystemServiceImpl implements ParsedApexSystemService, Par }; @DataClass.Generated( - time = 1641431950080L, + time = 1643723578605L, codegenVersion = "1.0.23", - sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceImpl.java", - inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedApexSystemService, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)") + sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java", + inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nprivate int initOrder\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [com.android.server.pm.pkg.component.ParsedApexSystemService, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java index 38a6f5a356e7..ed9aa2e6860a 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java @@ -53,10 +53,13 @@ public class ParsedApexSystemServiceUtils { R.styleable.AndroidManifestApexSystemService_minSdkVersion); String maxSdkVersion = sa.getString( R.styleable.AndroidManifestApexSystemService_maxSdkVersion); + int initOrder = sa.getInt(R.styleable.AndroidManifestApexSystemService_initOrder, 0); systemService.setName(className) .setMinSdkVersion(minSdkVersion) - .setMaxSdkVersion(maxSdkVersion); + .setMaxSdkVersion(maxSdkVersion) + .setInitOrder(initOrder); + if (!TextUtils.isEmpty(jarPath)) { systemService.setJarPath(jarPath); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 8d4ae908e79f..ca4d7178fbcc 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -24,6 +24,7 @@ import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS; import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS; +import static android.Manifest.permission.MANAGE_GAME_ACTIVITY; import static android.Manifest.permission.READ_FRAME_BUFFER; import static android.Manifest.permission.REMOVE_TASKS; import static android.Manifest.permission.START_TASKS_FROM_RECENTS; @@ -1771,6 +1772,43 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override + public int startActivityFromGameSession(IApplicationThread caller, String callingPackage, + String callingFeatureId, int callingPid, int callingUid, Intent intent, int taskId, + int userId) { + if (checkCallingPermission(MANAGE_GAME_ACTIVITY) != PERMISSION_GRANTED) { + final String msg = "Permission Denial: startActivityFromGameSession() from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid() + + " requires " + MANAGE_GAME_ACTIVITY; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + assertPackageMatchesCallingUid(callingPackage); + + final ActivityOptions activityOptions = ActivityOptions.makeBasic(); + activityOptions.setLaunchTaskId(taskId); + + userId = handleIncomingUser(callingPid, callingUid, userId, "startActivityFromGameSession"); + + final long origId = Binder.clearCallingIdentity(); + try { + return getActivityStartController() + .obtainStarter(intent, "startActivityFromGameSession") + .setCaller(caller) + .setCallingUid(callingUid) + .setCallingPid(callingPid) + .setCallingPackage(intent.getPackage()) + .setCallingFeatureId(callingFeatureId) + .setUserId(userId) + .setActivityOptions(activityOptions.toBundle()) + .setRealCallingUid(Binder.getCallingUid()) + .execute(); + } finally { + Binder.restoreCallingIdentity(origId); + } + } + + @Override public BackNavigationInfo startBackNavigation() { mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS, "startBackNavigation()"); diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index a8779fa79675..45a6cb9d3920 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -29,6 +29,7 @@ import android.os.SystemProperties; import android.util.Slog; import android.view.SurfaceControl; import android.window.BackNavigationInfo; +import android.window.IOnBackInvokedCallback; import android.window.TaskSnapshot; import com.android.internal.annotations.VisibleForTesting; @@ -91,29 +92,41 @@ class BackNavigationController { HardwareBuffer screenshotBuffer = null; int prevTaskId; int prevUserId; + IOnBackInvokedCallback callback; synchronized (task.mWmService.mGlobalLock) { activityRecord = task.topRunningActivity(); removedWindowContainer = activityRecord; taskWindowConfiguration = task.getTaskInfo().configuration.windowConfiguration; - ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "startBackNavigation task=%s, topRunningActivity=%s", - task, activityRecord); - - // IME is visible, back gesture will dismiss it, nothing to preview. - if (task.getDisplayContent().getImeContainer().isVisible()) { - return null; - } - - // Current Activity is home, there is no previous activity to display - if (activityRecord.isActivityTypeHome()) { - return null; + WindowState topChild = activityRecord.getTopChild(); + callback = topChild.getOnBackInvokedCallback(); + + ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "startBackNavigation task=%s, " + + "topRunningActivity=%s, topWindow=%s backCallback=%s", + task, activityRecord, topChild, + callback != null ? callback.getClass().getSimpleName() : null); + + // For IME and Home, either a callback is registered, or we do nothing. In both cases, + // we don't need to pass the leashes below. + if (task.getDisplayContent().getImeContainer().isVisible() + || activityRecord.isActivityTypeHome()) { + if (callback != null) { + return new BackNavigationInfo(BackNavigationInfo.TYPE_CALLBACK, + null /* topWindowLeash */, null /* screenshotSurface */, + null /* screenshotBuffer */, null /* taskWindowConfiguration */, + null /* onBackNavigationDone */, callback /* onBackInvokedCallback */); + } else { + return null; + } } prev = task.getActivity( (r) -> !r.finishing && r.getTask() == task && !r.isTopRunningActivity()); - if (prev != null) { + if (callback != null) { + backType = BackNavigationInfo.TYPE_CALLBACK; + } else if (prev != null) { backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY; } else if (task.returnsToHomeRootTask()) { prevTask = null; @@ -148,7 +161,7 @@ class BackNavigationController { // Prepare a leash to animate the current top window animLeash = removedWindowContainer.makeAnimationLeash() - .setName("BackPreview Leash") + .setName("BackPreview Leash for " + removedWindowContainer) .setHidden(false) .setBLASTLayer() .build(); @@ -158,7 +171,7 @@ class BackNavigationController { } SurfaceControl.Builder builder = new SurfaceControl.Builder() - .setName("BackPreview Screenshot") + .setName("BackPreview Screenshot for " + prev) .setParent(animationLeashParent) .setHidden(false) .setBLASTLayer(); @@ -171,6 +184,10 @@ class BackNavigationController { screenshotBuffer = getTaskSnapshot(prevTaskId, prevUserId); } } + + // The Animation leash needs to be above the screenshot surface, but the animation leash + // needs to be added before to be in the synchronized block. + tx.setLayer(animLeash, 1); tx.apply(); WindowContainer<?> finalRemovedWindowContainer = removedWindowContainer; @@ -183,13 +200,16 @@ class BackNavigationController { return null; } + RemoteCallback onBackNavigationDone = new RemoteCallback( + result -> resetSurfaces(finalRemovedWindowContainer + )); return new BackNavigationInfo(backType, animLeash, screenshotSurface, screenshotBuffer, taskWindowConfiguration, - new RemoteCallback(result -> resetSurfaces(finalRemovedWindowContainer - ))); + onBackNavigationDone, + callback); } diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 2ae2b4370522..f26c53938077 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -885,8 +885,16 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } @Override - public void setOnBackInvokedCallback(IWindow iWindow, - IOnBackInvokedCallback iOnBackInvokedCallback) throws RemoteException { - // TODO: Set the callback to the WindowState of the window. + public void setOnBackInvokedCallback(IWindow window, + IOnBackInvokedCallback onBackInvokedCallback) throws RemoteException { + synchronized (mService.mGlobalLock) { + WindowState windowState = mService.windowForClientLocked(this, window, false); + if (windowState == null) { + Slog.e(TAG_WM, + "setOnBackInvokedCallback(): Can't find window state for window:" + window); + } else { + windowState.setOnBackInvokedCallback(onBackInvokedCallback); + } + } } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 97735a29b027..b84ef773f85a 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -2197,10 +2197,6 @@ class Task extends TaskFragment { final Rect taskBounds = getBounds(); width = taskBounds.width(); height = taskBounds.height(); - - final int outset = getTaskOutset(); - width += 2 * outset; - height += 2 * outset; } if (width == mLastSurfaceSize.x && height == mLastSurfaceSize.y) { return; @@ -2209,28 +2205,6 @@ class Task extends TaskFragment { mLastSurfaceSize.set(width, height); } - /** - * Calculate an amount by which to expand the task bounds in each direction. - * Used to make room for shadows in the pinned windowing mode. - */ - int getTaskOutset() { - // If we are drawing shadows on the task then don't outset the root task. - if (mWmService.mRenderShadowsInCompositor) { - return 0; - } - DisplayContent displayContent = getDisplayContent(); - if (inPinnedWindowingMode() && displayContent != null) { - final DisplayMetrics displayMetrics = displayContent.getDisplayMetrics(); - - // We multiply by two to match the client logic for converting view elevation - // to insets, as in {@link WindowManager.LayoutParams#setSurfaceInsets} - return (int) Math.ceil( - mWmService.dipToPixel(PINNED_WINDOWING_MODE_ELEVATION_IN_DIP, displayMetrics) - * 2); - } - return 0; - } - @VisibleForTesting Point getLastSurfaceSize() { return mLastSurfaceSize; @@ -4338,7 +4312,7 @@ class Task extends TaskFragment { */ private void updateShadowsRadius(boolean taskIsFocused, SurfaceControl.Transaction pendingTransaction) { - if (!mWmService.mRenderShadowsInCompositor || !isRootTask()) return; + if (!isRootTask()) return; final float newShadowRadius = getShadowRadius(taskIsFocused); if (mShadowRadius != newShadowRadius) { @@ -6015,14 +5989,6 @@ class Task extends TaskFragment { scheduleAnimation(); } - @Override - void getRelativePosition(Point outPos) { - super.getRelativePosition(outPos); - final int outset = getTaskOutset(); - outPos.x -= outset; - outPos.y -= outset; - } - private Point getRelativePosition() { Point position = new Point(); getRelativePosition(position); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 5751777fc2cd..22c430ff8016 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -462,11 +462,6 @@ public class WindowManagerService extends IWindowManager.Stub int mVr2dDisplayId = INVALID_DISPLAY; boolean mVrModeEnabled = false; - /* If true, shadows drawn around the window will be rendered by the system compositor. If - * false, shadows will be drawn by the client by setting an elevation on the root view and - * the contents will be inset by the shadow radius. */ - boolean mRenderShadowsInCompositor = false; - /** * Tracks a map of input tokens to info that is used to decide whether to intercept * a key event. @@ -811,8 +806,6 @@ public class WindowManagerService extends IWindowManager.Stub resolver.registerContentObserver(mForceResizableUri, false, this, UserHandle.USER_ALL); resolver.registerContentObserver(mDevEnableNonResizableMultiWindowUri, false, this, UserHandle.USER_ALL); - resolver.registerContentObserver(mRenderShadowsInCompositorUri, false, this, - UserHandle.USER_ALL); resolver.registerContentObserver(mDisplaySettingsPathUri, false, this, UserHandle.USER_ALL); } @@ -853,11 +846,6 @@ public class WindowManagerService extends IWindowManager.Stub return; } - if (mRenderShadowsInCompositorUri.equals(uri)) { - setShadowRenderer(); - return; - } - if (mDisplaySettingsPathUri.equals(uri)) { updateDisplaySettingsLocation(); return; @@ -972,11 +960,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - private void setShadowRenderer() { - mRenderShadowsInCompositor = Settings.Global.getInt(mContext.getContentResolver(), - DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR, 1) != 0; - } - PowerManager mPowerManager; PowerManagerInternal mPowerManagerInternal; @@ -1395,7 +1378,6 @@ public class WindowManagerService extends IWindowManager.Stub float[] spotColor = {0.f, 0.f, 0.f, spotShadowAlpha}; SurfaceControl.setGlobalShadowSettings(ambientColor, spotColor, lightY, lightZ, lightRadius); - setShadowRenderer(); } /** diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 79c64b19882d..0d72e9a567bc 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -106,6 +106,7 @@ import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; @@ -245,6 +246,7 @@ import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.window.ClientWindowFrames; +import android.window.IOnBackInvokedCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.KeyInterceptionInfo; @@ -845,6 +847,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } }; + /** + * @see #setOnBackInvokedCallback(IOnBackInvokedCallback) + */ + private IOnBackInvokedCallback mOnBackInvokedCallback; + @Override WindowState asWindowState() { return this; @@ -1061,6 +1068,22 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return true; } + /** + * Used by {@link android.window.WindowOnBackInvokedDispatcher} to set the callback to be + * called when a back navigation action is initiated. + * @see BackNavigationController + */ + void setOnBackInvokedCallback(@Nullable IOnBackInvokedCallback onBackInvokedCallback) { + ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "%s: Setting back callback %s", + this, onBackInvokedCallback); + mOnBackInvokedCallback = onBackInvokedCallback; + } + + @Nullable + IOnBackInvokedCallback getOnBackInvokedCallback() { + return mOnBackInvokedCallback; + } + interface PowerManagerWrapper { void wakeUp(long time, @WakeReason int reason, String details); @@ -5398,17 +5421,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP outPoint.offset(-parentBounds.left, -parentBounds.top); } - Task rootTask = getRootTask(); - - // If we have root task outsets, that means the top-left - // will be outset, and we need to inset ourselves - // to account for it. If we actually have shadows we will - // then un-inset ourselves by the surfaceInsets. - if (rootTask != null) { - final int outset = rootTask.getTaskOutset(); - outPoint.offset(outset, outset); - } - // The surface size is larger than the window if the window has positive surface insets. transformSurfaceInsetsPosition(mTmpPoint, mAttrs.surfaceInsets); outPoint.offset(-mTmpPoint.x, -mTmpPoint.y); diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index 166a0f5272cf..0584604ac7b9 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -509,7 +509,7 @@ uint32_t GnssCallback::getConstellationType( template <class T_list, class T_sv_info> Return<void> GnssCallback::gnssSvStatusCbImpl(const T_list& svStatus) { // In HIDL or AIDL v1, if no listener is registered, do not report svInfoList to the framework. - if (gnssHalAidl == nullptr || gnssHalAidl->getInterfaceVersion() == 1) { + if (gnssHalAidl == nullptr || gnssHalAidl->getInterfaceVersion() <= 1) { if (!isSvStatusRegistered) { return Void(); } @@ -695,7 +695,7 @@ Status GnssCallbackAidl::gnssLocationCb(const GnssLocationAidl& location) { Status GnssCallbackAidl::gnssNmeaCb(const int64_t timestamp, const std::string& nmea) { // In AIDL v1, if no listener is registered, do not report nmea to the framework. - if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() == 1) { + if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() <= 1) { if (!isNmeaRegistered) { return Status::ok(); } diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index 79d80366efd3..be0ddc155ae0 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -123,6 +123,18 @@ <xs:annotation name="nonnull"/> <xs:annotation name="final"/> </xs:element> + <!-- The minimum HDR video size at which high-brightness-mode is allowed to operate. + Default is 0.5 if not specified--> + <xs:element name="minimumHdrPercentOfScreen" type="nonNegativeDecimal" + minOccurs="0" maxOccurs="1"> + <xs:annotation name="nullable"/> + <xs:annotation name="final"/> + </xs:element> + <!-- This LUT specifies how to boost HDR brightness at given SDR brightness (nits). --> + <xs:element type="sdrHdrRatioMap" name="sdrHdrRatioMap" minOccurs="0" maxOccurs="1"> + <xs:annotation name="nullable"/> + <xs:annotation name="final"/> + </xs:element> </xs:all> <xs:attribute name="enabled" type="xs:boolean" use="optional"/> </xs:complexType> @@ -158,6 +170,14 @@ </xs:restriction> </xs:simpleType> + <!-- Maps to DisplayDeviceConfig.INTERPOLATION_* values. --> + <xs:simpleType name="interpolation"> + <xs:restriction base="xs:string"> + <xs:enumeration value="default"/> + <xs:enumeration value="linear"/> + </xs:restriction> + </xs:simpleType> + <xs:complexType name="thermalThrottling"> <xs:complexType> <xs:element type="brightnessThrottlingMap" name="brightnessThrottlingMap"> @@ -196,6 +216,7 @@ <xs:annotation name="final"/> </xs:element> </xs:sequence> + <xs:attribute name="interpolation" type="interpolation" use="optional"/> </xs:complexType> <xs:complexType name="point"> @@ -211,6 +232,28 @@ </xs:sequence> </xs:complexType> + <xs:complexType name="sdrHdrRatioMap"> + <xs:sequence> + <xs:element name="point" type="sdrHdrRatioPoint" maxOccurs="unbounded" minOccurs="2"> + <xs:annotation name="nonnull"/> + <xs:annotation name="final"/> + </xs:element> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="sdrHdrRatioPoint"> + <xs:sequence> + <xs:element type="nonNegativeDecimal" name="sdrNits"> + <xs:annotation name="nonnull"/> + <xs:annotation name="final"/> + </xs:element> + <xs:element type="nonNegativeDecimal" name="hdrRatio"> + <xs:annotation name="nonnull"/> + <xs:annotation name="final"/> + </xs:element> + </xs:sequence> + </xs:complexType> + <xs:simpleType name="nonNegativeDecimal"> <xs:restriction base="xs:decimal"> <xs:minInclusive value="0.0"/> diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index 0b7df4d0bc7c..2890d686186e 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -90,23 +90,35 @@ package com.android.server.display.config { ctor public HighBrightnessMode(); method @NonNull public final boolean getAllowInLowPowerMode_all(); method public boolean getEnabled(); + method @Nullable public final java.math.BigDecimal getMinimumHdrPercentOfScreen_all(); method @NonNull public final java.math.BigDecimal getMinimumLux_all(); method @Nullable public final com.android.server.display.config.RefreshRateRange getRefreshRate_all(); + method @Nullable public final com.android.server.display.config.SdrHdrRatioMap getSdrHdrRatioMap_all(); method @NonNull public final com.android.server.display.config.ThermalStatus getThermalStatusLimit_all(); method public com.android.server.display.config.HbmTiming getTiming_all(); method @NonNull public final java.math.BigDecimal getTransitionPoint_all(); method public final void setAllowInLowPowerMode_all(@NonNull boolean); method public void setEnabled(boolean); + method public final void setMinimumHdrPercentOfScreen_all(@Nullable java.math.BigDecimal); method public final void setMinimumLux_all(@NonNull java.math.BigDecimal); method public final void setRefreshRate_all(@Nullable com.android.server.display.config.RefreshRateRange); + method public final void setSdrHdrRatioMap_all(@Nullable com.android.server.display.config.SdrHdrRatioMap); method public final void setThermalStatusLimit_all(@NonNull com.android.server.display.config.ThermalStatus); method public void setTiming_all(com.android.server.display.config.HbmTiming); method public final void setTransitionPoint_all(@NonNull java.math.BigDecimal); } + public enum Interpolation { + method public String getRawName(); + enum_constant public static final com.android.server.display.config.Interpolation _default; + enum_constant public static final com.android.server.display.config.Interpolation linear; + } + public class NitsMap { ctor public NitsMap(); + method public com.android.server.display.config.Interpolation getInterpolation(); method @NonNull public final java.util.List<com.android.server.display.config.Point> getPoint(); + method public void setInterpolation(com.android.server.display.config.Interpolation); } public class Point { @@ -125,6 +137,19 @@ package com.android.server.display.config { method public final void setMinimum(java.math.BigInteger); } + public class SdrHdrRatioMap { + ctor public SdrHdrRatioMap(); + method @NonNull public final java.util.List<com.android.server.display.config.SdrHdrRatioPoint> getPoint(); + } + + public class SdrHdrRatioPoint { + ctor public SdrHdrRatioPoint(); + method @NonNull public final java.math.BigDecimal getHdrRatio(); + method @NonNull public final java.math.BigDecimal getSdrNits(); + method public final void setHdrRatio(@NonNull java.math.BigDecimal); + method public final void setSdrNits(@NonNull java.math.BigDecimal); + } + public class SensorDetails { ctor public SensorDetails(); method @Nullable public final String getName(); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index d3ddb4726b01..c2dec067c870 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -154,6 +154,7 @@ import com.android.server.os.NativeTombstoneManagerService; import com.android.server.os.SchedulingPolicyService; import com.android.server.people.PeopleService; import com.android.server.pm.ApexManager; +import com.android.server.pm.ApexSystemServiceInfo; import com.android.server.pm.CrossProfileAppsService; import com.android.server.pm.DataLoaderManagerService; import com.android.server.pm.DynamicCodeLoggingService; @@ -224,8 +225,8 @@ import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import java.util.LinkedList; +import java.util.List; import java.util.Locale; -import java.util.Map; import java.util.Timer; import java.util.TreeSet; import java.util.concurrent.CountDownLatch; @@ -1471,7 +1472,7 @@ public final class SystemServer implements Dumpable { // TelecomLoader hooks into classes with defined HFP logic, // so check for either telephony or microphone. if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_MICROPHONE) || - mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) { + mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) { t.traceBegin("StartTelecomLoaderService"); mSystemServiceManager.startService(TelecomLoaderService.class); t.traceEnd(); @@ -1479,7 +1480,7 @@ public final class SystemServer implements Dumpable { t.traceBegin("StartTelephonyRegistry"); telephonyRegistry = new TelephonyRegistry( - context, new TelephonyRegistry.ConfigurationProvider()); + context, new TelephonyRegistry.ConfigurationProvider()); ServiceManager.addService("telephony.registry", telephonyRegistry); t.traceEnd(); @@ -2997,7 +2998,9 @@ public final class SystemServer implements Dumpable { t.traceEnd(); t.traceBegin("MakeTelephonyRegistryReady"); try { - if (telephonyRegistryF != null) telephonyRegistryF.systemRunning(); + if (telephonyRegistryF != null) { + telephonyRegistryF.systemRunning(); + } } catch (Throwable e) { reportWtf("Notifying TelephonyRegistry running", e); } @@ -3062,10 +3065,12 @@ public final class SystemServer implements Dumpable { */ private void startApexServices(@NonNull TimingsTraceAndSlog t) { t.traceBegin("startApexServices"); - Map<String, String> services = ApexManager.getInstance().getApexSystemServices(); - // TODO(satayev): introduce android:order for services coming the same apexes - for (String name : new TreeSet<>(services.keySet())) { - String jarPath = services.get(name); + // TODO(b/192880996): get the list from "android" package, once the manifest entries + // are migrated to system manifest. + List<ApexSystemServiceInfo> services = ApexManager.getInstance().getApexSystemServices(); + for (ApexSystemServiceInfo info : services) { + String name = info.getName(); + String jarPath = info.getJarPath(); t.traceBegin("starting " + name); if (TextUtils.isEmpty(jarPath)) { mSystemServiceManager.startService(name); diff --git a/services/tests/apexsystemservices/apexes/test_com.android.server/Android.bp b/services/tests/apexsystemservices/apexes/test_com.android.server/Android.bp index 16d624199d5a..0a9b7b1302cc 100644 --- a/services/tests/apexsystemservices/apexes/test_com.android.server/Android.bp +++ b/services/tests/apexsystemservices/apexes/test_com.android.server/Android.bp @@ -32,7 +32,7 @@ apex_test { name: "test_com.android.server", manifest: "manifest.json", androidManifest: "AndroidManifest.xml", - java_libs: ["FakeApexSystemService"], + java_libs: ["FakeApexSystemServices"], file_contexts: ":apex.test-file_contexts", key: "test_com.android.server.key", updatable: false, diff --git a/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml b/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml index eb741cad4ad9..6bec28463dc3 100644 --- a/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml +++ b/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml @@ -21,21 +21,29 @@ <application android:hasCode="false" android:testOnly="true"> <apex-system-service android:name="com.android.server.testing.FakeApexSystemService" - android:path="/apex/test_com.android.server/javalib/FakeApexSystemService.jar" - android:minSdkVersion="30"/> + android:path="/apex/test_com.android.server/javalib/FakeApexSystemServices.jar" + android:minSdkVersion="30" + /> + + <apex-system-service + android:name="com.android.server.testing.FakeApexSystemService2" + android:path="/apex/test_com.android.server/javalib/FakeApexSystemServices.jar" + android:minSdkVersion="30" + android:initOrder="1" + /> <!-- Always inactive system service, since maxSdkVersion is low --> <apex-system-service - android:name="com.android.apex.test.OldApexSystemService" - android:path="/apex/com.android.apex.test/javalib/fake.jar" + android:name="com.android.server.testing.OldApexSystemService" + android:path="/apex/test_com.android.server/javalib/fake.jar" android:minSdkVersion="1" android:maxSdkVersion="1" /> <!-- Always inactive system service, since minSdkVersion is high --> <apex-system-service - android:name="com.android.apex.test.NewApexSystemService" - android:path="/apex/com.android.apex.test/javalib/fake.jar" + android:name="com.android.server.testing.NewApexSystemService" + android:path="/apex/test_com.android.server/javalib/fake.jar" android:minSdkVersion="999999" /> </application> diff --git a/services/tests/apexsystemservices/service/Android.bp b/services/tests/apexsystemservices/services/Android.bp index 9d04f39f2237..477ea4cdad37 100644 --- a/services/tests/apexsystemservices/service/Android.bp +++ b/services/tests/apexsystemservices/services/Android.bp @@ -8,7 +8,7 @@ package { } java_library { - name: "FakeApexSystemService", + name: "FakeApexSystemServices", srcs: ["**/*.java"], sdk_version: "system_server_current", libs: [ diff --git a/services/tests/apexsystemservices/service/src/com/android/server/testing/FakeApexSystemService.java b/services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService.java index 4947c3455cc7..4947c3455cc7 100644 --- a/services/tests/apexsystemservices/service/src/com/android/server/testing/FakeApexSystemService.java +++ b/services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService.java diff --git a/services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService2.java b/services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService2.java new file mode 100644 index 000000000000..e83343b9c996 --- /dev/null +++ b/services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService2.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021 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.testing; + +import android.content.Context; +import android.util.Log; + +import androidx.annotation.NonNull; + +import com.android.server.SystemService; + +/** + * A fake system service that just logs when it is started. + */ +public class FakeApexSystemService2 extends SystemService { + + private static final String TAG = "FakeApexSystemService"; + + public FakeApexSystemService2(@NonNull Context context) { + super(context); + } + + @Override + public void onStart() { + Log.d(TAG, "FakeApexSystemService2 onStart"); + } +} diff --git a/services/tests/apexsystemservices/src/com/android/server/ApexSystemServicesTestCases.java b/services/tests/apexsystemservices/src/com/android/server/ApexSystemServicesTestCases.java index 2b453a9265bb..10635a138eb9 100644 --- a/services/tests/apexsystemservices/src/com/android/server/ApexSystemServicesTestCases.java +++ b/services/tests/apexsystemservices/src/com/android/server/ApexSystemServicesTestCases.java @@ -37,9 +37,15 @@ import org.junit.rules.RuleChain; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; +import java.util.Objects; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + @RunWith(DeviceJUnit4ClassRunner.class) public class ApexSystemServicesTestCases extends BaseHostJUnit4Test { + private static final int REBOOT_TIMEOUT = 1 * 60 * 1000; + private final InstallUtilsHost mHostUtils = new InstallUtilsHost(this); private final TemporaryFolder mTemporaryFolder = new TemporaryFolder(); private final SystemPreparer mPreparer = new SystemPreparer(mTemporaryFolder, this::getDevice); @@ -67,7 +73,7 @@ public class ApexSystemServicesTestCases extends BaseHostJUnit4Test { } @Test - public void noApexSystemServerStartsWithoutApex() throws Exception { + public void testNoApexSystemServiceStartsWithoutApex() throws Exception { mPreparer.reboot(); assertThat(getFakeApexSystemServiceLogcat()) @@ -75,20 +81,55 @@ public class ApexSystemServicesTestCases extends BaseHostJUnit4Test { } @Test - public void apexSystemServerStarts() throws Exception { + public void testApexSystemServiceStarts() throws Exception { // Pre-install the apex String apex = "test_com.android.server.apex"; mPreparer.pushResourceFile(apex, "/system/apex/" + apex); // Reboot activates the apex mPreparer.reboot(); + mDevice.waitForBootComplete(REBOOT_TIMEOUT); + assertThat(getFakeApexSystemServiceLogcat()) .contains("FakeApexSystemService onStart"); } + @Test + public void testInitOrder() throws Exception { + // Pre-install the apex + String apex = "test_com.android.server.apex"; + mPreparer.pushResourceFile(apex, "/system/apex/" + apex); + // Reboot activates the apex + mPreparer.reboot(); + + mDevice.waitForBootComplete(REBOOT_TIMEOUT); + + assertThat(getFakeApexSystemServiceLogcat().lines() + .map(ApexSystemServicesTestCases::getDebugMessage) + .filter(Objects::nonNull) + .collect(Collectors.toList())) + .containsExactly( + // Second service has a higher initOrder and must be started first + "FakeApexSystemService2 onStart", + "FakeApexSystemService onStart" + ) + .inOrder(); + } + private String getFakeApexSystemServiceLogcat() throws DeviceNotAvailableException { return mDevice.executeAdbCommand("logcat", "-v", "brief", "-d", "FakeApexSystemService:D", "*:S"); } + private static final Pattern DEBUG_MESSAGE = + Pattern.compile("(FakeApexSystemService[0-9]* onStart)"); + + private static String getDebugMessage(String logcatLine) { + return DEBUG_MESSAGE.matcher(logcatLine) + .results() + .map(m -> m.group(1)) + .findFirst() + .orElse(null); + } + } diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java index 6203c2f54f07..53fa3e2db376 100644 --- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java @@ -22,11 +22,14 @@ import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR; import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF; import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT; + import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED; import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED; import static com.android.server.display.AutomaticBrightnessController .AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE; +import static com.android.server.display.DisplayDeviceConfig.HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT; + import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID; import static org.junit.Assert.assertEquals; @@ -111,7 +114,8 @@ public class HighBrightnessModeControllerTest { private static final HighBrightnessModeData DEFAULT_HBM_DATA = new HighBrightnessModeData(MINIMUM_LUX, TRANSITION_POINT, TIME_WINDOW_MILLIS, TIME_ALLOWED_IN_WINDOW_MILLIS, TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS, - THERMAL_STATUS_LIMIT, ALLOW_IN_LOW_POWER_MODE); + THERMAL_STATUS_LIMIT, ALLOW_IN_LOW_POWER_MODE, + HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT); @Before public void setUp() { @@ -136,7 +140,7 @@ public class HighBrightnessModeControllerTest { initHandler(null); final HighBrightnessModeController hbmc = new HighBrightnessModeController( mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken, - mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, () -> {}, mContextSpy); + mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, null, () -> {}, mContextSpy); assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF); assertEquals(hbmc.getTransitionPoint(), HBM_TRANSITION_POINT_INVALID, 0.0f); } @@ -146,7 +150,7 @@ public class HighBrightnessModeControllerTest { initHandler(null); final HighBrightnessModeController hbmc = new HighBrightnessModeController( mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken, - mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, () -> {}, mContextSpy); + mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, null, () -> {}, mContextSpy); hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF); @@ -703,7 +707,7 @@ public class HighBrightnessModeControllerTest { initHandler(clock); return new HighBrightnessModeController(mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken, mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, - DEFAULT_HBM_DATA, () -> {}, mContextSpy); + DEFAULT_HBM_DATA, null, () -> {}, mContextSpy); } private void initHandler(OffsettableClock clock) { diff --git a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java index 7f7c716bc1f0..2f5993d1d989 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java @@ -61,7 +61,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.Map; +import java.util.List; @SmallTest @Presubmit @@ -136,9 +136,10 @@ public class ApexManagerTest { mApexManager.scanApexPackagesTraced(mPackageParser2, ParallelPackageParser.makeExecutorService()); - Map<String, String> services = mApexManager.getApexSystemServices(); + List<ApexSystemServiceInfo> services = mApexManager.getApexSystemServices(); assertThat(services).hasSize(1); - assertThat(services).containsKey("com.android.apex.test.ApexSystemService"); + assertThat(services.stream().map(ApexSystemServiceInfo::getName).findFirst().orElse(null)) + .matches("com.android.apex.test.ApexSystemService"); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java index 687779d686d1..fb3a6264169a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.window.BackNavigationInfo.typeToString; import static com.google.common.truth.Truth.assertThat; @@ -71,7 +72,8 @@ public class BackNavigationControllerTests extends WindowTestsBase { @Test public void backTypeCrossActivityWhenBackToPreviousActivity() { Task task = createTopTaskWithActivity(); - mAtm.setFocusedTask(task.mTaskId, createActivityRecord(task)); + mAtm.setFocusedTask(task.mTaskId, + createAppWindow(task, FIRST_APPLICATION_WINDOW, "window").mActivityRecord); BackNavigationInfo backNavigationInfo = mBackNavigationController.startBackNavigation(task, new StubTransaction()); assertThat(backNavigationInfo).isNotNull(); @@ -85,7 +87,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { @Test public void backNavInfoFullyPopulated() { Task task = createTopTaskWithActivity(); - createActivityRecord(task); + createAppWindow(task, FIRST_APPLICATION_WINDOW, "window"); // We need a mock screenshot so TaskSnapshotController taskSnapshotController = createMockTaskSnapshotController(); @@ -115,6 +117,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { private Task createTopTaskWithActivity() { Task task = createTask(mDefaultDisplay); ActivityRecord record = createActivityRecord(task); + createWindow(null, FIRST_APPLICATION_WINDOW, record, "window"); when(record.mSurfaceControl.isValid()).thenReturn(true); mAtm.setFocusedTask(task.mTaskId, record); return task; diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java index 65b5cf5f13c4..dcaa511bf7cc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java @@ -80,7 +80,6 @@ import android.app.IApplicationThread; import android.app.WindowConfiguration; import android.content.ComponentName; import android.content.pm.ActivityInfo; -import android.graphics.Rect; import android.os.Binder; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; @@ -231,34 +230,6 @@ public class RootTaskTests extends WindowTestsBase { } @Test - public void testTaskOutset() { - final Task task = createTask(mDisplayContent); - final int taskOutset = 10; - spyOn(task); - doReturn(taskOutset).when(task).getTaskOutset(); - doReturn(true).when(task).inMultiWindowMode(); - - // Mock the resolved override windowing mode to non-fullscreen - final WindowConfiguration windowConfiguration = - task.getResolvedOverrideConfiguration().windowConfiguration; - spyOn(windowConfiguration); - doReturn(WINDOWING_MODE_MULTI_WINDOW) - .when(windowConfiguration).getWindowingMode(); - - // Prevent adjust task dimensions - doNothing().when(task).adjustForMinimalTaskDimensions(any(), any(), any()); - - final Rect taskBounds = new Rect(200, 200, 800, 1000); - // Update surface position and size by the given bounds. - task.setBounds(taskBounds); - - assertEquals(taskBounds.width() + 2 * taskOutset, task.getLastSurfaceSize().x); - assertEquals(taskBounds.height() + 2 * taskOutset, task.getLastSurfaceSize().y); - assertEquals(taskBounds.left - taskOutset, task.getLastSurfacePosition().x); - assertEquals(taskBounds.top - taskOutset, task.getLastSurfacePosition().y); - } - - @Test public void testActivityAndTaskGetsProperType() { final Task task1 = new TaskBuilder(mSupervisor).build(); ActivityRecord activity1 = createNonAttachedActivityRecord(mDisplayContent); diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index e560f34cfcd8..d7cdb507362a 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -356,13 +356,17 @@ public class TelecomManager { "android.telecom.extra.INCOMING_CALL_EXTRAS"; /** - * Optional extra for {@link #ACTION_INCOMING_CALL} containing a boolean to indicate that the - * call has an externally generated ringer. Used by the HfpClientConnectionService when In Band - * Ringtone is enabled to prevent two ringers from being generated. + * Optional extra for {@link #addNewIncomingCall(PhoneAccountHandle, Bundle)} used to indicate + * that a call has an in-band ringtone associated with it. This is used when the device is + * acting as an HFP headset and the Bluetooth stack has received an in-band ringtone from the + * the HFP host which must be played instead of any local ringtone the device would otherwise + * have generated. + * * @hide */ - public static final String EXTRA_CALL_EXTERNAL_RINGER = - "android.telecom.extra.CALL_EXTERNAL_RINGER"; + @SystemApi + public static final String EXTRA_CALL_HAS_IN_BAND_RINGTONE = + "android.telecom.extra.CALL_HAS_IN_BAND_RINGTONE"; /** * Optional extra for {@link android.content.Intent#ACTION_CALL} and diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java index 5a12865fb2a0..e0145e661660 100644 --- a/telephony/java/android/telephony/SmsMessage.java +++ b/telephony/java/android/telephony/SmsMessage.java @@ -79,7 +79,7 @@ public class SmsMessage { public static final int ENCODING_8BIT = 2; public static final int ENCODING_16BIT = 3; /** - * @hide This value is not defined in global standard. Only in Korea, this is used. + * This value is not defined in global standard. Only in Korea, this is used. */ public static final int ENCODING_KSC5601 = 4; diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java index 1ff6ec1779cd..ec734716f6e4 100644 --- a/telephony/java/android/telephony/data/DataServiceCallback.java +++ b/telephony/java/android/telephony/data/DataServiceCallback.java @@ -261,9 +261,10 @@ public class DataServiceCallback { } /** - * Unthrottles the APN on the current transport. There is no matching "APN throttle" method. - * Instead, the APN is throttled when {@link IDataService#setupDataCall} fails within - * the time specified by {@link DataCallResponse#getRetryDurationMillis}. + * Unthrottles the APN on the current transport. + * The APN is throttled when {@link IDataService#setupDataCall} fails within + * the time specified by {@link DataCallResponse#getRetryDurationMillis} and will remain + * throttled until this method is called. * <p/> * see: {@link DataCallResponse#getRetryDurationMillis} * @@ -284,9 +285,9 @@ public class DataServiceCallback { /** * Unthrottles the DataProfile on the current transport. - * There is no matching "DataProfile throttle" method. - * Instead, the DataProfile is throttled when {@link IDataService#setupDataCall} fails within - * the time specified by {@link DataCallResponse#getRetryDurationMillis}. + * The DataProfile is throttled when {@link IDataService#setupDataCall} fails within + * the time specified by {@link DataCallResponse#getRetryDurationMillis} and will remain + * throttled until this method is called. * <p/> * see: {@link DataCallResponse#getRetryDurationMillis} * |