diff options
50 files changed, 1162 insertions, 161 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 9a417e9da3dd..6b5554bdd05a 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -18,9 +18,11 @@ java_defaults { // Add java_aconfig_libraries to here to add them to the core framework srcs: [ + ":android.app.usage.flags-aconfig-java{.generated_srcjars}", ":android.os.flags-aconfig-java{.generated_srcjars}", ":android.os.vibrator.flags-aconfig-java{.generated_srcjars}", ":android.security.flags-aconfig-java{.generated_srcjars}", + ":android.media.flags-aconfig-java{.generated_srcjars}", ":camera_platform_flags_core_java_lib{.generated_srcjars}", ":com.android.window.flags.window-aconfig-java{.generated_srcjars}", ":com.android.text.flags-aconfig-java{.generated_srcjars}", @@ -106,6 +108,19 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +// UsageStats +aconfig_declarations { + name: "android.app.usage.flags-aconfig", + package: "android.app.usage", + srcs: ["core/java/android/app/usage/*.aconfig"], +} + +java_aconfig_library { + name: "android.app.usage.flags-aconfig-java", + aconfig_declarations: "android.app.usage.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // OS aconfig_declarations { name: "android.os.flags-aconfig", @@ -157,3 +172,16 @@ java_aconfig_library { aconfig_declarations: "android.os.vibrator.flags-aconfig", defaults: ["framework-minus-apex-aconfig-java-defaults"], } + +// Media +aconfig_declarations { + name: "android.media.flags-aconfig", + package: "android.media", + srcs: ["media/java/android/media/*.aconfig"], +} + +java_aconfig_library { + name: "android.media.flags-aconfig-java", + aconfig_declarations: "android.media.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java index 17076bc4eea4..66c1efca0000 100644 --- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java +++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java @@ -419,6 +419,14 @@ public class PowerExemptionManager { */ public static final int REASON_SYSTEM_EXEMPT_APP_OP = 327; + /** + * Granted by {@link com.android.server.pm.PackageArchiverService} to the installer responsible + * for unarchiving an app. + * + * @hide + */ + public static final int REASON_PACKAGE_UNARCHIVE = 328; + /** @hide The app requests out-out. */ public static final int REASON_OPT_OUT_REQUESTED = 1000; @@ -502,6 +510,7 @@ public class PowerExemptionManager { REASON_ACTIVE_DEVICE_ADMIN, REASON_MEDIA_NOTIFICATION_TRANSFER, REASON_PACKAGE_INSTALLER, + REASON_PACKAGE_UNARCHIVE, }) @Retention(RetentionPolicy.SOURCE) public @interface ReasonCode {} diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 35a736dc076b..0c619815ac78 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3547,6 +3547,7 @@ package android.content { field public static final String ACTION_SHOW_SUSPENDED_APP_DETAILS = "android.intent.action.SHOW_SUSPENDED_APP_DETAILS"; field @Deprecated public static final String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED"; field public static final String ACTION_SPLIT_CONFIGURATION_CHANGED = "android.intent.action.SPLIT_CONFIGURATION_CHANGED"; + field public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE"; field public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP"; field public static final String ACTION_USER_ADDED = "android.intent.action.USER_ADDED"; field public static final String ACTION_USER_REMOVED = "android.intent.action.USER_REMOVED"; @@ -3796,6 +3797,9 @@ package android.content.pm { public class PackageArchiver { method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException; + method @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; + field public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS"; + field public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME"; } public class PackageInfo implements android.os.Parcelable { diff --git a/core/java/android/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig new file mode 100644 index 000000000000..afe87de1dbf5 --- /dev/null +++ b/core/java/android/app/usage/flags.aconfig @@ -0,0 +1,9 @@ +package: "android.app.usage" + +flag { + name: "user_interaction_type_api" + namespace: "power_optimization" + description: "Feature flag for user interaction event report/query API" + bug: "296061232" +} + diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 31f6418ac3d0..fe7d1e6fb232 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -5270,6 +5270,16 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_SHOW_FOREGROUND_SERVICE_MANAGER = "android.intent.action.SHOW_FOREGROUND_SERVICE_MANAGER"; + /** + * Broadcast Action: Sent to the responsible installer of an archived package when unarchival + * is requested. + * + * @see android.content.pm.PackageArchiver + * @hide + */ + @SystemApi + public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard intent categories (see addCategory()). diff --git a/core/java/android/content/pm/IPackageArchiverService.aidl b/core/java/android/content/pm/IPackageArchiverService.aidl index fc471c451370..dc6491d6d97e 100644 --- a/core/java/android/content/pm/IPackageArchiverService.aidl +++ b/core/java/android/content/pm/IPackageArchiverService.aidl @@ -23,4 +23,7 @@ interface IPackageArchiverService { @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES,android.Manifest.permission.REQUEST_DELETE_PACKAGES})") void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})") + void requestUnarchive(String packageName, String callerPackageName, in UserHandle userHandle); }
\ No newline at end of file diff --git a/core/java/android/content/pm/PackageArchiver.java b/core/java/android/content/pm/PackageArchiver.java index d739d5054800..b06523160b92 100644 --- a/core/java/android/content/pm/PackageArchiver.java +++ b/core/java/android/content/pm/PackageArchiver.java @@ -42,6 +42,26 @@ import android.os.RemoteException; @SystemApi public class PackageArchiver { + /** + * Extra field for the package name of a package that is requested to be unarchived. Sent as + * part of the {@link android.content.Intent#ACTION_UNARCHIVE_PACKAGE} intent. + * + * @hide + */ + @SystemApi + public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = + "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME"; + + /** + * If true, the requestor of the unarchival has specified that the app should be unarchived + * for {@link android.os.UserHandle#ALL}. + * + * @hide + */ + @SystemApi + public static final String EXTRA_UNARCHIVE_ALL_USERS = + "android.content.pm.extra.UNARCHIVE_ALL_USERS"; + private final Context mContext; private final IPackageArchiverService mService; @@ -58,7 +78,7 @@ public class PackageArchiver { * * @param statusReceiver Callback used to notify when the operation is completed. * @throws NameNotFoundException If {@code packageName} isn't found or not available to the - * caller. + * caller or isn't archived. * @hide */ @RequiresPermission(anyOf = { @@ -76,4 +96,34 @@ public class PackageArchiver { throw e.rethrowFromSystemServer(); } } + + /** + * Requests to unarchive a currently archived package. + * + * <p> Sends a request to unarchive an app to the responsible installer. The installer is + * determined by {@link InstallSourceInfo#getUpdateOwnerPackageName()}, or + * {@link InstallSourceInfo#getInstallingPackageName()} if the former value is null. + * + * <p> The installation will happen asynchronously and can be observed through + * {@link android.content.Intent#ACTION_PACKAGE_ADDED}. + * + * @throws NameNotFoundException If {@code packageName} isn't found or not visible to the + * caller or if the package has no installer on the device + * anymore to unarchive it. + * @hide + */ + @RequiresPermission(anyOf = { + Manifest.permission.INSTALL_PACKAGES, + Manifest.permission.REQUEST_INSTALL_PACKAGES}) + @SystemApi + public void requestUnarchive(@NonNull String packageName) + throws NameNotFoundException { + try { + mService.requestUnarchive(packageName, mContext.getPackageName(), mContext.getUser()); + } catch (ParcelableException e) { + e.maybeRethrow(NameNotFoundException.class); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java index c01664e55744..8be4c5858694 100644 --- a/core/java/android/inputmethodservice/NavigationBarController.java +++ b/core/java/android/inputmethodservice/NavigationBarController.java @@ -237,7 +237,7 @@ final class NavigationBarController { mNavigationBarFrame.setOnApplyWindowInsetsListener((view, insets) -> { if (mNavigationBarFrame != null) { boolean visible = insets.isVisible(captionBar()); - mNavigationBarFrame.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); + mNavigationBarFrame.setVisibility(visible ? View.VISIBLE : View.GONE); } return view.onApplyWindowInsets(insets); }); diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 5fe2aa1f5295..12527e91a87c 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -254,7 +254,6 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API, "true"); DEFAULT_FLAGS.put(SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION, "true"); DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS, "false"); - DEFAULT_FLAGS.put("settings_press_hold_nav_handle_to_search", "false"); // TODO: b/298454866 Replace with Trunk Stable Feature Flag DEFAULT_FLAGS.put(SETTINGS_REMOTEAUTH_ENROLLMENT_SETTINGS, "false"); } diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index 560e41b1aa33..b8d251fc5cc5 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -8,3 +8,10 @@ flag { description: "Whether the feature to sync different window-related config updates is enabled" bug: "260873529" } + +flag { + namespace: "windowing_sdk" + name: "activity_embedding_overlay_presentation_flag" + description: "Whether the overlay presentation feature is enabled" + bug: "243518738" +} diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 03480e42e5ac..302c7fad8173 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -5480,6 +5480,13 @@ of known compatibility issues. --> <string-array name="config_highRefreshRateBlacklist"></string-array> + <!-- The list of packages to automatically opt in to refresh rate suppressing by small area + detection. Format of this array should be packageName:threshold and threshold value should + be between 0 to 1--> + <string-array name="config_smallAreaDetectionAllowlist" translatable="false"> + <!-- Add packages:threshold here --> + </string-array> + <!-- The list of packages to force slowJpegMode for Apps using Camera API1 --> <string-array name="config_forceSlowJpegModeList" translatable="false"> <!-- Add packages here --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 7ea597420746..02209a7ff86b 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4288,6 +4288,8 @@ <java-symbol type="array" name="config_highRefreshRateBlacklist" /> <java-symbol type="array" name="config_forceSlowJpegModeList" /> + <java-symbol type="array" name="config_smallAreaDetectionAllowlist" /> + <java-symbol type="layout" name="chooser_dialog" /> <java-symbol type="layout" name="chooser_dialog_item" /> <java-symbol type="drawable" name="chooser_dialog_background" /> diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index a41fb64716e2..0778311e98bd 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -67,7 +67,6 @@ import android.view.View; import android.window.WindowContextInfo; import android.window.WindowTokenClientController; -import androidx.test.filters.FlakyTest; import androidx.test.filters.MediumTest; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.rule.ActivityTestRule; @@ -254,15 +253,9 @@ public class ActivityThreadTest { // Execute a local relaunch item with current scaled config (e.g. simulate recreate), // the config should not be scaled again. - final Configuration currentConfig = activity.getResources().getConfiguration(); - final ClientTransaction localTransaction = - newTransaction(activityThread, activity.getActivityToken()); - localTransaction.addCallback(ActivityRelaunchItem.obtain( - null /* pendingResults */, null /* pendingIntents */, 0 /* configChanges */, - new MergedConfiguration(currentConfig, currentConfig), - true /* preserveWindow */)); InstrumentationRegistry.getInstrumentation().runOnMainSync( - () -> activityThread.executeTransaction(localTransaction)); + () -> activityThread.executeTransaction( + newRelaunchResumeTransaction(activity))); assertScreenScale(scale, activity, originalActivityConfig, originalActivityMetrics); } finally { @@ -630,7 +623,6 @@ public class ActivityThreadTest { }); } - @FlakyTest(bugId = 298331121) @Test public void testHandleConfigurationChanged_DoesntOverrideActivityConfig() { final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index 9aac694e41bf..04795768aefc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -103,7 +103,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements default void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {} /** Whether this task listener supports compat UI. */ default boolean supportCompatUI() { - // All TaskListeners should support compat UI except PIP. + // All TaskListeners should support compat UI except PIP and StageCoordinator. return true; } /** Attaches a child window surface to the task surface. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java index cbff4640239e..77aefc8f7e4a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java @@ -208,7 +208,7 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract } private boolean getHasUserAspectRatioSettingsButton(@NonNull TaskInfo taskInfo) { - return taskInfo.topActivityEligibleForUserAspectRatioButton + return taskInfo.topActivityEligibleForUserAspectRatioButton && (taskInfo.topActivityBoundsLetterboxed || taskInfo.isUserFullscreenOverrideEnabled); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 842b1bf9e8af..94fa485efd5c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -228,6 +228,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private final Toast mSplitUnsupportedToast; private SplitRequest mSplitRequest; + /** + * Since StageCoordinator only coordinates MainStage and SideStage, it shouldn't support + * CompatUI layouts. CompatUI is handled separately by MainStage and SideStage. + */ + @Override + public boolean supportCompatUI() { + return false; + } + class SplitRequest { @SplitPosition int mActivatePosition; diff --git a/media/java/android/media/flags.aconfig b/media/java/android/media/flags.aconfig new file mode 100644 index 000000000000..8567a3b166ab --- /dev/null +++ b/media/java/android/media/flags.aconfig @@ -0,0 +1,8 @@ +package: "android.media" + +flag { + name: "haptics_customization_enabled" + namespace: "media" + description: "Enables the haptics customization feature" + bug: "241918098" +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt index 90a723f707da..b77368a429ae 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt @@ -253,6 +253,7 @@ private fun SingleRowTopAppBar( hideTitleSemantics = false, navigationIcon = navigationIcon, actions = actionsRow, + titleScaleDisabled = false, ) } } @@ -426,6 +427,7 @@ private fun TwoRowsTopAppBar( * accessibility services at the same time, when animating between collapsed / expanded states. * @param navigationIcon a navigation icon [Composable] * @param actions actions [Composable] + * @param titleScaleDisabled whether the title font scaling is disabled. Default is disabled. */ @Composable private fun TopAppBarLayout( @@ -443,6 +445,7 @@ private fun TopAppBarLayout( hideTitleSemantics: Boolean, navigationIcon: @Composable () -> Unit, actions: @Composable () -> Unit, + titleScaleDisabled: Boolean = true, ) { Layout( { @@ -466,9 +469,12 @@ private fun TopAppBarLayout( ProvideTextStyle(value = titleTextStyle) { CompositionLocalProvider( LocalContentColor provides titleContentColor, - // Disable the title font scaling by only passing the density but not the - // font scale. - LocalDensity provides Density(density = LocalDensity.current.density), + LocalDensity provides with(LocalDensity.current) { + Density( + density = density, + fontScale = if (titleScaleDisabled) 1f else fontScale, + ) + }, content = title ) } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt index d437e3579354..696e8776ccd7 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt @@ -18,6 +18,7 @@ package com.android.settingslib.spa.widget.scaffold import androidx.activity.compose.BackHandler import androidx.appcompat.R +import androidx.compose.foundation.focusable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.RowScope @@ -96,7 +97,8 @@ fun SearchScaffold( Modifier .padding(paddingValues.horizontalValues()) .padding(top = paddingValues.calculateTopPadding()) - .fillMaxSize(), + .focusable() + .fillMaxSize() ) { content( paddingValues.calculateBottomPadding(), diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt index f68078a8a340..82b032450938 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt @@ -29,6 +29,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.util.Compile import com.android.systemui.util.traceSection import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher @@ -239,7 +240,7 @@ constructor( private companion object { const val TAG = "DisplayRepository" - val DEBUG = Log.isLoggable(TAG, Log.DEBUG) + val DEBUG = Log.isLoggable(TAG, Log.DEBUG) || Compile.IS_DEBUG } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index d8b31a229261..d8bc5ff75b61 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -636,7 +636,13 @@ object Flags { val CLIPBOARD_SHARED_TRANSITIONS = unreleasedFlag("clipboard_shared_transitions", teamfood = true) + /** + * Whether the scene container (Flexiglass) is enabled. Note that [SCENE_CONTAINER] should be + * checked and toggled together with [SCENE_CONTAINER_ENABLED] so that ProGuard can remove + * unused code from our APK at compile time. + */ // TODO(b/283300105): Tracking Bug + @JvmField val SCENE_CONTAINER_ENABLED = false @JvmField val SCENE_CONTAINER = unreleasedFlag("scene_container") // 1900 @@ -764,11 +770,11 @@ object Flags { /** Enable the Compose implementation of the PeopleSpaceActivity. */ @JvmField - val COMPOSE_PEOPLE_SPACE = unreleasedFlag("compose_people_space") + val COMPOSE_PEOPLE_SPACE = releasedFlag("compose_people_space") /** Enable the Compose implementation of the Quick Settings footer actions. */ @JvmField - val COMPOSE_QS_FOOTER_ACTIONS = unreleasedFlag("compose_qs_footer_actions") + val COMPOSE_QS_FOOTER_ACTIONS = releasedFlag("compose_qs_footer_actions") /** Enable the share wifi button in Quick Settings internet dialog. */ @JvmField diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 8db7abf7347f..c1999043743c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -163,6 +163,7 @@ import com.android.systemui.plugins.FalsingManager.FalsingTapListener; import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; +import com.android.systemui.shade.data.repository.ShadeRepository; import com.android.systemui.shade.transition.ShadeTransitionController; import com.android.systemui.shared.system.InteractionJankMonitorWrapper; import com.android.systemui.shared.system.QuickStepContract; @@ -350,6 +351,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private final Interpolator mBounceInterpolator; private final NotificationShadeWindowController mNotificationShadeWindowController; private final ShadeExpansionStateManager mShadeExpansionStateManager; + private final ShadeRepository mShadeRepository; private final FalsingTapListener mFalsingTapListener = this::falsingAdditionalTapRequired; private final AccessibilityDelegate mAccessibilityDelegate = new ShadeAccessibilityDelegate(); private final NotificationGutsManager mGutsManager; @@ -710,7 +712,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump VibratorHelper vibratorHelper, LatencyTracker latencyTracker, PowerManager powerManager, - AccessibilityManager accessibilityManager, @DisplayId int displayId, + AccessibilityManager accessibilityManager, + @DisplayId int displayId, KeyguardUpdateMonitor keyguardUpdateMonitor, MetricsLogger metricsLogger, ShadeLogger shadeLogger, @@ -746,6 +749,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump ScreenOffAnimationController screenOffAnimationController, LockscreenGestureLogger lockscreenGestureLogger, ShadeExpansionStateManager shadeExpansionStateManager, + ShadeRepository shadeRepository, Optional<SysUIUnfoldComponent> unfoldComponent, SysUiState sysUiState, Provider<KeyguardBottomAreaViewController> keyguardBottomAreaViewControllerProvider, @@ -788,6 +792,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mLockscreenGestureLogger = lockscreenGestureLogger; mShadeExpansionStateManager = shadeExpansionStateManager; + mShadeRepository = shadeRepository; mShadeLog = shadeLogger; mGutsManager = gutsManager; mDreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel; @@ -3952,6 +3957,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } mExpandedFraction = Math.min(1f, maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight); + mShadeRepository.setLegacyShadeExpansion(mExpandedFraction); mQsController.setShadeExpansion(mExpandedHeight, mExpandedFraction); mExpansionDragDownAmountPx = h; mAmbientState.setExpansionFraction(mExpandedFraction); diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt index 6585fcb1ae53..ed719a651978 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt @@ -77,7 +77,9 @@ abstract class ShadeViewProviderModule { layoutInsetController: NotificationInsetsController, ): WindowRootView { return if ( - featureFlags.isEnabled(Flags.SCENE_CONTAINER) && ComposeFacade.isComposeAvailable() + Flags.SCENE_CONTAINER_ENABLED && + featureFlags.isEnabled(Flags.SCENE_CONTAINER) && + ComposeFacade.isComposeAvailable() ) { val sceneWindowRootView = layoutInflater.inflate(R.layout.scene_window_root, null) as SceneWindowRootView diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt index 5a8be1e328f6..509921fd38e1 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt @@ -34,21 +34,48 @@ interface ShadeRepository { /** ShadeModel information regarding shade expansion events */ val shadeModel: Flow<ShadeModel> - /** Amount qs has expanded. Quick Settings can be expanded without the full shade expansion. */ + /** + * Amount qs has expanded, [0-1]. 0 means fully collapsed, 1 means fully expanded. Quick + * Settings can be expanded without the full shade expansion. + */ val qsExpansion: StateFlow<Float> - /** The amount the shade has expanded */ - val shadeExpansion: StateFlow<Float> + /** + * The amount the lockscreen shade has dragged down by the user, [0-1]. 0 means fully collapsed, + * 1 means fully expanded. + */ + val lockscreenShadeExpansion: StateFlow<Float> + + /** + * NotificationPanelViewController.mExpandedFraction as a StateFlow. This nominally represents + * the amount the shade has expanded 0-1 like many other flows in this repo, but there are cases + * where its value will be 1 and no shade will be rendered, e.g. whenever the keyguard is + * visible and when quick settings is expanded. The confusing nature and impending deletion of + * this makes it unsuitable for future development, so usage is discouraged. + */ + @Deprecated("Use ShadeInteractor.shadeExpansion instead") + val legacyShadeExpansion: StateFlow<Float> /** Amount shade has expanded with regard to the UDFPS location */ val udfpsTransitionToFullShadeProgress: StateFlow<Float> /** The amount QS has expanded without notifications */ fun setQsExpansion(qsExpansion: Float) + fun setUdfpsTransitionToFullShadeProgress(progress: Float) - /** The amount the shade has expanded, [0-1]. 0 means fully collapsed, 1 means fully expanded */ - fun setShadeExpansion(expansion: Float) + /** + * Set the amount the shade has dragged down by the user, [0-1]. 0 means fully collapsed, 1 + * means fully expanded. + */ + fun setLockscreenShadeExpansion(lockscreenShadeExpansion: Float) + + /** + * Set the legacy expansion value. This should only be called whenever the value of + * NotificationPanelViewController.mExpandedFraction changes or in tests. + */ + @Deprecated("Should only be called by NPVC and tests") + fun setLegacyShadeExpansion(expandedFraction: Float) } /** Business logic for shade interactions */ @@ -84,18 +111,29 @@ constructor(shadeExpansionStateManager: ShadeExpansionStateManager) : ShadeRepos private val _qsExpansion = MutableStateFlow(0f) override val qsExpansion: StateFlow<Float> = _qsExpansion.asStateFlow() - private val _shadeExpansion = MutableStateFlow(0f) - override val shadeExpansion: StateFlow<Float> = _shadeExpansion.asStateFlow() + private val _lockscreenShadeExpansion = MutableStateFlow(0f) + override val lockscreenShadeExpansion: StateFlow<Float> = + _lockscreenShadeExpansion.asStateFlow() private var _udfpsTransitionToFullShadeProgress = MutableStateFlow(0f) override val udfpsTransitionToFullShadeProgress: StateFlow<Float> = _udfpsTransitionToFullShadeProgress.asStateFlow() + + private val _legacyShadeExpansion = MutableStateFlow(0f) + @Deprecated("Use ShadeInteractor.shadeExpansion instead") + override val legacyShadeExpansion: StateFlow<Float> = _legacyShadeExpansion.asStateFlow() + override fun setQsExpansion(qsExpansion: Float) { _qsExpansion.value = qsExpansion } - override fun setShadeExpansion(expansion: Float) { - _shadeExpansion.value = expansion + @Deprecated("Should only be called by NPVC and tests") + override fun setLegacyShadeExpansion(expandedFraction: Float) { + _legacyShadeExpansion.value = expandedFraction + } + + override fun setLockscreenShadeExpansion(lockscreenShadeExpansion: Float) { + _lockscreenShadeExpansion.value = lockscreenShadeExpansion } override fun setUdfpsTransitionToFullShadeProgress(progress: Float) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt index 288d32e58c7c..fd63b89d1199 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt @@ -22,6 +22,7 @@ import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository +import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.user.domain.interactor.UserInteractor @@ -31,6 +32,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @@ -45,6 +47,7 @@ constructor( userSetupRepository: UserSetupRepository, deviceProvisionedController: DeviceProvisionedController, userInteractor: UserInteractor, + sharedNotificationContainerInteractor: SharedNotificationContainerInteractor, repository: ShadeRepository, ) { /** Emits true if the shade is currently allowed and false otherwise. */ @@ -53,16 +56,31 @@ constructor( .map { it.isShadeEnabled() } .stateIn(scope, SharingStarted.Eagerly, initialValue = false) + /** + * Whether split shade, the combined notifications and quick settings shade used for large + * screens, is enabled. + */ + val splitShadeEnabled: Flow<Boolean> = + sharedNotificationContainerInteractor.configurationBasedDimensions + .map { dimens -> dimens.useSplitShade } + .distinctUntilChanged() + /** The amount [0-1] that the shade has been opened */ val shadeExpansion: Flow<Float> = - combine(repository.shadeExpansion, keyguardRepository.statusBarState) { - shadeExpansion, - statusBarState -> - // This is required, as shadeExpansion gets reset to 0f even with the shade open - if (statusBarState == StatusBarState.SHADE_LOCKED) { - 1f - } else { - shadeExpansion + combine( + repository.lockscreenShadeExpansion, + keyguardRepository.statusBarState, + repository.legacyShadeExpansion, + repository.qsExpansion, + splitShadeEnabled + ) { dragDownAmount, statusBarState, legacyShadeExpansion, qsExpansion, splitShadeEnabled -> + when (statusBarState) { + // legacyShadeExpansion is 1 instead of 0 when QS is expanded + StatusBarState.SHADE -> + if (!splitShadeEnabled && qsExpansion > 0f) 0f else legacyShadeExpansion + StatusBarState.KEYGUARD -> dragDownAmount + // This is required, as shadeExpansion gets reset to 0f even with the shade open + StatusBarState.SHADE_LOCKED -> 1f } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index f004982413e8..73bbbca09fe8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -447,7 +447,7 @@ class LockscreenShadeTransitionController @Inject constructor( if (!nsslController.isInLockedDownShade() || field == 0f || forceApplyAmount) { fractionToShade = MathUtils.saturate(dragDownAmount / notificationShelfTransitionDistance) - shadeRepository.setShadeExpansion(fractionToShade) + shadeRepository.setLockscreenShadeExpansion(fractionToShade) nsslController.setTransitionToFullShadeAmount(fractionToShade) qsTransitionController.dragDownAmount = value @@ -857,12 +857,12 @@ class DragDownHelper( MotionEvent.ACTION_MOVE -> { val h = y - initialTouchY // Adjust the touch slop if another gesture may be being performed. - val touchSlop = if (event.classification - == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE) { - touchSlop * slopMultiplier - } else { - touchSlop - } + val touchSlop = + if (event.classification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE) { + touchSlop * slopMultiplier + } else { + touchSlop + } if (h > touchSlop && h > Math.abs(x - initialTouchX)) { isDraggingDown = true captureStartingChild(initialTouchX, initialTouchY) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index c573ac638032..1c9ec27c6a1b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -626,6 +626,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mScreenOffAnimationController, mLockscreenGestureLogger, mShadeExpansionStateManager, + mShadeRepository, mSysUIUnfoldComponent, mSysUiState, () -> mKeyguardBottomAreaViewController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java index ab0ae05b0e83..e42a7a6f50d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java @@ -37,6 +37,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.TestScopeProvider; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.fragments.FragmentHostManager; @@ -61,6 +62,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; +import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor; import com.android.systemui.statusbar.phone.KeyguardBottomAreaView; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.KeyguardStatusBarView; @@ -174,6 +176,9 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { new FakeUserSetupRepository(), mDeviceProvisionedController, mUserInteractor, + new SharedNotificationContainerInteractor( + new FakeConfigurationRepository(), + mContext), mShadeRepository ); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt index e6e7482bdaf9..41ea5b747e06 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt @@ -25,7 +25,9 @@ import android.os.UserManager import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags @@ -36,6 +38,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository +import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.telephony.data.repository.FakeTelephonyRepository @@ -52,6 +55,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -71,6 +75,12 @@ class ShadeInteractorTest : SysuiTestCase() { private val disableFlagsRepository = FakeDisableFlagsRepository() private val keyguardRepository = FakeKeyguardRepository() private val shadeRepository = FakeShadeRepository() + private val configurationRepository = FakeConfigurationRepository() + private val sharedNotificationContainerInteractor = + SharedNotificationContainerInteractor( + configurationRepository, + mContext, + ) @Mock private lateinit var manager: UserManager @Mock private lateinit var headlessSystemUserMode: HeadlessSystemUserMode @@ -145,6 +155,7 @@ class ShadeInteractorTest : SysuiTestCase() { userSetupRepository, deviceProvisionedController, userInteractor, + sharedNotificationContainerInteractor, shadeRepository, ) } @@ -363,7 +374,7 @@ class ShadeInteractorTest : SysuiTestCase() { val actual by collectLastValue(underTest.shadeExpansion) keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED) - shadeRepository.setShadeExpansion(0.5f) + shadeRepository.setLockscreenShadeExpansion(0.5f) assertThat(actual).isEqualTo(1f) } @@ -375,10 +386,52 @@ class ShadeInteractorTest : SysuiTestCase() { keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) - shadeRepository.setShadeExpansion(0.5f) + shadeRepository.setLockscreenShadeExpansion(0.5f) assertThat(actual).isEqualTo(0.5f) - shadeRepository.setShadeExpansion(0.8f) + shadeRepository.setLockscreenShadeExpansion(0.8f) assertThat(actual).isEqualTo(0.8f) } + + fun shadeExpansionWhenInSplitShadeAndQsExpanded() = + testScope.runTest { + val actual by collectLastValue(underTest.shadeExpansion) + + // WHEN split shade is enabled and QS is expanded + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + overrideResource(R.bool.config_use_split_notification_shade, true) + configurationRepository.onAnyConfigurationChange() + runCurrent() + shadeRepository.setQsExpansion(.5f) + shadeRepository.setLegacyShadeExpansion(.7f) + + // THEN legacy shade expansion is passed through + assertThat(actual).isEqualTo(.7f) + } + + fun shadeExpansionWhenNotInSplitShadeAndQsExpanded() = + testScope.runTest { + val actual by collectLastValue(underTest.shadeExpansion) + + // WHEN split shade is not enabled and QS is expanded + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + shadeRepository.setQsExpansion(.5f) + shadeRepository.setLegacyShadeExpansion(1f) + + // THEN shade expansion is zero + assertThat(actual).isEqualTo(0f) + } + + fun shadeExpansionWhenNotInSplitShadeAndQsCollapsed() = + testScope.runTest { + val actual by collectLastValue(underTest.shadeExpansion) + + // WHEN split shade is not enabled and QS is expanded + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + shadeRepository.setQsExpansion(0f) + shadeRepository.setLegacyShadeExpansion(.6f) + + // THEN shade expansion is zero + assertThat(actual).isEqualTo(.6f) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt index fdaea2220de9..e086712308b6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt @@ -121,18 +121,33 @@ class ShadeRepositoryImplTest : SysuiTestCase() { } @Test - fun updateShadeExpansion() = + fun updateDragDownAmount() = testScope.runTest { - assertThat(underTest.shadeExpansion.value).isEqualTo(0f) + assertThat(underTest.lockscreenShadeExpansion.value).isEqualTo(0f) - underTest.setShadeExpansion(.5f) - assertThat(underTest.shadeExpansion.value).isEqualTo(.5f) + underTest.setLockscreenShadeExpansion(.5f) + assertThat(underTest.lockscreenShadeExpansion.value).isEqualTo(.5f) - underTest.setShadeExpansion(.82f) - assertThat(underTest.shadeExpansion.value).isEqualTo(.82f) + underTest.setLockscreenShadeExpansion(.82f) + assertThat(underTest.lockscreenShadeExpansion.value).isEqualTo(.82f) - underTest.setShadeExpansion(1f) - assertThat(underTest.shadeExpansion.value).isEqualTo(1f) + underTest.setLockscreenShadeExpansion(1f) + assertThat(underTest.lockscreenShadeExpansion.value).isEqualTo(1f) + } + + @Test + fun updateLegacyShadeExpansion() = + testScope.runTest { + assertThat(underTest.legacyShadeExpansion.value).isEqualTo(0f) + + underTest.setLegacyShadeExpansion(.5f) + assertThat(underTest.legacyShadeExpansion.value).isEqualTo(.5f) + + underTest.setLegacyShadeExpansion(.82f) + assertThat(underTest.legacyShadeExpansion.value).isEqualTo(.82f) + + underTest.setLegacyShadeExpansion(1f) + assertThat(underTest.legacyShadeExpansion.value).isEqualTo(1f) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt index 51e72c615372..6f7588097c70 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt @@ -10,6 +10,7 @@ import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.classifier.FalsingCollectorFake +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository @@ -29,6 +30,7 @@ import com.android.systemui.statusbar.notification.row.NotificationTestHelper import com.android.systemui.statusbar.notification.stack.AmbientState import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.LSShadeTransitionLogger @@ -102,6 +104,11 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { @Mock lateinit var transitionControllerCallback: LockscreenShadeTransitionController.Callback private val disableFlagsRepository = FakeDisableFlagsRepository() private val keyguardRepository = FakeKeyguardRepository() + private val configurationRepository = FakeConfigurationRepository() + private val sharedNotificationContainerInteractor = SharedNotificationContainerInteractor( + configurationRepository, + mContext, + ) private val shadeInteractor = ShadeInteractor( testScope.backgroundScope, @@ -110,6 +117,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { userSetupRepository = FakeUserSetupRepository(), deviceProvisionedController = mock(), userInteractor = mock(), + sharedNotificationContainerInteractor, repository = FakeShadeRepository(), ) private val powerInteractor = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index 55b52dc21434..75fb22dfddd0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -30,6 +30,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.shade.data.repository.FakeShadeRepository @@ -97,7 +98,11 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { keyguardTransitionInteractor = it.keyguardTransitionInteractor keyguardTransitionRepository = it.repository } - + sharedNotificationContainerInteractor = + SharedNotificationContainerInteractor( + configurationRepository, + mContext, + ) shadeInteractor = ShadeInteractor( testScope.backgroundScope, @@ -106,14 +111,9 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { userSetupRepository, deviceProvisionedController, userInteractor, + sharedNotificationContainerInteractor, shadeRepository, ) - - sharedNotificationContainerInteractor = - SharedNotificationContainerInteractor( - configurationRepository, - mContext, - ) underTest = SharedNotificationContainerViewModel( sharedNotificationContainerInteractor, @@ -228,7 +228,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { val isOnLockscreenWithoutShade by collectLastValue(underTest.isOnLockscreenWithoutShade) // First on AOD - shadeRepository.setShadeExpansion(0f) + shadeRepository.setLockscreenShadeExpansion(0f) shadeRepository.setQsExpansion(0f) keyguardTransitionRepository.sendTransitionStep( TransitionStep( @@ -242,19 +242,19 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { showLockscreen() // While state is LOCKSCREEN, validate variations of both shade and qs expansion - shadeRepository.setShadeExpansion(0.1f) + shadeRepository.setLockscreenShadeExpansion(0.1f) shadeRepository.setQsExpansion(0f) assertThat(isOnLockscreenWithoutShade).isFalse() - shadeRepository.setShadeExpansion(0.1f) + shadeRepository.setLockscreenShadeExpansion(0.1f) shadeRepository.setQsExpansion(0.1f) assertThat(isOnLockscreenWithoutShade).isFalse() - shadeRepository.setShadeExpansion(0f) + shadeRepository.setLockscreenShadeExpansion(0f) shadeRepository.setQsExpansion(0.1f) assertThat(isOnLockscreenWithoutShade).isFalse() - shadeRepository.setShadeExpansion(0f) + shadeRepository.setLockscreenShadeExpansion(0f) shadeRepository.setQsExpansion(0f) assertThat(isOnLockscreenWithoutShade).isTrue() } @@ -366,8 +366,9 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { } private suspend fun showLockscreen() { - shadeRepository.setShadeExpansion(0f) + shadeRepository.setLockscreenShadeExpansion(0f) shadeRepository.setQsExpansion(0f) + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) keyguardTransitionRepository.sendTransitionStep( TransitionStep( to = KeyguardState.LOCKSCREEN, @@ -377,8 +378,9 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { } private suspend fun showLockscreenWithShadeExpanded() { - shadeRepository.setShadeExpansion(1f) + shadeRepository.setLockscreenShadeExpansion(1f) shadeRepository.setQsExpansion(0f) + keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED) keyguardTransitionRepository.sendTransitionStep( TransitionStep( to = KeyguardState.LOCKSCREEN, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt index ccddca2d6611..08152a3c49cd 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt @@ -33,8 +33,12 @@ class FakeShadeRepository : ShadeRepository { private val _udfpsTransitionToFullShadeProgress = MutableStateFlow(0f) override val udfpsTransitionToFullShadeProgress = _udfpsTransitionToFullShadeProgress - private val _shadeExpansion = MutableStateFlow(0f) - override val shadeExpansion = _shadeExpansion + private val _lockscreenShadeExpansion = MutableStateFlow(0f) + override val lockscreenShadeExpansion = _lockscreenShadeExpansion + + private val _legacyShadeExpansion = MutableStateFlow(0f) + @Deprecated("Use ShadeInteractor instead") + override val legacyShadeExpansion = _legacyShadeExpansion fun setShadeModel(model: ShadeModel) { _shadeModel.value = model @@ -48,7 +52,12 @@ class FakeShadeRepository : ShadeRepository { _udfpsTransitionToFullShadeProgress.value = progress } - override fun setShadeExpansion(expansion: Float) { - _shadeExpansion.value = expansion + override fun setLockscreenShadeExpansion(lockscreenShadeExpansion: Float) { + _lockscreenShadeExpansion.value = lockscreenShadeExpansion + } + + @Deprecated("Should only be called by NPVC and tests") + override fun setLegacyShadeExpansion(expandedFraction: Float) { + _legacyShadeExpansion.value = expandedFraction } } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index b994105de406..f3ad4b443ba3 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -472,6 +472,8 @@ public final class DisplayManagerService extends SystemService { private SensorManager mSensorManager; private BrightnessTracker mBrightnessTracker; + private SmallAreaDetectionController mSmallAreaDetectionController; + // Whether minimal post processing is allowed by the user. @GuardedBy("mSyncRoot") @@ -738,6 +740,8 @@ public final class DisplayManagerService extends SystemService { filter.addAction(Intent.ACTION_DOCK_EVENT); mContext.registerReceiver(mIdleModeReceiver, filter); + + mSmallAreaDetectionController = SmallAreaDetectionController.create(mContext); } @VisibleForTesting @@ -3128,6 +3132,9 @@ public final class DisplayManagerService extends SystemService { pw.println(); mDisplayModeDirector.dump(pw); mBrightnessSynchronizer.dump(pw); + if (mSmallAreaDetectionController != null) { + mSmallAreaDetectionController.dump(pw); + } } private static float[] getFloatArray(TypedArray array) { diff --git a/services/core/java/com/android/server/display/SmallAreaDetectionController.java b/services/core/java/com/android/server/display/SmallAreaDetectionController.java new file mode 100644 index 000000000000..adaa5390cb9b --- /dev/null +++ b/services/core/java/com/android/server/display/SmallAreaDetectionController.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.pm.PackageManagerInternal; +import android.provider.DeviceConfig; +import android.provider.DeviceConfigInterface; +import android.util.ArrayMap; +import android.util.SparseArray; + +import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.BackgroundThread; +import com.android.server.LocalServices; +import com.android.server.pm.UserManagerInternal; + +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.Map; + +final class SmallAreaDetectionController { + private static native void nativeUpdateSmallAreaDetection(int[] uids, float[] thresholds); + private static native void nativeSetSmallAreaDetectionThreshold(int uid, float threshold); + + // TODO(b/281720315): Move this to DeviceConfig once server side ready. + private static final String KEY_SMALL_AREA_DETECTION_ALLOWLIST = + "small_area_detection_allowlist"; + + private final Object mLock = new Object(); + private final Context mContext; + private final PackageManagerInternal mPackageManager; + private final UserManagerInternal mUserManager; + @GuardedBy("mLock") + private final Map<String, Float> mAllowPkgMap = new ArrayMap<>(); + // TODO(b/298722189): Update allowlist when user changes + @GuardedBy("mLock") + private int[] mUserIds; + + static SmallAreaDetectionController create(@NonNull Context context) { + final SmallAreaDetectionController controller = + new SmallAreaDetectionController(context, DeviceConfigInterface.REAL); + final String property = DeviceConfigInterface.REAL.getProperty( + DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_SMALL_AREA_DETECTION_ALLOWLIST); + controller.updateAllowlist(property); + return controller; + } + + @VisibleForTesting + SmallAreaDetectionController(Context context, DeviceConfigInterface deviceConfig) { + mContext = context; + mPackageManager = LocalServices.getService(PackageManagerInternal.class); + mUserManager = LocalServices.getService(UserManagerInternal.class); + deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + BackgroundThread.getExecutor(), + new SmallAreaDetectionController.OnPropertiesChangedListener()); + mPackageManager.getPackageList(new PackageReceiver()); + } + + @VisibleForTesting + void updateAllowlist(@Nullable String property) { + synchronized (mLock) { + mAllowPkgMap.clear(); + if (property != null) { + final String[] mapStrings = property.split(","); + for (String mapString : mapStrings) putToAllowlist(mapString); + } else { + final String[] defaultMapStrings = mContext.getResources() + .getStringArray(R.array.config_smallAreaDetectionAllowlist); + for (String defaultMapString : defaultMapStrings) putToAllowlist(defaultMapString); + } + updateSmallAreaDetection(); + } + } + + @GuardedBy("mLock") + private void putToAllowlist(String rowData) { + // Data format: package:threshold - e.g. "com.abc.music:0.05" + final String[] items = rowData.split(":"); + if (items.length == 2) { + try { + final String pkg = items[0]; + final float threshold = Float.valueOf(items[1]); + mAllowPkgMap.put(pkg, threshold); + } catch (Exception e) { + // Just skip if items[1] - the threshold is not parsable number + } + } + } + + @GuardedBy("mLock") + private void updateUidListForAllUsers(SparseArray<Float> list, String pkg, float threshold) { + for (int i = 0; i < mUserIds.length; i++) { + final int userId = mUserIds[i]; + final int uid = mPackageManager.getPackageUid(pkg, 0, userId); + if (uid > 0) list.put(uid, threshold); + } + } + + @GuardedBy("mLock") + private void updateSmallAreaDetection() { + if (mAllowPkgMap.isEmpty()) return; + + mUserIds = mUserManager.getUserIds(); + + final SparseArray<Float> uidThresholdList = new SparseArray<>(); + for (String pkg : mAllowPkgMap.keySet()) { + final float threshold = mAllowPkgMap.get(pkg); + updateUidListForAllUsers(uidThresholdList, pkg, threshold); + } + + final int[] uids = new int[uidThresholdList.size()]; + final float[] thresholds = new float[uidThresholdList.size()]; + for (int i = 0; i < uidThresholdList.size(); i++) { + uids[i] = uidThresholdList.keyAt(i); + thresholds[i] = uidThresholdList.valueAt(i); + } + updateSmallAreaDetection(uids, thresholds); + } + + @VisibleForTesting + void updateSmallAreaDetection(int[] uids, float[] thresholds) { + nativeUpdateSmallAreaDetection(uids, thresholds); + } + + void setSmallAreaDetectionThreshold(int uid, float threshold) { + nativeSetSmallAreaDetectionThreshold(uid, threshold); + } + + void dump(PrintWriter pw) { + pw.println("Small area detection allowlist"); + pw.println(" Packages:"); + synchronized (mLock) { + for (String pkg : mAllowPkgMap.keySet()) { + pw.println(" " + pkg + " threshold = " + mAllowPkgMap.get(pkg)); + } + pw.println(" mUserIds=" + Arrays.toString(mUserIds)); + } + } + + private class OnPropertiesChangedListener implements DeviceConfig.OnPropertiesChangedListener { + public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) { + if (properties.getKeyset().contains(KEY_SMALL_AREA_DETECTION_ALLOWLIST)) { + updateAllowlist( + properties.getString(KEY_SMALL_AREA_DETECTION_ALLOWLIST, null /*default*/)); + } + } + } + + private final class PackageReceiver implements PackageManagerInternal.PackageListObserver { + @Override + public void onPackageAdded(@NonNull String packageName, int uid) { + synchronized (mLock) { + if (mAllowPkgMap.containsKey(packageName)) { + setSmallAreaDetectionThreshold(uid, mAllowPkgMap.get(packageName)); + } + } + } + } +} diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig index 1908e4dff234..dcac8c98d19f 100644 --- a/services/core/java/com/android/server/notification/flags.aconfig +++ b/services/core/java/com/android/server/notification/flags.aconfig @@ -13,3 +13,10 @@ flag { description: "This flag controls the polite notification feature" bug: "270456865" } + +flag { + name: "refactor_attention_helper" + namespace: "systemui" + description: "This flag controls the refactoring of NMS to NotificationAttentionHelper" + bug: "291907312" +} diff --git a/services/core/java/com/android/server/pm/PackageArchiverService.java b/services/core/java/com/android/server/pm/PackageArchiverService.java index 9c31dc9a1215..29f49c874640 100644 --- a/services/core/java/com/android/server/pm/PackageArchiverService.java +++ b/services/core/java/com/android/server/pm/PackageArchiverService.java @@ -17,17 +17,26 @@ package com.android.server.pm; import static android.content.pm.PackageManager.DELETE_KEEP_DATA; +import static android.os.PowerExemptionManager.REASON_PACKAGE_UNARCHIVE; +import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED; +import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.app.AppOpsManager; +import android.app.BroadcastOptions; import android.content.Context; +import android.content.Intent; import android.content.IntentSender; import android.content.pm.IPackageArchiverService; import android.content.pm.LauncherActivityInfo; import android.content.pm.LauncherApps; +import android.content.pm.PackageArchiver; import android.content.pm.PackageManager; import android.content.pm.VersionedPackage; import android.os.Binder; +import android.os.Bundle; import android.os.ParcelableException; import android.os.UserHandle; import android.text.TextUtils; @@ -36,6 +45,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.server.pm.pkg.ArchiveState; import com.android.server.pm.pkg.ArchiveState.ArchiveActivityInfo; import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.PackageUserStateInternal; import java.nio.file.Path; import java.util.ArrayList; @@ -51,7 +61,12 @@ import java.util.Objects; */ public class PackageArchiverService extends IPackageArchiverService.Stub { - private static final String TAG = "PackageArchiver"; + /** + * The maximum time granted for an app store to start a foreground service when unarchival + * is requested. + */ + // TODO(b/297358628) Make this configurable through a flag. + private static final int DEFAULT_UNARCHIVE_FOREGROUND_TIMEOUT_MS = 120 * 1000; private final Context mContext; private final PackageManagerService mPm; @@ -82,17 +97,14 @@ public class PackageArchiverService extends IPackageArchiverService.Stub { snapshot.enforceCrossUserPermission(binderUid, userId, true, true, "archiveApp"); verifyCaller(providedUid, binderUid); - PackageStateInternal ps = getPackageState(packageName, snapshot, binderUid, userId); - verifyInstaller(packageName, ps); - - // TODO(b/291569242) Verify that this list is not empty and return failure with - // intentsender - List<LauncherActivityInfo> mainActivities = getLauncherApps().getActivityList( - ps.getPackageName(), - new UserHandle(userId)); - - // TODO(b/282952870) Bug: should happen after the uninstall completes successfully - storeArchiveState(ps, mainActivities, userId); + ArchiveState archiveState; + try { + archiveState = createArchiveState(packageName, userId); + // TODO(b/282952870) Should be reverted if uninstall fails/cancels + storeArchiveState(packageName, archiveState, userId); + } catch (PackageManager.NameNotFoundException e) { + throw new ParcelableException(e); + } // TODO(b/278553670) Add special strings for the delete dialog mPm.mInstallerService.uninstall( @@ -100,25 +112,154 @@ public class PackageArchiverService extends IPackageArchiverService.Stub { callerPackageName, DELETE_KEEP_DATA, intentSender, userId); } - private static void verifyInstaller(String packageName, PackageStateInternal ps) { - if (ps.getInstallSource().mUpdateOwnerPackageName == null - && ps.getInstallSource().mInstallerPackageName == null) { + private ArchiveState createArchiveState(String packageName, int userId) + throws PackageManager.NameNotFoundException { + PackageStateInternal ps = getPackageState(packageName, mPm.snapshotComputer(), + Binder.getCallingUid(), userId); + String responsibleInstallerPackage = getResponsibleInstallerPackage(ps); + if (responsibleInstallerPackage == null) { + throw new PackageManager.NameNotFoundException( + TextUtils.formatSimple("No installer found to archive app %s.", + packageName)); + } + + List<LauncherActivityInfo> mainActivities = getLauncherActivityInfos(ps, userId); + List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(); + for (int i = 0; i < mainActivities.size(); i++) { + // TODO(b/278553670) Extract and store launcher icons + ArchiveActivityInfo activityInfo = new ArchiveActivityInfo( + mainActivities.get(i).getLabel().toString(), + Path.of("/TODO"), null); + archiveActivityInfos.add(activityInfo); + } + + return new ArchiveState(archiveActivityInfos, responsibleInstallerPackage); + } + + @Override + public void requestUnarchive( + @NonNull String packageName, + @NonNull String callerPackageName, + @NonNull UserHandle userHandle) { + Objects.requireNonNull(packageName); + Objects.requireNonNull(callerPackageName); + Objects.requireNonNull(userHandle); + + Computer snapshot = mPm.snapshotComputer(); + int userId = userHandle.getIdentifier(); + int binderUid = Binder.getCallingUid(); + int providedUid = snapshot.getPackageUid(callerPackageName, 0, userId); + snapshot.enforceCrossUserPermission(binderUid, userId, true, true, + "unarchiveApp"); + verifyCaller(providedUid, binderUid); + PackageStateInternal ps; + try { + ps = getPackageState(packageName, snapshot, binderUid, userId); + verifyArchived(ps, userId); + } catch (PackageManager.NameNotFoundException e) { + throw new ParcelableException(e); + } + String installerPackage = getResponsibleInstallerPackage(ps); + if (installerPackage == null) { throw new ParcelableException( new PackageManager.NameNotFoundException( - TextUtils.formatSimple("No installer found to archive app %s.", + TextUtils.formatSimple("No installer found to unarchive app %s.", packageName))); } + + mPm.mHandler.post(() -> unarchiveInternal(packageName, userHandle, installerPackage)); + } + + private void verifyArchived(PackageStateInternal ps, int userId) + throws PackageManager.NameNotFoundException { + PackageUserStateInternal userState = ps.getUserStateOrDefault(userId); + // TODO(b/288142708) Check for isInstalled false here too. + if (userState.getArchiveState() == null) { + throw new PackageManager.NameNotFoundException( + TextUtils.formatSimple("Package %s is not currently archived.", + ps.getPackageName())); + } + } + + @RequiresPermission( + allOf = { + Manifest.permission.INTERACT_ACROSS_USERS, + android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, + android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, + android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}, + conditional = true) + private void unarchiveInternal(String packageName, UserHandle userHandle, + String installerPackage) { + int userId = userHandle.getIdentifier(); + Intent unarchiveIntent = new Intent(Intent.ACTION_UNARCHIVE_PACKAGE); + unarchiveIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + unarchiveIntent.putExtra(PackageArchiver.EXTRA_UNARCHIVE_PACKAGE_NAME, packageName); + unarchiveIntent.putExtra(PackageArchiver.EXTRA_UNARCHIVE_ALL_USERS, + userId == UserHandle.USER_ALL); + unarchiveIntent.setPackage(installerPackage); + + // If the unarchival is requested for all users, the current user is used for unarchival. + UserHandle userForUnarchival = userId == UserHandle.USER_ALL + ? UserHandle.of(mPm.mUserManager.getCurrentUserId()) + : userHandle; + mContext.sendOrderedBroadcastAsUser( + unarchiveIntent, + userForUnarchival, + /* receiverPermission = */ null, + AppOpsManager.OP_NONE, + createUnarchiveOptions(), + /* resultReceiver= */ null, + /* scheduler= */ null, + /* initialCode= */ 0, + /* initialData= */ null, + /* initialExtras= */ null); + } + + private List<LauncherActivityInfo> getLauncherActivityInfos(PackageStateInternal ps, + int userId) throws PackageManager.NameNotFoundException { + List<LauncherActivityInfo> mainActivities = + Binder.withCleanCallingIdentity(() -> getLauncherApps().getActivityList( + ps.getPackageName(), + new UserHandle(userId))); + if (mainActivities.isEmpty()) { + throw new PackageManager.NameNotFoundException( + TextUtils.formatSimple("The app %s does not have a main activity.", + ps.getPackageName())); + } + + return mainActivities; + } + + @RequiresPermission(anyOf = {android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, + android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, + android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) + private Bundle createUnarchiveOptions() { + BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setTemporaryAppAllowlist(getUnarchiveForegroundTimeout(), + TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED, + REASON_PACKAGE_UNARCHIVE, ""); + return options.toBundle(); + } + + private static int getUnarchiveForegroundTimeout() { + return DEFAULT_UNARCHIVE_FOREGROUND_TIMEOUT_MS; + } + + private String getResponsibleInstallerPackage(PackageStateInternal ps) { + return ps.getInstallSource().mUpdateOwnerPackageName == null + ? ps.getInstallSource().mInstallerPackageName + : ps.getInstallSource().mUpdateOwnerPackageName; } @NonNull private static PackageStateInternal getPackageState(String packageName, - Computer snapshot, int callingUid, int userId) { + Computer snapshot, int callingUid, int userId) + throws PackageManager.NameNotFoundException { PackageStateInternal ps = snapshot.getPackageStateFiltered(packageName, callingUid, userId); if (ps == null) { - throw new ParcelableException( - new PackageManager.NameNotFoundException( - TextUtils.formatSimple("Package %s not found.", packageName))); + throw new PackageManager.NameNotFoundException( + TextUtils.formatSimple("Package %s not found.", packageName)); } return ps; } @@ -130,38 +271,25 @@ public class PackageArchiverService extends IPackageArchiverService.Stub { return mLauncherApps; } - private void storeArchiveState(PackageStateInternal ps, - List<LauncherActivityInfo> mainActivities, int userId) { - List<ArchiveActivityInfo> activityInfos = new ArrayList<>(); - for (int i = 0; i < mainActivities.size(); i++) { - // TODO(b/278553670) Extract and store launcher icons - ArchiveActivityInfo activityInfo = new ArchiveActivityInfo( - mainActivities.get(i).getLabel().toString(), - Path.of("/TODO"), null); - activityInfos.add(activityInfo); - } - - InstallSource installSource = ps.getInstallSource(); - String installerPackageName = installSource.mUpdateOwnerPackageName != null - ? installSource.mUpdateOwnerPackageName : installSource.mInstallerPackageName; - + private void storeArchiveState(String packageName, ArchiveState archiveState, int userId) + throws PackageManager.NameNotFoundException { synchronized (mPm.mLock) { - PackageSetting packageSetting = getPackageSettingLocked(ps.getPackageName(), userId); + PackageSetting packageSetting = getPackageSettingLocked(packageName, userId); packageSetting .modifyUserState(userId) - .setArchiveState(new ArchiveState(activityInfos, installerPackageName)); + .setArchiveState(archiveState); } } @NonNull @GuardedBy("mPm.mLock") - private PackageSetting getPackageSettingLocked(String packageName, int userId) { + private PackageSetting getPackageSettingLocked(String packageName, int userId) + throws PackageManager.NameNotFoundException { PackageSetting ps = mPm.mSettings.getPackageLPr(packageName); // Shouldn't happen, we already verify presence of the package in getPackageState() if (ps == null || !ps.getUserStateOrDefault(userId).isInstalled()) { - throw new ParcelableException( - new PackageManager.NameNotFoundException( - TextUtils.formatSimple("Package %s not found.", packageName))); + throw new PackageManager.NameNotFoundException( + TextUtils.formatSimple("Package %s not found.", packageName)); } return ps; } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index cbac39a5e0f2..63794d57fabc 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -3354,6 +3354,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { return true; } break; + case KeyEvent.KEYCODE_DEL: + case KeyEvent.KEYCODE_GRAVE: + if (firstDown && event.isMetaPressed()) { + logKeyboardSystemsEvent(event, KeyboardLogEvent.BACK); + injectBackGesture(event.getDownTime()); + return true; + } case KeyEvent.KEYCODE_DPAD_UP: if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { StatusBarManagerInternal statusbar = getStatusBarManagerInternal(); @@ -3365,9 +3372,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { } break; case KeyEvent.KEYCODE_DPAD_LEFT: - if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { - enterStageSplitFromRunningApp(true /* leftOrTop */); - logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION); + if (firstDown && event.isMetaPressed()) { + if (event.isCtrlPressed()) { + enterStageSplitFromRunningApp(true /* leftOrTop */); + logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION); + } else { + logKeyboardSystemsEvent(event, KeyboardLogEvent.BACK); + injectBackGesture(event.getDownTime()); + } return true; } break; @@ -3630,6 +3642,25 @@ public class PhoneWindowManager implements WindowManagerPolicy { return (metaState & KeyEvent.META_META_ON) != 0; } + @SuppressLint("MissingPermission") + private void injectBackGesture(long downtime) { + // Create and inject down event + KeyEvent downEvent = new KeyEvent(downtime, downtime, KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_BACK, 0 /* repeat */, 0 /* metaState */, + KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, + KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, + InputDevice.SOURCE_KEYBOARD); + mInputManager.injectInputEvent(downEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + + + // Create and inject up event + KeyEvent upEvent = KeyEvent.changeAction(downEvent, KeyEvent.ACTION_UP); + mInputManager.injectInputEvent(upEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + + downEvent.recycle(); + upEvent.recycle(); + } + private boolean handleHomeShortcuts(int displayId, IBinder focusedToken, KeyEvent event) { // First we always handle the home key here, so applications // can never break it, although if keyguard is on, we do let diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 53d1adfefbb9..fd42077bed7d 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -6183,6 +6183,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public void onPackageReplaced(ApplicationInfo aInfo) { synchronized (mGlobalLock) { + // In case if setWindowManager hasn't been called yet when booting. + if (mRootWindowContainer == null) return; mRootWindowContainer.updateActivityApplicationInfo(aInfo); } } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 1871cf6175c5..707b7799460e 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR; import static android.view.Display.TYPE_INTERNAL; import static android.view.InsetsFrameProvider.SOURCE_ARBITRARY_RECTANGLE; import static android.view.InsetsFrameProvider.SOURCE_CONTAINER_BOUNDS; @@ -1201,8 +1202,8 @@ public class DisplayPolicy { throw new IllegalArgumentException("IME insets must be provided by a window."); } - if (mNavigationBar != null && navigationBarPosition(displayFrames.mRotation) - == NAV_BAR_BOTTOM) { + if (!ENABLE_HIDE_IME_CAPTION_BAR && mNavigationBar != null + && navigationBarPosition(displayFrames.mRotation) == NAV_BAR_BOTTOM) { // In gesture navigation, nav bar frame is larger than frame to calculate insets. // IME should not provide frame which is smaller than the nav bar frame. Otherwise, // nav bar might be overlapped with the content of the client when IME is shown. diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 2a995b24ef4f..ec5378f01ce3 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -41,6 +41,7 @@ cc_library_static { "com_android_server_companion_virtual_InputController.cpp", "com_android_server_devicepolicy_CryptoTestHelper.cpp", "com_android_server_display_DisplayControl.cpp", + "com_android_server_display_SmallAreaDetectionController.cpp", "com_android_server_connectivity_Vpn.cpp", "com_android_server_gpu_GpuService.cpp", "com_android_server_HardwarePropertiesManagerService.cpp", diff --git a/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp new file mode 100644 index 000000000000..b256f168f2af --- /dev/null +++ b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2023 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. + */ + +#define LOG_TAG "SmallAreaDetectionController" + +#include <gui/SurfaceComposerClient.h> +#include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedPrimitiveArray.h> + +#include "jni.h" +#include "utils/Log.h" + +namespace android { +static void nativeUpdateSmallAreaDetection(JNIEnv* env, jclass clazz, jintArray juids, + jfloatArray jthresholds) { + if (juids == nullptr || jthresholds == nullptr) return; + + ScopedIntArrayRO uids(env, juids); + ScopedFloatArrayRO thresholds(env, jthresholds); + + if (uids.size() != thresholds.size()) { + ALOGE("uids size exceeds thresholds size!"); + return; + } + + std::vector<int32_t> uidVector; + std::vector<float> thresholdVector; + size_t size = uids.size(); + uidVector.reserve(size); + thresholdVector.reserve(size); + for (int i = 0; i < size; i++) { + uidVector.push_back(static_cast<int32_t>(uids[i])); + thresholdVector.push_back(static_cast<float>(thresholds[i])); + } + SurfaceComposerClient::updateSmallAreaDetection(uidVector, thresholdVector); +} + +static void nativeSetSmallAreaDetectionThreshold(JNIEnv* env, jclass clazz, jint uid, + jfloat threshold) { + SurfaceComposerClient::setSmallAreaDetectionThreshold(uid, threshold); +} + +static const JNINativeMethod gMethods[] = { + {"nativeUpdateSmallAreaDetection", "([I[F)V", (void*)nativeUpdateSmallAreaDetection}, + {"nativeSetSmallAreaDetectionThreshold", "(IF)V", + (void*)nativeSetSmallAreaDetectionThreshold}, +}; + +int register_android_server_display_smallAreaDetectionController(JNIEnv* env) { + return jniRegisterNativeMethods(env, "com/android/server/display/SmallAreaDetectionController", + gMethods, NELEM(gMethods)); +} + +}; // namespace android diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index 97d7be6a718e..f6f673746609 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -67,6 +67,7 @@ int register_android_server_app_GameManagerService(JNIEnv* env); int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env); int register_com_android_server_display_DisplayControl(JNIEnv* env); int register_com_android_server_SystemClockTime(JNIEnv* env); +int register_android_server_display_smallAreaDetectionController(JNIEnv* env); }; using namespace android; @@ -126,5 +127,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_com_android_server_wm_TaskFpsCallbackController(env); register_com_android_server_display_DisplayControl(env); register_com_android_server_SystemClockTime(env); + register_android_server_display_smallAreaDetectionController(env); return JNI_VERSION_1_4; } diff --git a/services/tests/displayservicetests/Android.bp b/services/tests/displayservicetests/Android.bp index fb14419a13c0..e28028f9fc2b 100644 --- a/services/tests/displayservicetests/Android.bp +++ b/services/tests/displayservicetests/Android.bp @@ -35,6 +35,7 @@ android_test { "mockingservicestests-utils-mockito", "platform-compat-test-rules", "platform-test-annotations", + "service-permission.stubs.system_server", "services.core", "servicestests-utils", "testables", diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index 40d7a774608b..a23539e37409 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -64,6 +64,7 @@ import android.compat.testing.PlatformCompatChangeRule; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import android.content.res.Resources; import android.graphics.Insets; import android.graphics.Rect; @@ -113,6 +114,7 @@ import com.android.server.display.DisplayManagerService.SyncRoot; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.input.InputManagerInternal; import com.android.server.lights.LightsManager; +import com.android.server.pm.UserManagerInternal; import com.android.server.sensors.SensorManagerInternal; import com.android.server.wm.WindowManagerInternal; @@ -291,10 +293,11 @@ public class DisplayManagerServiceTest { @Mock LocalDisplayAdapter.SurfaceControlProxy mSurfaceControlProxy; @Mock IBinder mMockDisplayToken; @Mock SensorManagerInternal mMockSensorManagerInternal; - @Mock SensorManager mSensorManager; - @Mock DisplayDeviceConfig mMockDisplayDeviceConfig; + @Mock PackageManagerInternal mMockPackageManagerInternal; + @Mock UserManagerInternal mMockUserManagerInternal; + @Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor; @Mock DisplayManagerFlags mMockFlags; @@ -315,6 +318,10 @@ public class DisplayManagerServiceTest { LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class); LocalServices.addService( VirtualDeviceManagerInternal.class, mMockVirtualDeviceManagerInternal); + LocalServices.removeServiceForTest(PackageManagerInternal.class); + LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal); + LocalServices.removeServiceForTest(UserManagerInternal.class); + LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal); // TODO: b/287945043 mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); mResources = Mockito.spy(mContext.getResources()); diff --git a/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java new file mode 100644 index 000000000000..1ce79a5b596b --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import static android.os.Process.INVALID_UID; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ContextWrapper; +import android.content.pm.PackageManagerInternal; +import android.provider.DeviceConfigInterface; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.LocalServices; +import com.android.server.pm.UserManagerInternal; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class SmallAreaDetectionControllerTest { + + @Rule + public MockitoRule mRule = MockitoJUnit.rule(); + + @Mock + private PackageManagerInternal mMockPackageManagerInternal; + @Mock + private UserManagerInternal mMockUserManagerInternal; + + private SmallAreaDetectionController mSmallAreaDetectionController; + + private static final String PKG_A = "com.a.b.c"; + private static final String PKG_B = "com.d.e.f"; + private static final String PKG_NOT_INSTALLED = "com.not.installed"; + private static final float THRESHOLD_A = 0.05f; + private static final float THRESHOLD_B = 0.07f; + private static final int USER_1 = 110; + private static final int USER_2 = 111; + private static final int UID_A_1 = 11011111; + private static final int UID_A_2 = 11111111; + private static final int UID_B_1 = 11022222; + private static final int UID_B_2 = 11122222; + + @Before + public void setup() { + LocalServices.removeServiceForTest(PackageManagerInternal.class); + LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal); + LocalServices.removeServiceForTest(UserManagerInternal.class); + LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal); + + when(mMockUserManagerInternal.getUserIds()).thenReturn(new int[]{USER_1, USER_2}); + when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_1)).thenReturn(UID_A_1); + when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_2)).thenReturn(UID_A_2); + when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_1)).thenReturn(UID_B_1); + when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_2)).thenReturn(UID_B_2); + when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_1)).thenReturn( + INVALID_UID); + when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_2)).thenReturn( + INVALID_UID); + + mSmallAreaDetectionController = spy(new SmallAreaDetectionController( + new ContextWrapper(ApplicationProvider.getApplicationContext()), + DeviceConfigInterface.REAL)); + doNothing().when(mSmallAreaDetectionController).updateSmallAreaDetection(any(), any()); + } + + @Test + public void testUpdateAllowlist_validProperty() { + final String property = PKG_A + ":" + THRESHOLD_A + "," + PKG_B + ":" + THRESHOLD_B; + mSmallAreaDetectionController.updateAllowlist(property); + + final int[] resultUidArray = {UID_A_1, UID_B_1, UID_A_2, UID_B_2}; + final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_B, THRESHOLD_A, THRESHOLD_B}; + verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray), + eq(resultThresholdArray)); + } + + @Test + public void testUpdateAllowlist_includeInvalidRow() { + final String property = PKG_A + "," + PKG_B + ":" + THRESHOLD_B; + mSmallAreaDetectionController.updateAllowlist(property); + + final int[] resultUidArray = {UID_B_1, UID_B_2}; + final float[] resultThresholdArray = {THRESHOLD_B, THRESHOLD_B}; + verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray), + eq(resultThresholdArray)); + } + + @Test + public void testUpdateAllowlist_includeNotInstalledPkg() { + final String property = + PKG_A + ":" + THRESHOLD_A + "," + PKG_NOT_INSTALLED + ":" + THRESHOLD_B; + mSmallAreaDetectionController.updateAllowlist(property); + + final int[] resultUidArray = {UID_A_1, UID_A_2}; + final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_A}; + verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray), + eq(resultThresholdArray)); + } + + @Test + public void testUpdateAllowlist_invalidProperty() { + final String property = PKG_A; + mSmallAreaDetectionController.updateAllowlist(property); + + verify(mSmallAreaDetectionController, never()).updateSmallAreaDetection(any(), any()); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java index c7e1bda2e1b8..e58a2342a718 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java @@ -16,41 +16,51 @@ package com.android.server.pm; +import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; import static android.content.pm.PackageManager.DELETE_KEEP_DATA; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.AppOpsManager; import android.content.Context; +import android.content.Intent; import android.content.IntentSender; import android.content.pm.LauncherActivityInfo; import android.content.pm.LauncherApps; +import android.content.pm.PackageArchiver; import android.content.pm.PackageManager; import android.content.pm.VersionedPackage; import android.os.Binder; import android.os.Build; +import android.os.Bundle; import android.os.ParcelableException; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; +import android.text.TextUtils; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.server.pm.pkg.ArchiveState; import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.PackageUserStateImpl; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -64,7 +74,8 @@ import java.util.List; public class PackageArchiverServiceTest { private static final String PACKAGE = "com.example"; - private static final String CALLER_PACKAGE = "com.vending"; + private static final String CALLER_PACKAGE = "com.caller"; + private static final String INSTALLER_PACKAGE = "com.installer"; @Rule public final MockSystemRule mMockSystem = new MockSystemRule(); @@ -84,11 +95,11 @@ public class PackageArchiverServiceTest { private final InstallSource mInstallSource = InstallSource.create( - CALLER_PACKAGE, - CALLER_PACKAGE, - CALLER_PACKAGE, + INSTALLER_PACKAGE, + INSTALLER_PACKAGE, + INSTALLER_PACKAGE, Binder.getCallingUid(), - CALLER_PACKAGE, + INSTALLER_PACKAGE, /* installerAttributionTag= */ null, /* packageSource= */ 0); @@ -96,6 +107,8 @@ public class PackageArchiverServiceTest { private final int mUserId = UserHandle.CURRENT.getIdentifier(); + private PackageUserStateImpl mUserState; + private PackageSetting mPackageSetting; private PackageArchiverService mArchiveService; @@ -116,11 +129,16 @@ public class PackageArchiverServiceTest { when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn( mPackageState); + when(mComputer.getPackageStateFiltered(eq(INSTALLER_PACKAGE), anyInt(), + anyInt())).thenReturn(mock(PackageStateInternal.class)); when(mPackageState.getPackageName()).thenReturn(PACKAGE); when(mPackageState.getInstallSource()).thenReturn(mInstallSource); mPackageSetting = createBasicPackageSetting(); when(mMockSystem.mocks().getSettings().getPackageLPr(eq(PACKAGE))).thenReturn( mPackageSetting); + mUserState = new PackageUserStateImpl().setInstalled(true); + mPackageSetting.setUserState(mUserId, mUserState); + when(mPackageState.getUserStateOrDefault(eq(mUserId))).thenReturn(mUserState); when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps); when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn( mLauncherActivityInfos); @@ -135,9 +153,7 @@ public class PackageArchiverServiceTest { Exception e = assertThrows( SecurityException.class, () -> mArchiveService.requestArchive(PACKAGE, "different", mIntentSender, - UserHandle.CURRENT - ) - ); + UserHandle.CURRENT)); assertThat(e).hasMessageThat().isEqualTo( String.format( "The UID %s of callerPackageName set by the caller doesn't match the " @@ -154,9 +170,7 @@ public class PackageArchiverServiceTest { Exception e = assertThrows( ParcelableException.class, () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, - UserHandle.CURRENT - ) - ); + UserHandle.CURRENT)); assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); assertThat(e.getCause()).hasMessageThat().isEqualTo( String.format("Package %s not found.", PACKAGE)); @@ -169,9 +183,7 @@ public class PackageArchiverServiceTest { Exception e = assertThrows( ParcelableException.class, () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, - UserHandle.CURRENT - ) - ); + UserHandle.CURRENT)); assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); assertThat(e.getCause()).hasMessageThat().isEqualTo( String.format("Package %s not found.", PACKAGE)); @@ -193,25 +205,28 @@ public class PackageArchiverServiceTest { Exception e = assertThrows( ParcelableException.class, () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, - UserHandle.CURRENT - ) - ); + UserHandle.CURRENT)); assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); assertThat(e.getCause()).hasMessageThat().isEqualTo( String.format("No installer found to archive app %s.", PACKAGE)); } @Test - public void archiveApp_success() { - List<ArchiveState.ArchiveActivityInfo> activityInfos = new ArrayList<>(); - for (LauncherActivityInfo mainActivity : createLauncherActivities()) { - // TODO(b/278553670) Extract and store launcher icons - ArchiveState.ArchiveActivityInfo activityInfo = new ArchiveState.ArchiveActivityInfo( - mainActivity.getLabel().toString(), - Path.of("/TODO"), null); - activityInfos.add(activityInfo); - } + public void archiveApp_noMainActivities() { + when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn( + List.of()); + Exception e = assertThrows( + ParcelableException.class, + () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, + UserHandle.CURRENT)); + assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); + assertThat(e.getCause()).hasMessageThat().isEqualTo( + TextUtils.formatSimple("The app %s does not have a main activity.", PACKAGE)); + } + + @Test + public void archiveApp_success() { mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT); verify(mInstallerService).uninstall( @@ -220,7 +235,112 @@ public class PackageArchiverServiceTest { eq(UserHandle.CURRENT.getIdentifier())); assertThat(mPackageSetting.readUserState( UserHandle.CURRENT.getIdentifier()).getArchiveState()).isEqualTo( - new ArchiveState(activityInfos, CALLER_PACKAGE)); + createArchiveState()); + } + + @Test + public void unarchiveApp_callerPackageNameIncorrect() { + mUserState.setArchiveState(createArchiveState()).setInstalled(false); + + Exception e = assertThrows( + SecurityException.class, + () -> mArchiveService.requestUnarchive(PACKAGE, "different", + UserHandle.CURRENT)); + assertThat(e).hasMessageThat().isEqualTo( + String.format( + "The UID %s of callerPackageName set by the caller doesn't match the " + + "caller's actual UID %s.", + 0, + Binder.getCallingUid())); + } + + @Test + public void unarchiveApp_packageNotInstalled() { + mUserState.setArchiveState(createArchiveState()).setInstalled(false); + when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn( + null); + + Exception e = assertThrows( + ParcelableException.class, + () -> mArchiveService.requestUnarchive(PACKAGE, CALLER_PACKAGE, + UserHandle.CURRENT)); + assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); + assertThat(e.getCause()).hasMessageThat().isEqualTo( + String.format("Package %s not found.", PACKAGE)); + } + + @Test + public void unarchiveApp_notArchived() { + Exception e = assertThrows( + ParcelableException.class, + () -> mArchiveService.requestUnarchive(PACKAGE, CALLER_PACKAGE, + UserHandle.CURRENT)); + assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); + assertThat(e.getCause()).hasMessageThat().isEqualTo( + String.format("Package %s is not currently archived.", PACKAGE)); + } + + @Test + public void unarchiveApp_noInstallerFound() { + mUserState.setArchiveState(createArchiveState()); + InstallSource otherInstallSource = + InstallSource.create( + CALLER_PACKAGE, + CALLER_PACKAGE, + /* installerPackageName= */ null, + Binder.getCallingUid(), + /* updateOwnerPackageName= */ null, + /* installerAttributionTag= */ null, + /* packageSource= */ 0); + when(mPackageState.getInstallSource()).thenReturn(otherInstallSource); + + Exception e = assertThrows( + ParcelableException.class, + () -> mArchiveService.requestUnarchive(PACKAGE, CALLER_PACKAGE, + UserHandle.CURRENT)); + assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); + assertThat(e.getCause()).hasMessageThat().isEqualTo( + String.format("No installer found to unarchive app %s.", PACKAGE)); + } + + @Test + public void unarchiveApp_success() { + mUserState.setArchiveState(createArchiveState()).setInstalled(false); + + mArchiveService.requestUnarchive(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT); + mMockSystem.mocks().getHandler().flush(); + + ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(mContext).sendOrderedBroadcastAsUser( + intentCaptor.capture(), + eq(UserHandle.CURRENT), + /* receiverPermission = */ isNull(), + eq(AppOpsManager.OP_NONE), + any(Bundle.class), + /* resultReceiver= */ isNull(), + /* scheduler= */ isNull(), + /* initialCode= */ eq(0), + /* initialData= */ isNull(), + /* initialExtras= */ isNull()); + Intent intent = intentCaptor.getValue(); + assertThat(intent.getFlags() & FLAG_RECEIVER_FOREGROUND).isNotEqualTo(0); + assertThat(intent.getStringExtra(PackageArchiver.EXTRA_UNARCHIVE_PACKAGE_NAME)).isEqualTo( + PACKAGE); + assertThat( + intent.getBooleanExtra(PackageArchiver.EXTRA_UNARCHIVE_ALL_USERS, true)).isFalse(); + assertThat(intent.getPackage()).isEqualTo(INSTALLER_PACKAGE); + } + + private static ArchiveState createArchiveState() { + List<ArchiveState.ArchiveActivityInfo> activityInfos = new ArrayList<>(); + for (LauncherActivityInfo mainActivity : createLauncherActivities()) { + // TODO(b/278553670) Extract and store launcher icons + ArchiveState.ArchiveActivityInfo activityInfo = new ArchiveState.ArchiveActivityInfo( + mainActivity.getLabel().toString(), + Path.of("/TODO"), null); + activityInfos.add(activityInfo); + } + return new ArchiveState(activityInfos, INSTALLER_PACKAGE); } private static List<LauncherActivityInfo> createLauncherActivities() { diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java index 8fadecd0dc38..e13dc3eea729 100644 --- a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java @@ -65,6 +65,12 @@ public class ShortcutLoggingTests extends ShortcutKeyTestBase { KeyboardLogEvent.RECENT_APPS, KeyEvent.KEYCODE_TAB, ALT_ON}, {"BACK key -> Go back", new int[]{KeyEvent.KEYCODE_BACK}, KeyboardLogEvent.BACK, KeyEvent.KEYCODE_BACK, 0}, + {"Meta + `(grave) -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_GRAVE}, + KeyboardLogEvent.BACK, KeyEvent.KEYCODE_GRAVE, META_ON}, + {"Meta + Left arrow -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DPAD_LEFT}, + KeyboardLogEvent.BACK, KeyEvent.KEYCODE_DPAD_LEFT, META_ON}, + {"Meta + Del -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DEL}, + KeyboardLogEvent.BACK, KeyEvent.KEYCODE_DEL, META_ON}, {"APP_SWITCH key -> Open App switcher", new int[]{KeyEvent.KEYCODE_APP_SWITCH}, KeyboardLogEvent.APP_SWITCH, KeyEvent.KEYCODE_APP_SWITCH, 0}, {"ASSIST key -> Launch assistant", new int[]{KeyEvent.KEYCODE_ASSIST}, diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java index dd90e0450280..bf86563e3d86 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR; import static android.view.DisplayCutout.NO_CUTOUT; import static android.view.InsetsSource.ID_IME; import static android.view.RoundedCorners.NO_ROUNDED_CORNERS; @@ -427,11 +428,11 @@ public class DisplayPolicyTests extends WindowTestsBase { @SetupWindows(addWindows = { W_NAVIGATION_BAR, W_INPUT_METHOD }) @Test public void testImeMinimalSourceFrame() { + Assume.assumeFalse("Behavior no longer needed with ENABLE_HIDE_IME_CAPTION_BAR", + ENABLE_HIDE_IME_CAPTION_BAR); + final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy(); - final DisplayInfo displayInfo = new DisplayInfo(); - displayInfo.logicalWidth = 1000; - displayInfo.logicalHeight = 2000; - displayInfo.rotation = ROTATION_0; + final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo(); WindowManager.LayoutParams attrs = mNavBarWindow.mAttrs; displayPolicy.addWindowLw(mNavBarWindow, attrs); @@ -466,10 +467,6 @@ public class DisplayPolicyTests extends WindowTestsBase { @Test public void testImeInsetsGivenContentFrame() { final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy(); - final DisplayInfo displayInfo = new DisplayInfo(); - displayInfo.logicalWidth = 1000; - displayInfo.logicalHeight = 2000; - displayInfo.rotation = ROTATION_0; mDisplayContent.setInputMethodWindowLocked(mImeWindow); mImeWindow.getControllableInsetProvider().setServerVisible(true); diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java index 06033c7ebf75..3fcec963593c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java @@ -184,7 +184,7 @@ public class LetterboxConfigurationPersisterTest { LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT); firstPersister.setLetterboxPositionForVerticalReachability(false, LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP); - waitForCompletion(mPersisterQueue); + waitForCompletion(firstPersisterQueue); final int newPositionForHorizontalReachability = firstPersister.getLetterboxPositionForHorizontalReachability(false); final int newPositionForVerticalReachability = |