summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt21
-rw-r--r--core/api/module-lib-current.txt8
-rw-r--r--core/api/test-current.txt8
-rw-r--r--core/java/android/app/ActivityManager.java127
-rw-r--r--core/java/android/app/IActivityManager.aidl4
-rw-r--r--core/java/android/app/IUidFrozenStateChangedCallback.aidl25
-rw-r--r--core/java/android/app/WallpaperManager.java228
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java30
-rw-r--r--core/java/android/hardware/input/VirtualKeyboardConfig.java11
-rw-r--r--core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java61
-rw-r--r--core/java/android/inputmethodservice/RemoteInputConnection.java11
-rw-r--r--core/java/android/os/CancellationSignalBeamer.java66
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java5
-rw-r--r--core/java/android/view/autofill/AutofillManager.java8
-rw-r--r--core/java/android/view/inputmethod/CancellableHandwritingGesture.java53
-rw-r--r--core/java/android/view/inputmethod/InsertModeGesture.java7
-rw-r--r--core/java/android/view/inputmethod/RemoteInputConnectionImpl.java54
-rw-r--r--core/java/android/window/ITaskOrganizerController.aidl10
-rw-r--r--core/java/android/window/TaskOrganizer.java19
-rw-r--r--core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl7
-rw-r--r--core/res/AndroidManifest.xml10
-rw-r--r--core/res/res/values/public-staging.xml12
-rw-r--r--core/tests/coretests/src/android/hardware/face/FaceManagerTest.java2
-rw-r--r--core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java2
-rw-r--r--data/etc/com.android.systemui.xml1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java759
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java37
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt16
-rw-r--r--libs/WindowManager/Shell/tests/unittest/Android.bp2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java47
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java17
-rw-r--r--packages/CompanionDeviceManager/res/drawable/btn_negative_multiple_devices.xml3
-rw-r--r--packages/CompanionDeviceManager/res/layout/activity_confirmation.xml27
-rw-r--r--packages/CompanionDeviceManager/res/values/styles.xml4
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java7
-rw-r--r--packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java4
-rw-r--r--packages/SystemUI/Android.bp3
-rw-r--r--packages/SystemUI/AndroidManifest.xml1
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java25
-rw-r--r--packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml29
-rw-r--r--packages/SystemUI/res/layout/dream_overlay_media_entry_chip.xml11
-rw-r--r--packages/SystemUI/res/layout/media_recommendation_view.xml6
-rw-r--r--packages/SystemUI/res/layout/media_smartspace_recommendations.xml8
-rw-r--r--packages/SystemUI/res/values/dimens.xml2
-rw-r--r--packages/SystemUI/res/values/styles.xml2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java55
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java29
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt131
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt132
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java177
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java52
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java9
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java79
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java48
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java5
-rw-r--r--services/core/java/com/android/server/am/BroadcastProcessQueue.java2
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java4
-rw-r--r--services/core/java/com/android/server/am/CachedAppOptimizer.java193
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java6
-rw-r--r--services/core/java/com/android/server/am/UidRecord.java34
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java8
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java4
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java47
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java8
-rw-r--r--services/core/java/com/android/server/wm/ActivityStartController.java40
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java10
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java36
-rw-r--r--services/core/java/com/android/server/wm/LetterboxUiController.java4
-rw-r--r--services/core/java/com/android/server/wm/TaskOrganizerController.java9
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java49
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java75
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java106
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java11
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java14
-rw-r--r--telephony/java/android/telephony/SubscriptionManager.java7
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java15
-rw-r--r--wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java176
109 files changed, 3154 insertions, 624 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index e091b629d040..8cf7571f4e8a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -224,6 +224,7 @@ package android {
field @Deprecated public static final String PERSISTENT_ACTIVITY = "android.permission.PERSISTENT_ACTIVITY";
field public static final String POST_NOTIFICATIONS = "android.permission.POST_NOTIFICATIONS";
field @Deprecated public static final String PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS";
+ field public static final String PROVIDE_OWN_AUTOFILL_SUGGESTIONS = "android.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS";
field public static final String PROVIDE_REMOTE_CREDENTIALS = "android.permission.PROVIDE_REMOTE_CREDENTIALS";
field public static final String QUERY_ALL_PACKAGES = "android.permission.QUERY_ALL_PACKAGES";
field public static final String READ_ASSISTANT_APP_SEARCH_DATA = "android.permission.READ_ASSISTANT_APP_SEARCH_DATA";
@@ -7575,23 +7576,23 @@ package android.app {
method public android.content.Intent getCropAndSetWallpaperIntent(android.net.Uri);
method public int getDesiredMinimumHeight();
method public int getDesiredMinimumWidth();
- method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable getDrawable();
- method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable getDrawable(int);
- method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable getFastDrawable();
- method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable getFastDrawable(int);
+ method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable getDrawable();
+ method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable getDrawable(int);
+ method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable getFastDrawable();
+ method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable getFastDrawable(int);
method public static android.app.WallpaperManager getInstance(android.content.Context);
method @Nullable public android.app.WallpaperColors getWallpaperColors(int);
- method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.os.ParcelFileDescriptor getWallpaperFile(int);
+ method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.os.ParcelFileDescriptor getWallpaperFile(int);
method public int getWallpaperId(int);
method public android.app.WallpaperInfo getWallpaperInfo();
method @Nullable public android.app.WallpaperInfo getWallpaperInfo(int);
method public boolean hasResourceWallpaper(@RawRes int);
method public boolean isSetWallpaperAllowed();
method public boolean isWallpaperSupported();
- method @Nullable public android.graphics.drawable.Drawable peekDrawable();
- method @Nullable public android.graphics.drawable.Drawable peekDrawable(int);
- method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable peekFastDrawable();
- method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable peekFastDrawable(int);
+ method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable peekDrawable();
+ method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable peekDrawable(int);
+ method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable peekFastDrawable();
+ method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable peekFastDrawable(int);
method public void removeOnColorsChangedListener(@NonNull android.app.WallpaperManager.OnColorsChangedListener);
method public void sendWallpaperCommand(android.os.IBinder, String, int, int, int, android.os.Bundle);
method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public void setBitmap(android.graphics.Bitmap) throws java.io.IOException;
@@ -55076,7 +55077,7 @@ package android.view.autofill {
method public void registerCallback(@Nullable android.view.autofill.AutofillManager.AutofillCallback);
method public void requestAutofill(@NonNull android.view.View);
method public void requestAutofill(@NonNull android.view.View, int, @NonNull android.graphics.Rect);
- method public void setAutofillRequestCallback(@NonNull java.util.concurrent.Executor, @NonNull android.view.autofill.AutofillRequestCallback);
+ method @RequiresPermission(android.Manifest.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS) public void setAutofillRequestCallback(@NonNull java.util.concurrent.Executor, @NonNull android.view.autofill.AutofillRequestCallback);
method public void setUserData(@Nullable android.service.autofill.UserData);
method public boolean showAutofillDialog(@NonNull android.view.View);
method public boolean showAutofillDialog(@NonNull android.view.View, int);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 1e21c77675c6..58f78aa4fc15 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -18,10 +18,18 @@ package android.app {
public class ActivityManager {
method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void addHomeVisibilityListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.HomeVisibilityListener);
+ method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void registerUidFrozenStateChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.app.ActivityManager.UidFrozenStateChangedCallback);
method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void removeHomeVisibilityListener(@NonNull android.app.HomeVisibilityListener);
+ method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void unregisterUidFrozenStateChangedCallback(@NonNull android.app.ActivityManager.UidFrozenStateChangedCallback);
method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String);
}
+ public static interface ActivityManager.UidFrozenStateChangedCallback {
+ method public void onUidFrozenStateChanged(@NonNull int[], @NonNull int[]);
+ field public static final int UID_FROZEN_STATE_FROZEN = 1; // 0x1
+ field public static final int UID_FROZEN_STATE_UNFROZEN = 2; // 0x2
+ }
+
public class AppOpsManager {
field public static final String OPSTR_NO_ISOLATED_STORAGE = "android:no_isolated_storage";
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 658a25dd1faa..c2c7180f22ef 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -476,7 +476,7 @@ package android.app {
method @Nullable public android.graphics.Rect peekBitmapDimensions(int);
method public void setWallpaperZoomOut(@NonNull android.os.IBinder, float);
method public boolean shouldEnableWideColorGamut();
- method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public boolean wallpaperSupportsWcg(int);
+ method public boolean wallpaperSupportsWcg(int);
}
public class WindowConfiguration implements java.lang.Comparable<android.app.WindowConfiguration> android.os.Parcelable {
@@ -2033,6 +2033,7 @@ package android.net.wifi.sharedconnectivity.service {
public abstract class SharedConnectivityService extends android.app.Service {
method public void onBind();
+ method public final void setCountdownLatch(@Nullable java.util.concurrent.CountDownLatch);
}
}
@@ -3600,6 +3601,11 @@ package android.view.displayhash {
package android.view.inputmethod {
+ public abstract class CancellableHandwritingGesture extends android.view.inputmethod.HandwritingGesture {
+ ctor public CancellableHandwritingGesture();
+ method public void setCancellationSignal(@NonNull android.os.CancellationSignal);
+ }
+
public abstract class HandwritingGesture {
method @NonNull public static android.view.inputmethod.HandwritingGesture fromByteArray(@NonNull byte[]);
method public final int getGestureType();
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index ff75098ae5e8..981f14020370 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -231,6 +231,133 @@ public class ActivityManager {
final ArrayMap<OnUidImportanceListener, UidObserver> mImportanceListeners = new ArrayMap<>();
/**
+ * Map of callbacks that have registered for {@link UidFrozenStateChanged} events.
+ * Will be called when a Uid has become frozen or unfrozen.
+ */
+ final ArrayMap<UidFrozenStateChangedCallback, Executor> mFrozenStateChangedCallbacks =
+ new ArrayMap<>();
+
+ private final IUidFrozenStateChangedCallback mFrozenStateChangedCallback =
+ new IUidFrozenStateChangedCallback.Stub() {
+ @Override
+ public void onUidFrozenStateChanged(int[] uids, int[] frozenStates) {
+ synchronized (mFrozenStateChangedCallbacks) {
+ mFrozenStateChangedCallbacks.forEach((callback, executor) -> {
+ executor.execute(
+ () -> callback.onUidFrozenStateChanged(uids, frozenStates));
+ });
+ }
+ }
+ };
+
+ /**
+ * Callback object for {@link #registerUidFrozenStateChangedCallback}
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public interface UidFrozenStateChangedCallback {
+ /**
+ * Indicates that the UID was frozen.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ int UID_FROZEN_STATE_FROZEN = 1;
+
+ /**
+ * Indicates that the UID was unfrozen.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ int UID_FROZEN_STATE_UNFROZEN = 2;
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = false, prefix = {"UID_FROZEN_STATE_"}, value = {
+ UID_FROZEN_STATE_FROZEN,
+ UID_FROZEN_STATE_UNFROZEN,
+ })
+ public @interface UidFrozenState {}
+
+ /**
+ * @param uids The UIDs for which the frozen state has changed
+ * @param frozenStates Frozen state for each UID index, Will be set to
+ * {@link UidFrozenStateChangedCallback#UID_FROZEN_STATE_FROZEN}
+ * when the UID is frozen. When the UID is unfrozen,
+ * {@link UidFrozenStateChangedCallback#UID_FROZEN_STATE_UNFROZEN}
+ * will be set.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ void onUidFrozenStateChanged(@NonNull int[] uids,
+ @NonNull @UidFrozenState int[] frozenStates);
+ }
+
+ /**
+ * Register a {@link UidFrozenStateChangedCallback} object to receive notification
+ * when a UID is frozen or unfrozen. Will throw an exception if the same
+ * callback object is registered more than once.
+ *
+ * @param executor The executor that the callback will be run from.
+ * @param callback The callback to be registered. Callbacks for previous frozen/unfrozen
+ * UID changes will not be delivered. Only changes in state from the point of
+ * registration onward will be reported.
+ * @throws IllegalStateException if the {@code callback} is already registered.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public void registerUidFrozenStateChangedCallback(
+ @NonNull Executor executor,
+ @NonNull UidFrozenStateChangedCallback callback) {
+ synchronized (mFrozenStateChangedCallbacks) {
+ if (mFrozenStateChangedCallbacks.containsKey(callback)) {
+ throw new IllegalArgumentException("Callback already registered: " + callback);
+ }
+ mFrozenStateChangedCallbacks.put(callback, executor);
+ if (mFrozenStateChangedCallbacks.size() > 1) {
+ /* There's no need to register more than one binder interface */
+ return;
+ }
+
+ try {
+ getService().registerUidFrozenStateChangedCallback(mFrozenStateChangedCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Unregister a {@link UidFrozenStateChangedCallback} callback.
+ * @param callback The callback to be unregistered.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public void unregisterUidFrozenStateChangedCallback(
+ @NonNull UidFrozenStateChangedCallback callback) {
+ synchronized (mFrozenStateChangedCallbacks) {
+ mFrozenStateChangedCallbacks.remove(callback);
+ if (mFrozenStateChangedCallbacks.isEmpty()) {
+ try {
+ getService().unregisterUidFrozenStateChangedCallback(
+ mFrozenStateChangedCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+
+ /**
* <a href="{@docRoot}guide/topics/manifest/meta-data-element.html">{@code
* <meta-data>}</a> name for a 'home' Activity that declares a package that is to be
* uninstalled in lieu of the declaring one. The package named here must be
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 62298a5e9656..2879062248a8 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -35,6 +35,7 @@ import android.app.IServiceConnection;
import android.app.IStopUserCallback;
import android.app.ITaskStackListener;
import android.app.IUiAutomationConnection;
+import android.app.IUidFrozenStateChangedCallback;
import android.app.IUidObserver;
import android.app.IUserSwitchObserver;
import android.app.Notification;
@@ -877,4 +878,7 @@ interface IActivityManager {
/** Logs API state change to associate with an FGS, used for FGS Type Metrics */
void logFgsApiStateChanged(int apiType, int state, int appUid, int appPid);
+
+ void registerUidFrozenStateChangedCallback(in IUidFrozenStateChangedCallback callback);
+ void unregisterUidFrozenStateChangedCallback(in IUidFrozenStateChangedCallback callback);
}
diff --git a/core/java/android/app/IUidFrozenStateChangedCallback.aidl b/core/java/android/app/IUidFrozenStateChangedCallback.aidl
new file mode 100644
index 000000000000..d6d94da8c393
--- /dev/null
+++ b/core/java/android/app/IUidFrozenStateChangedCallback.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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 android.app;
+
+/** {@hide} */
+oneway interface IUidFrozenStateChangedCallback {
+ /**
+ * Report a new frozen state for the Uid list.
+ */
+ void onUidFrozenStateChanged(in int[] uids, in int[] frozenStates);
+}
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index ff1782456596..540342b03f1a 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -16,6 +16,11 @@
package android.app;
+import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE;
+import static android.Manifest.permission.READ_WALLPAPER_INTERNAL;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
+
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -28,6 +33,9 @@ import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.annotation.UiContext;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -84,6 +92,7 @@ import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -108,8 +117,26 @@ import java.util.concurrent.TimeUnit;
*/
@SystemService(Context.WALLPAPER_SERVICE)
public class WallpaperManager {
+
private static String TAG = "WallpaperManager";
private static final boolean DEBUG = false;
+
+ /**
+ * Trying to read the wallpaper file or bitmap in T will return
+ * the default wallpaper bitmap/file instead of throwing a SecurityException.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ static final long RETURN_DEFAULT_ON_SECURITY_EXCEPTION = 239784307L;
+
+ /**
+ * In U and later, attempting to read the wallpaper file or bitmap will throw an exception,
+ * (except with the READ_WALLPAPER_INTERNAL permission).
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ static final long THROW_ON_SECURITY_EXCEPTION = 237508058L;
+
private float mWallpaperXStep = -1;
private float mWallpaperYStep = -1;
private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
@@ -585,7 +612,8 @@ public class WallpaperManager {
}
}
synchronized (this) {
- if (mCachedWallpaper != null && mCachedWallpaper.isValid(userId, which)) {
+ if (mCachedWallpaper != null && mCachedWallpaper.isValid(userId, which) && context
+ .checkSelfPermission(READ_WALLPAPER_INTERNAL) == PERMISSION_GRANTED) {
return mCachedWallpaper.mCachedWallpaper;
}
mCachedWallpaper = null;
@@ -596,6 +624,19 @@ public class WallpaperManager {
} catch (OutOfMemoryError e) {
Log.w(TAG, "Out of memory loading the current wallpaper: " + e);
} catch (SecurityException e) {
+ /*
+ * Apps with target SDK <= S can still access the wallpaper through
+ * READ_EXTERNAL_STORAGE. In T however, app that previously had access to the
+ * wallpaper via READ_EXTERNAL_STORAGE will get a SecurityException here.
+ * Thus, in T specifically, return the default wallpaper instead of crashing.
+ */
+ if (CompatChanges.isChangeEnabled(RETURN_DEFAULT_ON_SECURITY_EXCEPTION)
+ && !CompatChanges.isChangeEnabled(THROW_ON_SECURITY_EXCEPTION)) {
+ Log.w(TAG, "No permission to access wallpaper, returning default"
+ + " wallpaper to avoid crashing legacy app.");
+ return getDefaultWallpaper(context, FLAG_SYSTEM);
+ }
+
if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O_MR1) {
Log.w(TAG, "No permission to access wallpaper, suppressing"
+ " exception to avoid crashing legacy app.");
@@ -808,6 +849,18 @@ public class WallpaperManager {
}
/**
+ * <strong> Important note: </strong>
+ * <ul>
+ * <li>Up to version S, this method requires the
+ * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li>
+ * <li>Starting in T, directly accessing the wallpaper is not possible anymore,
+ * instead the default system wallpaper is returned
+ * (some versions of T may throw a {@code SecurityException}).</li>
+ * <li>From version U, this method should not be used
+ * and will always throw a @code SecurityException}.</li>
+ * </ul>
+ * <br>
+ *
* Retrieve the current system wallpaper; if
* no wallpaper is set, the system built-in static wallpaper is returned.
* This is returned as an
@@ -821,14 +874,28 @@ public class WallpaperManager {
* @return Returns a Drawable object that will draw the system wallpaper,
* or {@code null} if no system wallpaper exists or if the calling application
* is not able to access the wallpaper.
+ *
+ * @throws SecurityException as described in the note
*/
- @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
@Nullable
+ @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL})
public Drawable getDrawable() {
return getDrawable(FLAG_SYSTEM);
}
/**
+ * <strong> Important note: </strong>
+ * <ul>
+ * <li>Up to version S, this method requires the
+ * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li>
+ * <li>Starting in T, directly accessing the wallpaper is not possible anymore,
+ * instead the default system wallpaper is returned
+ * (some versions of T may throw a {@code SecurityException}).</li>
+ * <li>From version U, this method should not be used
+ * and will always throw a @code SecurityException}.</li>
+ * </ul>
+ * <br>
+ *
* Retrieve the requested wallpaper; if
* no wallpaper is set, the requested built-in static wallpaper is returned.
* This is returned as an
@@ -844,9 +911,11 @@ public class WallpaperManager {
* @return Returns a Drawable object that will draw the requested wallpaper,
* or {@code null} if the requested wallpaper does not exist or if the calling application
* is not able to access the wallpaper.
+ *
+ * @throws SecurityException as described in the note
*/
- @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
@Nullable
+ @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL})
public Drawable getDrawable(@SetWallpaperFlags int which) {
final ColorManagementProxy cmProxy = getColorManagementProxy();
Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, which, cmProxy);
@@ -1069,6 +1138,18 @@ public class WallpaperManager {
}
/**
+ * <strong> Important note: </strong>
+ * <ul>
+ * <li>Up to version S, this method requires the
+ * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li>
+ * <li>Starting in T, directly accessing the wallpaper is not possible anymore,
+ * instead the default system wallpaper is returned
+ * (some versions of T may throw a {@code SecurityException}).</li>
+ * <li>From version U, this method should not be used
+ * and will always throw a @code SecurityException}.</li>
+ * </ul>
+ * <br>
+ *
* Retrieve the current system wallpaper; if there is no wallpaper set,
* a null pointer is returned. This is returned as an
* abstract Drawable that you can install in a View to display whatever
@@ -1076,13 +1157,28 @@ public class WallpaperManager {
*
* @return Returns a Drawable object that will draw the wallpaper or a
* null pointer if wallpaper is unset.
+ *
+ * @throws SecurityException as described in the note
*/
@Nullable
+ @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL})
public Drawable peekDrawable() {
return peekDrawable(FLAG_SYSTEM);
}
/**
+ * <strong> Important note: </strong>
+ * <ul>
+ * <li>Up to version S, this method requires the
+ * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li>
+ * <li>Starting in T, directly accessing the wallpaper is not possible anymore,
+ * instead the default system wallpaper is returned
+ * (some versions of T may throw a {@code SecurityException}).</li>
+ * <li>From version U, this method should not be used
+ * and will always throw a @code SecurityException}.</li>
+ * </ul>
+ * <br>
+ *
* Retrieve the requested wallpaper; if there is no wallpaper set,
* a null pointer is returned. This is returned as an
* abstract Drawable that you can install in a View to display whatever
@@ -1092,11 +1188,14 @@ public class WallpaperManager {
* IllegalArgumentException if an invalid wallpaper is requested.
* @return Returns a Drawable object that will draw the wallpaper or a null pointer if
* wallpaper is unset.
+ *
+ * @throws SecurityException as described in the note
*/
@Nullable
+ @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL})
public Drawable peekDrawable(@SetWallpaperFlags int which) {
final ColorManagementProxy cmProxy = getColorManagementProxy();
- Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, which, cmProxy);
+ Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, which, cmProxy);
if (bm != null) {
Drawable dr = new BitmapDrawable(mContext.getResources(), bm);
dr.setDither(false);
@@ -1106,6 +1205,18 @@ public class WallpaperManager {
}
/**
+ * <strong> Important note: </strong>
+ * <ul>
+ * <li>Up to version S, this method requires the
+ * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li>
+ * <li>Starting in T, directly accessing the wallpaper is not possible anymore,
+ * instead the default wallpaper is returned
+ * (some versions of T may throw a {@code SecurityException}).</li>
+ * <li>From version U, this method should not be used
+ * and will always throw a @code SecurityException}.</li>
+ * </ul>
+ * <br>
+ *
* Like {@link #getDrawable()}, but the returned Drawable has a number
* of limitations to reduce its overhead as much as possible. It will
* never scale the wallpaper (only centering it if the requested bounds
@@ -1117,14 +1228,28 @@ public class WallpaperManager {
* the same density as the screen (not in density compatibility mode).
*
* @return Returns a Drawable object that will draw the wallpaper.
+ *
+ * @throws SecurityException as described in the note
*/
- @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
@Nullable
+ @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL})
public Drawable getFastDrawable() {
return getFastDrawable(FLAG_SYSTEM);
}
/**
+ * <strong> Important note: </strong>
+ * <ul>
+ * <li>Up to version S, this method requires the
+ * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li>
+ * <li>Starting in T, directly accessing the wallpaper is not possible anymore,
+ * instead the default system wallpaper is returned
+ * (some versions of T may throw a {@code SecurityException}).</li>
+ * <li>From version U, this method should not be used
+ * and will always throw a @code SecurityException}.</li>
+ * </ul>
+ * <br>
+ *
* Like {@link #getDrawable(int)}, but the returned Drawable has a number
* of limitations to reduce its overhead as much as possible. It will
* never scale the wallpaper (only centering it if the requested bounds
@@ -1138,9 +1263,11 @@ public class WallpaperManager {
* @param which The {@code FLAG_*} identifier of a valid wallpaper type. Throws
* IllegalArgumentException if an invalid wallpaper is requested.
* @return Returns a Drawable object that will draw the wallpaper.
+ *
+ * @throws SecurityException as described in the note
*/
- @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
@Nullable
+ @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL})
public Drawable getFastDrawable(@SetWallpaperFlags int which) {
final ColorManagementProxy cmProxy = getColorManagementProxy();
Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, which, cmProxy);
@@ -1151,19 +1278,45 @@ public class WallpaperManager {
}
/**
+ * <strong> Important note: </strong>
+ * <ul>
+ * <li>Up to version S, this method requires the
+ * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li>
+ * <li>Starting in T, directly accessing the wallpaper is not possible anymore,
+ * instead the default system wallpaper is returned
+ * (some versions of T may throw a {@code SecurityException}).</li>
+ * <li>From version U, this method should not be used
+ * and will always throw a @code SecurityException}.</li>
+ * </ul>
+ * <br>
+ *
* Like {@link #getFastDrawable()}, but if there is no wallpaper set,
* a null pointer is returned.
*
* @return Returns an optimized Drawable object that will draw the
* wallpaper or a null pointer if these is none.
+ *
+ * @throws SecurityException as described in the note
*/
- @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
@Nullable
+ @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL})
public Drawable peekFastDrawable() {
return peekFastDrawable(FLAG_SYSTEM);
}
/**
+ * <strong> Important note: </strong>
+ * <ul>
+ * <li>Up to version S, this method requires the
+ * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li>
+ * <li>Starting in T, directly accessing the wallpaper is not possible anymore,
+ * instead the default system wallpaper is returned
+ * (some versions of T may throw a {@code SecurityException}).</li>
+ * <li>From version U, this method should not be used
+ * and will always throw a @code SecurityException}.</li>
+ * </ul>
+ * <br>
+ *
* Like {@link #getFastDrawable()}, but if there is no wallpaper set,
* a null pointer is returned.
*
@@ -1171,12 +1324,14 @@ public class WallpaperManager {
* IllegalArgumentException if an invalid wallpaper is requested.
* @return Returns an optimized Drawable object that will draw the
* wallpaper or a null pointer if these is none.
+ *
+ * @throws SecurityException as described in the note
*/
- @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
@Nullable
+ @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL})
public Drawable peekFastDrawable(@SetWallpaperFlags int which) {
final ColorManagementProxy cmProxy = getColorManagementProxy();
- Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, which, cmProxy);
+ Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, which, cmProxy);
if (bm != null) {
return new FastBitmapDrawable(bm);
}
@@ -1194,7 +1349,6 @@ public class WallpaperManager {
* @hide
*/
@TestApi
- @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
public boolean wallpaperSupportsWcg(int which) {
if (!shouldEnableWideColorGamut()) {
return false;
@@ -1295,6 +1449,18 @@ public class WallpaperManager {
}
/**
+ * <strong> Important note: </strong>
+ * <ul>
+ * <li>Up to version S, this method requires the
+ * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li>
+ * <li>Starting in T, directly accessing the wallpaper is not possible anymore,
+ * instead the default system wallpaper is returned
+ * (some versions of T may throw a {@code SecurityException}).</li>
+ * <li>From version U, this method should not be used
+ * and will always throw a @code SecurityException}.</li>
+ * </ul>
+ * <br>
+ *
* Get an open, readable file descriptor to the given wallpaper image file.
* The caller is responsible for closing the file descriptor when done ingesting the file.
*
@@ -1305,14 +1471,17 @@ public class WallpaperManager {
* @param which The wallpaper whose image file is to be retrieved. Must be a single
* defined kind of wallpaper, either {@link #FLAG_SYSTEM} or
* {@link #FLAG_LOCK}.
- * @return An open, readable file desriptor to the requested wallpaper image file;
+ * @return An open, readable file descriptor to the requested wallpaper image file;
* or {@code null} if no such wallpaper is configured or if the calling app does
* not have permission to read the current wallpaper.
*
* @see #FLAG_LOCK
* @see #FLAG_SYSTEM
+ *
+ * @throws SecurityException as described in the note
*/
- @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
+ @Nullable
+ @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL})
public ParcelFileDescriptor getWallpaperFile(@SetWallpaperFlags int which) {
return getWallpaperFile(which, mContext.getUserId());
}
@@ -1475,13 +1644,18 @@ public class WallpaperManager {
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (SecurityException e) {
+ if (CompatChanges.isChangeEnabled(RETURN_DEFAULT_ON_SECURITY_EXCEPTION)
+ && !CompatChanges.isChangeEnabled(THROW_ON_SECURITY_EXCEPTION)) {
+ Log.w(TAG, "No permission to access wallpaper, returning default"
+ + " wallpaper file to avoid crashing legacy app.");
+ return getDefaultSystemWallpaperFile();
+ }
if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O_MR1) {
Log.w(TAG, "No permission to access wallpaper, suppressing"
+ " exception to avoid crashing legacy app.");
return null;
- } else {
- throw e;
}
+ throw e;
}
}
}
@@ -2586,6 +2760,24 @@ public class WallpaperManager {
return null;
}
+ /**
+ * util used in T to return a default system wallpaper file
+ * when third party apps attempt to read the wallpaper with {@link #getWallpaperFile}
+ */
+ private static ParcelFileDescriptor getDefaultSystemWallpaperFile() {
+ for (String path: getDefaultSystemWallpaperPaths()) {
+ File file = new File(path);
+ if (file.exists()) {
+ try {
+ return ParcelFileDescriptor.open(file, MODE_READ_ONLY);
+ } catch (FileNotFoundException e) {
+ // continue; default wallpaper file not found on this path
+ }
+ }
+ }
+ return null;
+ }
+
private static InputStream getWallpaperInputStream(String path) {
if (!TextUtils.isEmpty(path)) {
final File file = new File(path);
@@ -2600,6 +2792,14 @@ public class WallpaperManager {
return null;
}
+ /**
+ * @return a list of paths to the system default wallpapers, in order of priority:
+ * if the file exists for the first path of this list, the first path should be used.
+ */
+ private static List<String> getDefaultSystemWallpaperPaths() {
+ return List.of(SystemProperties.get(PROP_WALLPAPER), getCmfWallpaperPath());
+ }
+
private static String getCmfWallpaperPath() {
return Environment.getProductDirectory() + WALLPAPER_CMF_PATH + "default_wallpaper_"
+ VALUE_CMF_COLOR;
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index c95d081f5f7d..dfb9cf65a4b9 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -2550,41 +2550,15 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <ul>
* <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_UNSPECIFIED UNSPECIFIED}</li>
* <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_SRGB SRGB}</li>
- * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_LINEAR_SRGB LINEAR_SRGB}</li>
- * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_EXTENDED_SRGB EXTENDED_SRGB}</li>
- * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_LINEAR_EXTENDED_SRGB LINEAR_EXTENDED_SRGB}</li>
- * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_BT709 BT709}</li>
- * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_BT2020 BT2020}</li>
- * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_DCI_P3 DCI_P3}</li>
* <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_DISPLAY_P3 DISPLAY_P3}</li>
- * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_NTSC_1953 NTSC_1953}</li>
- * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_SMPTE_C SMPTE_C}</li>
- * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_ADOBE_RGB ADOBE_RGB}</li>
- * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_PRO_PHOTO_RGB PRO_PHOTO_RGB}</li>
- * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_ACES ACES}</li>
- * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_ACESCG ACESCG}</li>
- * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_CIE_XYZ CIE_XYZ}</li>
- * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_CIE_LAB CIE_LAB}</li>
+ * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_BT2020_HLG BT2020_HLG}</li>
* </ul>
*
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
* @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_UNSPECIFIED
* @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_SRGB
- * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_LINEAR_SRGB
- * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_EXTENDED_SRGB
- * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_LINEAR_EXTENDED_SRGB
- * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_BT709
- * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_BT2020
- * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_DCI_P3
* @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_DISPLAY_P3
- * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_NTSC_1953
- * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_SMPTE_C
- * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_ADOBE_RGB
- * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_PRO_PHOTO_RGB
- * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_ACES
- * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_ACESCG
- * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_CIE_XYZ
- * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_CIE_LAB
+ * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_BT2020_HLG
* @hide
*/
public static final Key<long[]> REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP =
diff --git a/core/java/android/hardware/input/VirtualKeyboardConfig.java b/core/java/android/hardware/input/VirtualKeyboardConfig.java
index d788df49bfc5..6d03065214ca 100644
--- a/core/java/android/hardware/input/VirtualKeyboardConfig.java
+++ b/core/java/android/hardware/input/VirtualKeyboardConfig.java
@@ -110,10 +110,7 @@ public final class VirtualKeyboardConfig extends VirtualInputDeviceConfig implem
/**
* Sets the preferred input language of the virtual keyboard using an IETF
- * <a href="https://tools.ietf.org/html/bcp47">BCP-47</a>
- * conformant tag. See {@code keyboardLocale} attribute in
- * frameworks/base/packages/InputDevices/res/xml/keyboard_layouts.xml for a list of
- * supported language tags.
+ * <a href="https://tools.ietf.org/html/bcp47">BCP-47</a> conformant tag.
*
* The passed in {@code languageTag} will be canonized using {@link
* ULocale} and used by the system as a hint to configure the keyboard layout.
@@ -135,7 +132,7 @@ public final class VirtualKeyboardConfig extends VirtualInputDeviceConfig implem
public Builder setLanguageTag(@NonNull String languageTag) {
Objects.requireNonNull(languageTag, "languageTag cannot be null");
ULocale locale = ULocale.forLanguageTag(languageTag);
- if (locale.getLanguage().isEmpty() || locale.getCountry().isEmpty()) {
+ if (locale.getLanguage().isEmpty()) {
throw new IllegalArgumentException("The language tag is not valid.");
}
mLanguageTag = ULocale.createCanonical(locale).toLanguageTag();
@@ -144,8 +141,8 @@ public final class VirtualKeyboardConfig extends VirtualInputDeviceConfig implem
/**
* Sets the preferred layout type of the virtual keyboard. See {@code keyboardLayoutType}
- * attribute in frameworks/base/packages/InputDevices/res/xml/keyboard_layouts.xml for a
- * list of supported layout types.
+ * attribute in frameworks/base/core/res/res/values/attrs.xml for a list of supported
+ * layout types.
*
* Note that the preferred layout is not guaranteed. If the specified layout type is
* well-formed but not supported, the keyboard will be using English US QWERTY layout.
diff --git a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
index 268db1e61368..47b8550612b9 100644
--- a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
+++ b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
@@ -25,6 +25,8 @@ import android.annotation.Nullable;
import android.graphics.RectF;
import android.os.Bundle;
import android.os.CancellationSignal;
+import android.os.CancellationSignalBeamer;
+import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.view.KeyEvent;
@@ -59,6 +61,7 @@ final class IRemoteInputConnectionInvoker {
@NonNull
private final IRemoteInputConnection mConnection;
private final int mSessionId;
+ private CancellationSignalBeamer.Sender mBeamer;
private IRemoteInputConnectionInvoker(@NonNull IRemoteInputConnection inputConnection,
int sessionId) {
@@ -681,7 +684,7 @@ final class IRemoteInputConnectionInvoker {
* InputConnectionCommandHeader, ParcelableHandwritingGesture, ResultReceiver)}.
*/
@AnyThread
- public void performHandwritingGesture(@NonNull ParcelableHandwritingGesture gesture,
+ public void performHandwritingGesture(@NonNull HandwritingGesture gesture,
@Nullable @CallbackExecutor Executor executor, @Nullable IntConsumer consumer) {
ResultReceiver resultReceiver = null;
if (consumer != null) {
@@ -689,7 +692,11 @@ final class IRemoteInputConnectionInvoker {
resultReceiver = new IntResultReceiver(executor, consumer);
}
try {
- mConnection.performHandwritingGesture(createHeader(), gesture, resultReceiver);
+ try (var ignored = getCancellationSignalBeamer().beamScopeIfNeeded(gesture)) {
+ mConnection.performHandwritingGesture(createHeader(),
+ ParcelableHandwritingGesture.of(gesture),
+ resultReceiver);
+ }
} catch (RemoteException e) {
if (consumer != null && executor != null) {
executor.execute(() -> consumer.accept(
@@ -700,25 +707,59 @@ final class IRemoteInputConnectionInvoker {
/**
* Invokes one of {@link IRemoteInputConnection#previewHandwritingGesture(
- * InputConnectionCommandHeader, ParcelableHandwritingGesture, CancellationSignal)}
+ * InputConnectionCommandHeader, HandwritingGesture, IBinder)}
*/
@AnyThread
public boolean previewHandwritingGesture(
- @NonNull ParcelableHandwritingGesture gesture,
+ @NonNull HandwritingGesture gesture,
@Nullable CancellationSignal cancellationSignal) {
- if (cancellationSignal != null && cancellationSignal.isCanceled()) {
- return false; // cancelled.
- }
-
- // TODO(b/254727073): Implement CancellationSignal
try {
- mConnection.previewHandwritingGesture(createHeader(), gesture, null);
+ try (var csToken = beam(cancellationSignal)) {
+ mConnection.previewHandwritingGesture(createHeader(),
+ ParcelableHandwritingGesture.of(gesture),
+ csToken);
+ }
return true;
} catch (RemoteException e) {
return false;
}
}
+ @Nullable
+ CancellationSignalBeamer.Sender.CloseableToken beam(CancellationSignal cs) {
+ if (cs == null) {
+ return null;
+ }
+ return getCancellationSignalBeamer().beam(cs);
+ }
+
+ private CancellationSignalBeamer.Sender getCancellationSignalBeamer() {
+ if (mBeamer != null) {
+ return mBeamer;
+ }
+ mBeamer = new CancellationSignalBeamer.Sender() {
+ @Override
+ public void onCancel(IBinder token) {
+ try {
+ mConnection.cancelCancellationSignal(token);
+ } catch (RemoteException e) {
+ // Remote process likely died, ignore.
+ }
+ }
+
+ @Override
+ public void onForget(IBinder token) {
+ try {
+ mConnection.forgetCancellationSignal(token);
+ } catch (RemoteException e) {
+ // Remote process likely died, ignore.
+ }
+ }
+ };
+
+ return mBeamer;
+ }
+
/**
* Invokes {@link IRemoteInputConnection#requestCursorUpdates(InputConnectionCommandHeader, int,
* int, AndroidFuture)}.
diff --git a/core/java/android/inputmethodservice/RemoteInputConnection.java b/core/java/android/inputmethodservice/RemoteInputConnection.java
index ec26ace79cd8..56e69bf4170c 100644
--- a/core/java/android/inputmethodservice/RemoteInputConnection.java
+++ b/core/java/android/inputmethodservice/RemoteInputConnection.java
@@ -34,7 +34,6 @@ import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.HandwritingGesture;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputContentInfo;
-import android.view.inputmethod.ParcelableHandwritingGesture;
import android.view.inputmethod.PreviewableHandwritingGesture;
import android.view.inputmethod.SurroundingText;
import android.view.inputmethod.TextAttribute;
@@ -424,16 +423,18 @@ final class RemoteInputConnection implements InputConnection {
public void performHandwritingGesture(
@NonNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor,
@Nullable IntConsumer consumer) {
- mInvoker.performHandwritingGesture(ParcelableHandwritingGesture.of(gesture), executor,
- consumer);
+ mInvoker.performHandwritingGesture(gesture, executor, consumer);
}
@AnyThread
public boolean previewHandwritingGesture(
@NonNull PreviewableHandwritingGesture gesture,
@Nullable CancellationSignal cancellationSignal) {
- return mInvoker.previewHandwritingGesture(ParcelableHandwritingGesture.of(gesture),
- cancellationSignal);
+ if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+ return false; // cancelled.
+ }
+
+ return mInvoker.previewHandwritingGesture(gesture, cancellationSignal);
}
@AnyThread
diff --git a/core/java/android/os/CancellationSignalBeamer.java b/core/java/android/os/CancellationSignalBeamer.java
index afb5ff7bf626..b4247831ddc5 100644
--- a/core/java/android/os/CancellationSignalBeamer.java
+++ b/core/java/android/os/CancellationSignalBeamer.java
@@ -19,9 +19,13 @@ package android.os;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.system.SystemCleaner;
+import android.util.Pair;
+import android.view.inputmethod.CancellableHandwritingGesture;
+import android.view.inputmethod.HandwritingGesture;
import java.lang.ref.Cleaner;
import java.lang.ref.Reference;
+import java.util.ArrayList;
import java.util.HashMap;
/**
@@ -143,6 +147,58 @@ public class CancellationSignalBeamer {
*/
public abstract void onForget(IBinder token);
+ private static final ThreadLocal<Pair<Sender, ArrayList<CloseableToken>>> sScope =
+ new ThreadLocal<>();
+
+ /**
+ * Beams a {@link CancellationSignal} through an existing Binder interface.
+ * @param gesture {@link HandwritingGesture} that supports
+ * {@link CancellableHandwritingGesture cancellation} requesting cancellation token.
+ * @return {@link IBinder} token. MUST be {@link MustClose#close}d <em>after</em>
+ * the binder call transporting it to the remote process, best with
+ * try-with-resources. {@code null} if {@code cs} was {@code null} or if
+ * {@link HandwritingGesture} isn't {@link CancellableHandwritingGesture cancellable}.
+ */
+ public MustClose beamScopeIfNeeded(HandwritingGesture gesture) {
+ if (!(gesture instanceof CancellableHandwritingGesture)) {
+ return null;
+ }
+ sScope.set(Pair.create(this, new ArrayList<>()));
+ return () -> {
+ var tokens = sScope.get().second;
+ sScope.remove();
+ for (int i = tokens.size() - 1; i >= 0; i--) {
+ if (tokens.get(i) != null) {
+ tokens.get(i).close();
+ }
+ }
+ };
+ }
+
+ /**
+ * An {@link AutoCloseable} interface with {@link AutoCloseable#close()} callback.
+ */
+ public interface MustClose extends AutoCloseable {
+ @Override
+ void close();
+ }
+
+ /**
+ * Beams a {@link CancellationSignal} token from existing scope created by previous call to
+ * {@link #beamScopeIfNeeded()}
+ * @param cs {@link CancellationSignal} for which token should be returned.
+ * @return {@link IBinder} token.
+ */
+ public static IBinder beamFromScope(CancellationSignal cs) {
+ var state = sScope.get();
+ if (state != null) {
+ var token = state.first.beam(cs);
+ state.second.add(token);
+ return token;
+ }
+ return null;
+ }
+
private static class Token extends Binder implements CloseableToken, Runnable {
private final Sender mSender;
@@ -200,7 +256,7 @@ public class CancellationSignalBeamer {
*
* MUST be closed <em>after</em> it is sent over binder, ideally through try-with-resources.
*/
- public interface CloseableToken extends IBinder, AutoCloseable {
+ public interface CloseableToken extends IBinder, MustClose {
@Override
void close(); // No throws
}
@@ -215,10 +271,10 @@ public class CancellationSignalBeamer {
* Constructs a new {@code Receiver}.
*
* @param cancelOnSenderDeath if true, {@link CancellationSignal}s obtained from
- * {@link #unbeam} are automatically {@link #cancel}led if the sender token
- * {@link Binder#linkToDeath dies}; otherwise they are simnply dropped. Note: if the
- * sending process drops all references to the {@link CancellationSignal} before
- * process death, the cancellation is not guaranteed.
+ * {@link #unbeam} are automatically {@link #cancel}led if the sender token
+ * {@link Binder#linkToDeath dies}; otherwise they are simnply dropped. Note: if the
+ * sending process drops all references to the {@link CancellationSignal} before
+ * process death, the cancellation is not guaranteed.
*/
public Receiver(boolean cancelOnSenderDeath) {
mCancelOnSenderDeath = cancelOnSenderDeath;
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 259012f5eb30..8d95c0251203 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -1458,7 +1458,7 @@ public abstract class WallpaperService extends Service {
com.android.internal.R.dimen.config_wallpaperDimAmount);
mWallpaperDimAmount = mDefaultDimAmount;
mPreviousWallpaperDimAmount = mWallpaperDimAmount;
- mDisplayState = mDisplay.getState();
+ mDisplayState = mDisplay.getCommittedState();
if (DEBUG) Log.v(TAG, "onCreate(): " + this);
Trace.beginSection("WPMS.Engine.onCreate");
@@ -1548,7 +1548,8 @@ public abstract class WallpaperService extends Service {
return;
}
if (!mDestroyed) {
- mDisplayState = mDisplay == null ? Display.STATE_UNKNOWN : mDisplay.getState();
+ mDisplayState = mDisplay == null ? Display.STATE_UNKNOWN :
+ mDisplay.getCommittedState();
boolean visible = mVisible && mDisplayState != Display.STATE_OFF;
if (DEBUG) {
Log.v(
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 0deaa7636310..ab0c4df9efc1 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -16,6 +16,7 @@
package android.view.autofill;
+import static android.Manifest.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS;
import static android.service.autofill.FillRequest.FLAG_IME_SHOWING;
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
@@ -34,6 +35,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresFeature;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
@@ -2223,8 +2225,14 @@ public final class AutofillManager {
* @param executor specifies the thread upon which the callbacks will be invoked.
* @param callback which handles autofill request to provide client's suggestions.
*/
+ @RequiresPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS)
public void setAutofillRequestCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull AutofillRequestCallback callback) {
+ if (mContext.checkSelfPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires USE_APP_AUTOFILL permission!");
+ }
+
synchronized (mLock) {
mRequestCallbackExecutor = executor;
mAutofillRequestCallback = callback;
diff --git a/core/java/android/view/inputmethod/CancellableHandwritingGesture.java b/core/java/android/view/inputmethod/CancellableHandwritingGesture.java
new file mode 100644
index 000000000000..3e7974b0a6b8
--- /dev/null
+++ b/core/java/android/view/inputmethod/CancellableHandwritingGesture.java
@@ -0,0 +1,53 @@
+/*
+ * 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 android.view.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.CancellationSignal;
+import android.os.CancellationSignalBeamer;
+import android.os.IBinder;
+
+/**
+ * A {@link HandwritingGesture} that can be {@link CancellationSignal#cancel() cancelled}.
+ * @hide
+ */
+@TestApi
+public abstract class CancellableHandwritingGesture extends HandwritingGesture {
+ CancellationSignal mCancellationSignal;
+
+ IBinder mCancellationSignalToken;
+
+ /**
+ * Set {@link CancellationSignal} for testing only.
+ * @hide
+ */
+ @TestApi
+ public void setCancellationSignal(@NonNull CancellationSignal cancellationSignal) {
+ mCancellationSignal = cancellationSignal;
+ }
+
+ CancellationSignal getCancellationSignal() {
+ return mCancellationSignal;
+ }
+
+ void unbeamCancellationSignal(CancellationSignalBeamer.Receiver receiver) {
+ mCancellationSignal = receiver.unbeam(mCancellationSignalToken);
+ mCancellationSignalToken = null;
+ }
+
+}
diff --git a/core/java/android/view/inputmethod/InsertModeGesture.java b/core/java/android/view/inputmethod/InsertModeGesture.java
index 6b9d7fbbc65b..1fc56dea8f65 100644
--- a/core/java/android/view/inputmethod/InsertModeGesture.java
+++ b/core/java/android/view/inputmethod/InsertModeGesture.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.graphics.PointF;
import android.os.CancellationSignal;
+import android.os.CancellationSignalBeamer;
import android.os.Parcel;
import android.os.Parcelable;
import android.widget.TextView;
@@ -39,10 +40,9 @@ import java.util.Objects;
* {@link CancellationSignal#setOnCancelListener(CancellationSignal.OnCancelListener)} obtained from
* {@link #getCancellationSignal()}.
*/
-public final class InsertModeGesture extends HandwritingGesture implements Parcelable {
+public final class InsertModeGesture extends CancellableHandwritingGesture implements Parcelable {
private PointF mPoint;
- private CancellationSignal mCancellationSignal;
private InsertModeGesture(PointF point, String fallbackText,
CancellationSignal cancellationSignal) {
@@ -56,6 +56,7 @@ public final class InsertModeGesture extends HandwritingGesture implements Parce
mType = GESTURE_TYPE_INSERT_MODE;
mFallbackText = source.readString8();
mPoint = source.readTypedObject(PointF.CREATOR);
+ mCancellationSignalToken = source.readStrongBinder();
}
/**
@@ -64,6 +65,7 @@ public final class InsertModeGesture extends HandwritingGesture implements Parce
* {@link CancellationSignal#cancel()} and toolkit can receive cancel using
* {@link CancellationSignal#setOnCancelListener(CancellationSignal.OnCancelListener)}.
*/
+ @Override
@NonNull
public CancellationSignal getCancellationSignal() {
return mCancellationSignal;
@@ -183,5 +185,6 @@ public final class InsertModeGesture extends HandwritingGesture implements Parce
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString8(mFallbackText);
dest.writeTypedObject(mPoint, flags);
+ dest.writeStrongBinder(CancellationSignalBeamer.Sender.beamFromScope(mCancellationSignal));
}
}
diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
index 6f8b422da218..eb91d08dc278 100644
--- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
@@ -31,8 +31,9 @@ import android.annotation.Nullable;
import android.graphics.RectF;
import android.os.Bundle;
import android.os.CancellationSignal;
+import android.os.CancellationSignalBeamer;
import android.os.Handler;
-import android.os.ICancellationSignal;
+import android.os.IBinder;
import android.os.Looper;
import android.os.ResultReceiver;
import android.os.Trace;
@@ -179,6 +180,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
private final AtomicBoolean mHasPendingImmediateCursorAnchorInfoUpdate =
new AtomicBoolean(false);
+ private CancellationSignalBeamer.Receiver mBeamer;
+
RemoteInputConnectionImpl(@NonNull Looper looper,
@NonNull InputConnection inputConnection,
@NonNull InputMethodManager inputMethodManager, @Nullable View servedView) {
@@ -422,6 +425,22 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
}
@Override
+ public void cancelCancellationSignal(IBinder token) {
+ if (mBeamer == null) {
+ return;
+ }
+ mBeamer.cancel(token);
+ }
+
+ @Override
+ public void forgetCancellationSignal(IBinder token) {
+ if (mBeamer == null) {
+ return;
+ }
+ mBeamer.forget(token);
+ }
+
+ @Override
public String toString() {
return "RemoteInputConnectionImpl{"
+ "connection=" + getInputConnection()
@@ -988,6 +1007,22 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void performHandwritingGesture(
InputConnectionCommandHeader header, ParcelableHandwritingGesture gestureContainer,
ResultReceiver resultReceiver) {
+ final HandwritingGesture gesture = gestureContainer.get();
+ if (gesture instanceof CancellableHandwritingGesture) {
+ // For cancellable gestures, unbeam and save the CancellationSignal.
+ CancellableHandwritingGesture cancellableGesture =
+ (CancellableHandwritingGesture) gesture;
+ cancellableGesture.unbeamCancellationSignal(getCancellationSignalBeamer());
+ if (cancellableGesture.getCancellationSignal() != null
+ && cancellableGesture.getCancellationSignal().isCanceled()) {
+ // Send result for canceled operations.
+ if (resultReceiver != null) {
+ resultReceiver.send(
+ InputConnection.HANDWRITING_GESTURE_RESULT_CANCELLED, null);
+ }
+ return;
+ }
+ }
dispatchWithTracing("performHandwritingGesture", () -> {
if (header.mSessionId != mCurrentSessionId.get()) {
if (resultReceiver != null) {
@@ -1009,7 +1044,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
// TODO(210039666): implement Cleaner to return HANDWRITING_GESTURE_RESULT_UNKNOWN if
// editor doesn't return any type.
ic.performHandwritingGesture(
- gestureContainer.get(),
+ gesture,
resultReceiver != null ? Runnable::run : null,
resultReceiver != null
? (resultCode) -> resultReceiver.send(resultCode, null /* resultData */)
@@ -1021,10 +1056,11 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void previewHandwritingGesture(
InputConnectionCommandHeader header, ParcelableHandwritingGesture gestureContainer,
- ICancellationSignal transport) {
+ IBinder cancellationSignalToken) {
+ final CancellationSignal cancellationSignal =
+ cancellationSignalToken != null
+ ? getCancellationSignalBeamer().unbeam(cancellationSignalToken) : null;
- // TODO(b/254727073): Implement CancellationSignal receiver
- final CancellationSignal cancellationSignal = CancellationSignal.fromTransport(transport);
// Previews always use PreviewableHandwritingGesture but if incorrectly wrong class is
// passed, ClassCastException will be sent back to caller.
final PreviewableHandwritingGesture gesture =
@@ -1045,6 +1081,14 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
});
}
+ private CancellationSignalBeamer.Receiver getCancellationSignalBeamer() {
+ if (mBeamer != null) {
+ return mBeamer;
+ }
+ mBeamer = new CancellationSignalBeamer.Receiver(true /* cancelOnSenderDeath */);
+ return mBeamer;
+ }
+
@Dispatching(cancellable = true)
@Override
public void requestCursorUpdates(InputConnectionCommandHeader header, int cursorUpdateMode,
diff --git a/core/java/android/window/ITaskOrganizerController.aidl b/core/java/android/window/ITaskOrganizerController.aidl
index 0032b9ce0512..e10f7c838c74 100644
--- a/core/java/android/window/ITaskOrganizerController.aidl
+++ b/core/java/android/window/ITaskOrganizerController.aidl
@@ -73,11 +73,17 @@ interface ITaskOrganizerController {
/**
* Controls whether ignore orientation request logic in {@link
- * com.android.server.wm.DisplayArea} is disabled at runtime.
+ * com.android.server.wm.DisplayArea} is disabled at runtime and how to optionally map some
+ * requested orientations to others.
*
* @param isDisabled when {@code true}, the system always ignores the value of {@link
* com.android.server.wm.DisplayArea#getIgnoreOrientationRequest} and app
* requested orientation is respected.
+ * @param fromOrientations The orientations we want to map to the correspondent orientations
+ * in toOrientation.
+ * @param toOrientations The orientations we map to the ones in fromOrientations at the same
+ * index
*/
- void setIsIgnoreOrientationRequestDisabled(boolean isDisabled);
+ void setOrientationRequestPolicy(boolean isIgnoreOrientationRequestDisabled,
+ in int[] fromOrientations, in int[] toOrientations);
}
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index d4728c1187d7..2913faf9d74d 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -267,17 +267,24 @@ public class TaskOrganizer extends WindowOrganizer {
/**
* Controls whether ignore orientation request logic in {@link
- * com.android.server.wm.DisplayArea} is disabled at runtime.
+ * com.android.server.wm.DisplayArea} is disabled at runtime and how to optionally map some
+ * requested orientation to others.
*
- * @param isDisabled when {@code true}, the system always ignores the value of {@link
- * com.android.server.wm.DisplayArea#getIgnoreOrientationRequest} and app
- * requested orientation is respected.
+ * @param isIgnoreOrientationRequestDisabled when {@code true}, the system always ignores the
+ * value of {@link com.android.server.wm.DisplayArea#getIgnoreOrientationRequest}
+ * and app requested orientation is respected.
+ * @param fromOrientations The orientations we want to map to the correspondent orientations
+ * in toOrientation.
+ * @param toOrientations The orientations we map to the ones in fromOrientations at the same
+ * index
* @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
- public void setIsIgnoreOrientationRequestDisabled(boolean isDisabled) {
+ public void setOrientationRequestPolicy(boolean isIgnoreOrientationRequestDisabled,
+ @Nullable int[] fromOrientations, @Nullable int[] toOrientations) {
try {
- mTaskOrganizerController.setIsIgnoreOrientationRequestDisabled(isDisabled);
+ mTaskOrganizerController.setOrientationRequestPolicy(isIgnoreOrientationRequestDisabled,
+ fromOrientations, toOrientations);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl b/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl
index b375936860a8..baaf99a36d4d 100644
--- a/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl
+++ b/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl
@@ -18,6 +18,7 @@ package com.android.internal.inputmethod;
import android.graphics.RectF;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.ICancellationSignal;
import android.os.ResultReceiver;
import android.view.KeyEvent;
@@ -94,7 +95,7 @@ import com.android.internal.inputmethod.InputConnectionCommandHeader;
in ParcelableHandwritingGesture gesture, in ResultReceiver resultReceiver);
void previewHandwritingGesture(in InputConnectionCommandHeader header,
- in ParcelableHandwritingGesture gesture, in ICancellationSignal transport);
+ in ParcelableHandwritingGesture gesture, in IBinder cancellationSignal);
void setComposingRegion(in InputConnectionCommandHeader header, int start, int end);
@@ -124,4 +125,8 @@ import com.android.internal.inputmethod.InputConnectionCommandHeader;
void replaceText(in InputConnectionCommandHeader header, int start, int end, CharSequence text,
int newCursorPosition,in TextAttribute textAttribute);
+
+ void cancelCancellationSignal(in IBinder token);
+ void forgetCancellationSignal(in IBinder token);
+
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 06e91c3bebfa..092f6e50aea1 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4038,8 +4038,7 @@
android:description="@string/permdesc_setWallpaperHints"
android:protectionLevel="normal" />
- <!-- Allow the app to read the system wallpaper image without
- holding the READ_EXTERNAL_STORAGE permission.
+ <!-- Allow the app to read the system and lock wallpaper images.
<p>Not for use by third-party applications.
@hide
@SystemApi
@@ -7436,6 +7435,13 @@
<permission android:name="android.permission.EXECUTE_APP_ACTION"
android:protectionLevel="internal|role" />
+ <!-- Allows an application to display its suggestions using the autofill framework.
+ <p>For now, this permission is only granted to the Browser application.
+ <p>Protection level: internal|role
+ -->
+ <permission android:name="android.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS"
+ android:protectionLevel="internal|role" />
+
<!-- @SystemApi Allows an application to create virtual devices in VirtualDeviceManager.
@hide -->
<permission android:name="android.permission.CREATE_VIRTUAL_DEVICE"
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index d868a0a32a54..696c0ed0530a 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -269,18 +269,6 @@
<public name="system_palette_key_color_tertiary_dark"/>
<public name="system_palette_key_color_neutral_dark"/>
<public name="system_palette_key_color_neutral_variant_dark"/>
- <public name="system_primary_fixed" />
- <public name="system_primary_fixed_dim" />
- <public name="system_on_primary_fixed" />
- <public name="system_on_primary_fixed_variant" />
- <public name="system_secondary_fixed" />
- <public name="system_secondary_fixed_dim" />
- <public name="system_on_secondary_fixed" />
- <public name="system_on_secondary_fixed_variant" />
- <public name="system_tertiary_fixed" />
- <public name="system_tertiary_fixed_dim" />
- <public name="system_on_tertiary_fixed" />
- <public name="system_on_tertiary_fixed_variant" />
<public name="system_outline_variant_light" />
<public name="system_outline_variant_dark" />
</staging-public-group>
diff --git a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
index 9b1f0cd9e8da..9a202ae4d176 100644
--- a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
@@ -112,7 +112,7 @@ public class FaceManagerTest {
mCaptor.getValue().onAllAuthenticatorsRegistered(mProps);
List<FaceSensorPropertiesInternal> actual = mFaceManager.getSensorPropertiesInternal();
- assertThat(actual).isEqualTo(mProps);
+ assertThat(actual).containsExactlyElementsIn(mProps);
verify(mService, never()).getSensorPropertiesInternal(any());
}
diff --git a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
index f31903a73111..5058065710be 100644
--- a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
@@ -113,7 +113,7 @@ public class FingerprintManagerTest {
List<FingerprintSensorPropertiesInternal> actual =
mFingerprintManager.getSensorPropertiesInternal();
- assertThat(actual).isEqualTo(mProps);
+ assertThat(actual).containsExactlyElementsIn(mProps);
verify(mService, never()).getSensorPropertiesInternal(any());
}
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index fe639ffb1cc6..922dbb5fef38 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -51,6 +51,7 @@
<permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
<permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
<permission name="android.permission.READ_PRECISE_PHONE_STATE"/>
+ <permission name="android.permission.READ_WALLPAPER_INTERNAL"/>
<permission name="android.permission.REAL_GET_TASKS"/>
<permission name="android.permission.REQUEST_NETWORK_SCORES"/>
<permission name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE"/>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index 22b841a338c1..913239f74bf2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -75,7 +75,7 @@ class BackAnimationRunner {
};
mWaitingAnimation = false;
try {
- mRunner.onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers,
+ getRunner().onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers,
nonApps, callback);
} catch (RemoteException e) {
Log.w(TAG, "Failed call onAnimationStart", e);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING
new file mode 100644
index 000000000000..837d5ff3b073
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING
@@ -0,0 +1,32 @@
+{
+ "presubmit": [
+ {
+ "name": "WMShellUnitTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "include-filter": "com.android.wm.shell.back"
+ }
+ ]
+ },
+ {
+ "name": "CtsWindowManagerDeviceTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "include-filter": "android.server.wm.BackGestureInvokedTest"
+ },
+ {
+ "include-filter": "android.server.wm.BackNavigationTests"
+ },
+ {
+ "include-filter": "android.server.wm.OnBackInvokedCallbackGestureTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 3d5230d5cf90..57b5b8f24fad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -82,6 +82,7 @@ import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.recents.RecentsTransitionHandler;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.startingsurface.StartingSurface;
@@ -520,6 +521,9 @@ public abstract class WMShellBaseModule {
desktopModeTaskRepository, mainExecutor));
}
+ @BindsOptionalOf
+ abstract RecentsTransitionHandler optionalRecentsTransitionHandler();
+
//
// Shell transitions
//
@@ -803,6 +807,7 @@ public abstract class WMShellBaseModule {
Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
Optional<FreeformComponents> freeformComponents,
Optional<RecentTasksController> recentTasksOptional,
+ Optional<RecentsTransitionHandler> recentsTransitionHandlerOptional,
Optional<OneHandedController> oneHandedControllerOptional,
Optional<HideDisplayCutoutController> hideDisplayCutoutControllerOptional,
Optional<ActivityEmbeddingController> activityEmbeddingOptional,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 7a83d101578f..cc0da2840fa0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -83,6 +83,7 @@ import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.recents.RecentsTransitionHandler;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
@@ -528,9 +529,20 @@ public abstract class WMShellModule {
ShellInit shellInit,
Optional<SplitScreenController> splitScreenOptional,
Optional<PipTouchHandler> pipTouchHandlerOptional,
+ Optional<RecentsTransitionHandler> recentsTransitionHandler,
Transitions transitions) {
return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional,
- pipTouchHandlerOptional);
+ pipTouchHandlerOptional, recentsTransitionHandler);
+ }
+
+ @WMSingleton
+ @Provides
+ static RecentsTransitionHandler provideRecentsTransitionHandler(
+ ShellInit shellInit,
+ Transitions transitions,
+ Optional<RecentTasksController> recentTasksController) {
+ return new RecentsTransitionHandler(shellInit, transitions,
+ recentTasksController.orElse(null));
}
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
index 2d84d211e30a..318a49a8de31 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
@@ -21,6 +21,8 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
import static android.view.Display.DEFAULT_DISPLAY;
import android.app.ActivityManager;
@@ -33,6 +35,7 @@ import android.graphics.Rect;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
+import android.view.Display;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.SurfaceControl;
@@ -44,6 +47,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -80,6 +84,12 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
private final DisplayController mDisplayController;
private final DisplayInsetsController mDisplayInsetsController;
+ /**
+ * The value of the {@link R.bool.config_reverseDefaultRotation} property which defines how
+ * {@link Display#getRotation} values are mapped to screen orientations
+ */
+ private final boolean mReverseDefaultRotationEnabled;
+
@VisibleForTesting
ActivityManager.RunningTaskInfo mLaunchRootTask;
@VisibleForTesting
@@ -188,6 +198,8 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
mDisplayInsetsController = displayInsetsController;
mKidsModeSettingsObserver = kidsModeSettingsObserver;
shellInit.addInitCallback(this::onInit, this);
+ mReverseDefaultRotationEnabled = context.getResources().getBoolean(
+ R.bool.config_reverseDefaultRotation);
}
public KidsModeTaskOrganizer(
@@ -211,6 +223,8 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
mDisplayController = displayController;
mDisplayInsetsController = displayInsetsController;
shellInit.addInitCallback(this::onInit, this);
+ mReverseDefaultRotationEnabled = context.getResources().getBoolean(
+ R.bool.config_reverseDefaultRotation);
}
/**
@@ -294,7 +308,14 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
// Needed since many Kids apps aren't optimised to support both orientations and it will be
// hard for kids to understand the app compat mode.
// TODO(229961548): Remove ignoreOrientationRequest exception for Kids Mode once possible.
- setIsIgnoreOrientationRequestDisabled(true);
+ if (mReverseDefaultRotationEnabled) {
+ setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled */ true,
+ /* fromOrientations */ new int[]{SCREEN_ORIENTATION_REVERSE_LANDSCAPE},
+ /* toOrientations */ new int[]{SCREEN_ORIENTATION_LANDSCAPE});
+ } else {
+ setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled */ true,
+ /* fromOrientations */ null, /* toOrientations */ null);
+ }
final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(DEFAULT_DISPLAY);
if (displayLayout != null) {
mDisplayWidth = displayLayout.width();
@@ -315,7 +336,8 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
@VisibleForTesting
void disable() {
- setIsIgnoreOrientationRequestDisabled(false);
+ setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled */ false,
+ /* fromOrientations */ null, /* toOrientations */ null);
mDisplayInsetsController.removeInsetsChangedListener(DEFAULT_DISPLAY,
mOnInsetsChangedListener);
mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
index d961d8658b98..78de5f3e7a1f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
@@ -75,4 +75,9 @@ interface IPip {
* Sets the height and visibility of the Launcher keep clear area.
*/
oneway void setLauncherKeepClearAreaHeight(boolean visible, int height) = 6;
+
+ /**
+ * Sets the app icon size in pixel used by Launcher
+ */
+ oneway void setLauncherAppIconSize(int iconSizePx) = 7;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index fe8ede67c415..1187126f5bf1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -371,10 +371,11 @@ public class PipAnimationController {
new PipContentOverlay.PipSnapshotOverlay(snapshot, sourceRectHint));
}
- void setAppIconContentOverlay(Context context, Rect bounds, ActivityInfo activityInfo) {
+ void setAppIconContentOverlay(Context context, Rect bounds, ActivityInfo activityInfo,
+ int appIconSizePx) {
reattachContentOverlay(
new PipContentOverlay.PipAppIconOverlay(context, bounds,
- () -> new IconProvider(context).getIcon(activityInfo)));
+ new IconProvider(context).getIcon(activityInfo), appIconSizePx));
}
private void reattachContentOverlay(PipContentOverlay overlay) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
index f08742db8ebf..9a775dff1f69 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -86,6 +86,7 @@ public class PipBoundsState {
private int mStashedState = STASH_TYPE_NONE;
private int mStashOffset;
private @Nullable PipReentryState mPipReentryState;
+ private final LauncherState mLauncherState = new LauncherState();
private final @Nullable PipSizeSpecHandler mPipSizeSpecHandler;
private @Nullable ComponentName mLastPipComponentName;
private final @NonNull MotionBoundsState mMotionBoundsState = new MotionBoundsState();
@@ -482,6 +483,10 @@ public class PipBoundsState {
mOnPipExclusionBoundsChangeCallbacks.remove(onPipExclusionBoundsChangeCallback);
}
+ public LauncherState getLauncherState() {
+ return mLauncherState;
+ }
+
/** Source of truth for the current bounds of PIP that may be in motion. */
public static class MotionBoundsState {
/** The bounds used when PIP is in motion (e.g. during a drag or animation) */
@@ -534,6 +539,25 @@ public class PipBoundsState {
}
}
+ /** Data class for Launcher state. */
+ public static final class LauncherState {
+ private int mAppIconSizePx;
+
+ public void setAppIconSizePx(int appIconSizePx) {
+ mAppIconSizePx = appIconSizePx;
+ }
+
+ public int getAppIconSizePx() {
+ return mAppIconSizePx;
+ }
+
+ void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + LauncherState.class.getSimpleName());
+ pw.println(innerPrefix + "getAppIconSizePx=" + getAppIconSizePx());
+ }
+ }
+
static final class PipReentryState {
private static final String TAG = PipReentryState.class.getSimpleName();
@@ -587,6 +611,7 @@ public class PipBoundsState {
} else {
mPipReentryState.dump(pw, innerPrefix);
}
+ mLauncherState.dump(pw, innerPrefix);
mMotionBoundsState.dump(pw, innerPrefix);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
index d228dfbb7705..9fa57cacb11f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
@@ -32,8 +32,6 @@ import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.TaskSnapshot;
-import java.util.function.Supplier;
-
/**
* Represents the content overlay used during the entering PiP animation.
*/
@@ -176,9 +174,8 @@ public abstract class PipContentOverlay {
/** A {@link PipContentOverlay} shows app icon on solid color background. */
public static final class PipAppIconOverlay extends PipContentOverlay {
private static final String TAG = PipAppIconOverlay.class.getSimpleName();
- // Align with the practical / reasonable launcher:iconImageSize as in
- // vendor/unbundled_google/packages/NexusLauncher/res/xml/device_profiles.xml
- private static final int APP_ICON_SIZE_DP = 66;
+ // The maximum size for app icon in pixel.
+ private static final int MAX_APP_ICON_SIZE_DP = 72;
private final Context mContext;
private final int mAppIconSizePx;
@@ -188,14 +185,16 @@ public abstract class PipContentOverlay {
private Bitmap mBitmap;
- public PipAppIconOverlay(Context context, Rect appBounds, Supplier<Drawable> iconSupplier) {
+ public PipAppIconOverlay(Context context, Rect appBounds,
+ Drawable appIcon, int appIconSizePx) {
mContext = context;
- mAppIconSizePx = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, APP_ICON_SIZE_DP,
- context.getResources().getDisplayMetrics());
+ final int maxAppIconSizePx = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP,
+ MAX_APP_ICON_SIZE_DP, context.getResources().getDisplayMetrics());
+ mAppIconSizePx = Math.min(maxAppIconSizePx, appIconSizePx);
mAppBounds = new Rect(appBounds);
mBitmap = Bitmap.createBitmap(appBounds.width(), appBounds.height(),
Bitmap.Config.ARGB_8888);
- prepareAppIconOverlay(iconSupplier);
+ prepareAppIconOverlay(appIcon);
mLeash = new SurfaceControl.Builder(new SurfaceSession())
.setCallsite(TAG)
.setName(LAYER_NAME)
@@ -238,7 +237,7 @@ public abstract class PipContentOverlay {
}
}
- private void prepareAppIconOverlay(Supplier<Drawable> iconSupplier) {
+ private void prepareAppIconOverlay(Drawable appIcon) {
final Canvas canvas = new Canvas();
canvas.setBitmap(mBitmap);
final TypedArray ta = mContext.obtainStyledAttributes(new int[] {
@@ -252,7 +251,6 @@ public abstract class PipContentOverlay {
} finally {
ta.recycle();
}
- final Drawable appIcon = iconSupplier.get();
final Rect appIconBounds = new Rect(
mAppBounds.centerX() - mAppIconSizePx / 2,
mAppBounds.centerY() - mAppIconSizePx / 2,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index d5b9c5e8d8ff..52f5a8cfd8e0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -1608,7 +1608,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
if (SystemProperties.getBoolean(
"persist.wm.debug.enable_pip_app_icon_overlay", true)) {
animator.setAppIconContentOverlay(
- mContext, currentBounds, mTaskInfo.topActivityInfo);
+ mContext, currentBounds, mTaskInfo.topActivityInfo,
+ mPipBoundsState.getLauncherState().getAppIconSizePx());
} else {
animator.setColorContentOverlay(mContext);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 45bb73bdc0d2..49a27c57dc73 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -808,7 +808,8 @@ public class PipTransition extends PipTransitionController {
"persist.wm.debug.enable_pip_app_icon_overlay", true)
&& hasTopActivityInfo) {
animator.setAppIconContentOverlay(
- mContext, currentBounds, taskInfo.topActivityInfo);
+ mContext, currentBounds, taskInfo.topActivityInfo,
+ mPipBoundsState.getLauncherState().getAppIconSizePx());
} else {
animator.setColorContentOverlay(mContext);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index b1e431d1b2e8..9ee4b65b5836 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -942,6 +942,10 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
}
+ private void setLauncherAppIconSize(int iconSizePx) {
+ mPipBoundsState.getLauncherState().setAppIconSizePx(iconSizePx);
+ }
+
private void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
mOnIsInPipStateChangedListener = callback;
if (mOnIsInPipStateChangedListener != null) {
@@ -1286,26 +1290,26 @@ public class PipController implements PipTransitionController.PipTransitionCallb
overlay.setUnreleasedWarningCallSite("PipController.stopSwipePipToHome");
}
executeRemoteCallWithTaskPermission(mController, "stopSwipePipToHome",
- (controller) -> {
- controller.stopSwipePipToHome(taskId, componentName, destinationBounds,
- overlay);
- });
+ (controller) -> controller.stopSwipePipToHome(
+ taskId, componentName, destinationBounds, overlay));
}
@Override
public void setShelfHeight(boolean visible, int height) {
executeRemoteCallWithTaskPermission(mController, "setShelfHeight",
- (controller) -> {
- controller.setShelfHeight(visible, height);
- });
+ (controller) -> controller.setShelfHeight(visible, height));
}
@Override
public void setLauncherKeepClearAreaHeight(boolean visible, int height) {
executeRemoteCallWithTaskPermission(mController, "setLauncherKeepClearAreaHeight",
- (controller) -> {
- controller.setLauncherKeepClearAreaHeight(visible, height);
- });
+ (controller) -> controller.setLauncherKeepClearAreaHeight(visible, height));
+ }
+
+ @Override
+ public void setLauncherAppIconSize(int iconSizePx) {
+ executeRemoteCallWithTaskPermission(mController, "setLauncherAppIconSize",
+ (controller) -> controller.setLauncherAppIconSize(iconSizePx));
}
@Override
@@ -1323,9 +1327,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
@Override
public void setPipAnimationTypeToAlpha() {
executeRemoteCallWithTaskPermission(mController, "setPipAnimationTypeToAlpha",
- (controller) -> {
- controller.setPinnedStackAnimationType(ANIM_TYPE_ALPHA);
- });
+ (controller) -> controller.setPinnedStackAnimationType(ANIM_TYPE_ALPHA));
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
index 1a6c1d65db03..4048c5b8feab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
@@ -17,6 +17,11 @@
package com.android.wm.shell.recents;
import android.app.ActivityManager.RunningTaskInfo;
+import android.app.IApplicationThread;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.IRecentsAnimationRunner;
import com.android.wm.shell.recents.IRecentTasksListener;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
@@ -45,4 +50,10 @@ interface IRecentTasks {
* Gets the set of running tasks.
*/
RunningTaskInfo[] getRunningTasks(int maxNum) = 4;
+
+ /**
+ * Starts a recents transition.
+ */
+ oneway void startRecentsTransition(in PendingIntent intent, in Intent fillIn, in Bundle options,
+ IApplicationThread appThread, IRecentsAnimationRunner listener) = 5;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 0d9faa3c6f83..c5bfd8753994 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -24,13 +24,18 @@ import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_RE
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
+import android.app.IApplicationThread;
+import android.app.PendingIntent;
import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
import android.os.RemoteException;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
+import android.view.IRecentsAnimationRunner;
import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
@@ -79,6 +84,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
private final TaskStackListenerImpl mTaskStackListener;
private final RecentTasksImpl mImpl = new RecentTasksImpl();
private final ActivityTaskManager mActivityTaskManager;
+ private RecentsTransitionHandler mTransitionHandler = null;
private IRecentTasksListener mListener;
private final boolean mIsDesktopMode;
@@ -150,6 +156,10 @@ public class RecentTasksController implements TaskStackListenerCallback,
mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTaskListener(this));
}
+ void setTransitionHandler(RecentsTransitionHandler handler) {
+ mTransitionHandler = handler;
+ }
+
/**
* Adds a split pair. This call does not validate the taskIds, only that they are not the same.
*/
@@ -492,5 +502,18 @@ public class RecentTasksController implements TaskStackListenerCallback,
true /* blocking */);
return tasks[0];
}
+
+ @Override
+ public void startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
+ IApplicationThread appThread, IRecentsAnimationRunner listener) {
+ if (mController.mTransitionHandler == null) {
+ Slog.e(TAG, "Used shell-transitions startRecentsTransition without"
+ + " shell-transitions");
+ return;
+ }
+ executeRemoteCallWithTaskPermission(mController, "startRecentsTransition",
+ (controller) -> controller.mTransitionHandler.startRecentsTransition(
+ intent, fillIn, options, appThread, listener));
+ }
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
new file mode 100644
index 000000000000..da8c805eb038
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -0,0 +1,759 @@
+/*
+ * 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.wm.shell.recents;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
+import static android.view.WindowManager.TRANSIT_SLEEP;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.IApplicationThread;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.view.IRecentsAnimationController;
+import android.view.IRecentsAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.window.PictureInPictureSurfaceTransaction;
+import android.window.TaskSnapshot;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.TransitionUtil;
+
+import java.util.ArrayList;
+
+/**
+ * Handles the Recents (overview) animation. Only one of these can run at a time. A recents
+ * transition must be created via {@link #startRecentsTransition}. Anything else will be ignored.
+ */
+public class RecentsTransitionHandler implements Transitions.TransitionHandler {
+ private static final String TAG = "RecentsTransitionHandler";
+
+ private final Transitions mTransitions;
+ private final ShellExecutor mExecutor;
+ private IApplicationThread mAnimApp = null;
+ private final ArrayList<RecentsController> mControllers = new ArrayList<>();
+
+ /**
+ * List of other handlers which might need to mix recents with other things. These are checked
+ * in the order they are added. Ideally there should only be one.
+ */
+ private final ArrayList<RecentsMixedHandler> mMixers = new ArrayList<>();
+
+ public RecentsTransitionHandler(ShellInit shellInit, Transitions transitions,
+ @Nullable RecentTasksController recentTasksController) {
+ mTransitions = transitions;
+ mExecutor = transitions.getMainExecutor();
+ if (!Transitions.ENABLE_SHELL_TRANSITIONS) return;
+ if (recentTasksController == null) return;
+ shellInit.addInitCallback(() -> {
+ recentTasksController.setTransitionHandler(this);
+ transitions.addHandler(this);
+ }, this);
+ }
+
+ /** Register a mixer handler. {@see RecentsMixedHandler}*/
+ public void addMixer(RecentsMixedHandler mixer) {
+ mMixers.add(mixer);
+ }
+
+ /** Unregister a Mixed Handler */
+ public void removeMixer(RecentsMixedHandler mixer) {
+ mMixers.remove(mixer);
+ }
+
+ void startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
+ IApplicationThread appThread, IRecentsAnimationRunner listener) {
+ // only care about latest one.
+ mAnimApp = appThread;
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.sendPendingIntent(intent, fillIn, options);
+ final RecentsController controller = new RecentsController(listener);
+ RecentsMixedHandler mixer = null;
+ Transitions.TransitionHandler mixedHandler = null;
+ for (int i = 0; i < mMixers.size(); ++i) {
+ mixedHandler = mMixers.get(i).handleRecentsRequest(wct);
+ if (mixedHandler != null) {
+ mixer = mMixers.get(i);
+ break;
+ }
+ }
+ final IBinder transition = mTransitions.startTransition(TRANSIT_TO_FRONT, wct,
+ mixedHandler == null ? this : mixedHandler);
+ if (mixer != null) {
+ mixer.setRecentsTransition(transition);
+ }
+ if (transition == null) {
+ controller.cancel();
+ return;
+ }
+ controller.setTransition(transition);
+ mControllers.add(controller);
+ }
+
+ @Override
+ public WindowContainerTransaction handleRequest(IBinder transition,
+ TransitionRequestInfo request) {
+ // do not directly handle requests. Only entry point should be via startRecentsTransition
+ return null;
+ }
+
+ private int findController(IBinder transition) {
+ for (int i = mControllers.size() - 1; i >= 0; --i) {
+ if (mControllers.get(i).mTransition == transition) return i;
+ }
+ return -1;
+ }
+
+ @Override
+ public boolean startAnimation(IBinder transition, TransitionInfo info,
+ SurfaceControl.Transaction startTransaction,
+ SurfaceControl.Transaction finishTransaction,
+ Transitions.TransitionFinishCallback finishCallback) {
+ final int controllerIdx = findController(transition);
+ if (controllerIdx < 0) return false;
+ final RecentsController controller = mControllers.get(controllerIdx);
+ Transitions.setRunningRemoteTransitionDelegate(mAnimApp);
+ mAnimApp = null;
+ if (!controller.start(info, startTransaction, finishTransaction, finishCallback)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void mergeAnimation(IBinder transition, TransitionInfo info,
+ SurfaceControl.Transaction t, IBinder mergeTarget,
+ Transitions.TransitionFinishCallback finishCallback) {
+ final int targetIdx = findController(mergeTarget);
+ if (targetIdx < 0) return;
+ final RecentsController controller = mControllers.get(targetIdx);
+ controller.merge(info, t, finishCallback);
+ }
+
+ @Override
+ public void onTransitionConsumed(IBinder transition, boolean aborted,
+ SurfaceControl.Transaction finishTransaction) {
+ final int idx = findController(transition);
+ if (idx < 0) return;
+ mControllers.get(idx).cancel();
+ }
+
+ /** There is only one of these and it gets reset on finish. */
+ private class RecentsController extends IRecentsAnimationController.Stub {
+ private IRecentsAnimationRunner mListener;
+ private IBinder.DeathRecipient mDeathHandler;
+ private Transitions.TransitionFinishCallback mFinishCB = null;
+ private SurfaceControl.Transaction mFinishTransaction = null;
+
+ /**
+ * List of tasks that we are switching away from via this transition. Upon finish, these
+ * pausing tasks will become invisible.
+ * These need to be ordered since the order must be restored if there is no task-switch.
+ */
+ private ArrayList<TaskState> mPausingTasks = null;
+
+ /**
+ * List of tasks that we are switching to. Upon finish, these will remain visible and
+ * on top.
+ */
+ private ArrayList<TaskState> mOpeningTasks = null;
+
+ private WindowContainerToken mPipTask = null;
+ private WindowContainerToken mRecentsTask = null;
+ private int mRecentsTaskId = -1;
+ private TransitionInfo mInfo = null;
+ private boolean mOpeningSeparateHome = false;
+ private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null;
+ private PictureInPictureSurfaceTransaction mPipTransaction = null;
+ private IBinder mTransition = null;
+ private boolean mKeyguardLocked = false;
+ private boolean mWillFinishToHome = false;
+
+ /** The animation is idle, waiting for the user to choose a task to switch to. */
+ private static final int STATE_NORMAL = 0;
+
+ /** The user chose a new task to switch to and the animation is animating to it. */
+ private static final int STATE_NEW_TASK = 1;
+
+ /** The latest state that the recents animation is operating in. */
+ private int mState = STATE_NORMAL;
+
+ RecentsController(IRecentsAnimationRunner listener) {
+ mListener = listener;
+ mDeathHandler = () -> mExecutor.execute(() -> {
+ if (mListener == null) return;
+ if (mFinishCB != null) {
+ finish(mWillFinishToHome, false /* leaveHint */);
+ }
+ });
+ try {
+ mListener.asBinder().linkToDeath(mDeathHandler, 0 /* flags */);
+ } catch (RemoteException e) {
+ mListener = null;
+ }
+ }
+
+ void setTransition(IBinder transition) {
+ mTransition = transition;
+ }
+
+ void cancel() {
+ // restoring (to-home = false) involves submitting more WM changes, so by default, use
+ // toHome = true when canceling.
+ cancel(true /* toHome */);
+ }
+
+ void cancel(boolean toHome) {
+ if (mFinishCB != null && mListener != null) {
+ try {
+ mListener.onAnimationCanceled(null, null);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error canceling recents animation", e);
+ }
+ finish(toHome, false /* userLeave */);
+ } else {
+ cleanUp();
+ }
+ }
+
+ /**
+ * Sends a cancel message to the recents animation with snapshots. Used to trigger a
+ * "replace-with-screenshot" like behavior.
+ */
+ private boolean sendCancelWithSnapshots() {
+ int[] taskIds = null;
+ TaskSnapshot[] snapshots = null;
+ if (mPausingTasks.size() > 0) {
+ taskIds = new int[mPausingTasks.size()];
+ snapshots = new TaskSnapshot[mPausingTasks.size()];
+ try {
+ for (int i = 0; i < mPausingTasks.size(); ++i) {
+ snapshots[i] = ActivityTaskManager.getService().takeTaskSnapshot(
+ mPausingTasks.get(0).mTaskInfo.taskId);
+ }
+ } catch (RemoteException e) {
+ taskIds = null;
+ snapshots = null;
+ }
+ }
+ try {
+ mListener.onAnimationCanceled(taskIds, snapshots);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error canceling recents animation", e);
+ return false;
+ }
+ return true;
+ }
+
+ void cleanUp() {
+ if (mListener != null && mDeathHandler != null) {
+ mListener.asBinder().unlinkToDeath(mDeathHandler, 0 /* flags */);
+ mDeathHandler = null;
+ }
+ mListener = null;
+ mFinishCB = null;
+ // clean-up leash surfacecontrols and anything that might reference them.
+ if (mLeashMap != null) {
+ for (int i = 0; i < mLeashMap.size(); ++i) {
+ mLeashMap.valueAt(i).release();
+ }
+ mLeashMap = null;
+ }
+ mFinishTransaction = null;
+ mPausingTasks = null;
+ mOpeningTasks = null;
+ mInfo = null;
+ mTransition = null;
+ mControllers.remove(this);
+ }
+
+ boolean start(TransitionInfo info, SurfaceControl.Transaction t,
+ SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCB) {
+ if (mListener == null || mTransition == null) {
+ cleanUp();
+ return false;
+ }
+ // First see if this is a valid recents transition.
+ boolean hasPausingTasks = false;
+ for (int i = 0; i < info.getChanges().size(); ++i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (TransitionUtil.isWallpaper(change)) continue;
+ if (TransitionUtil.isClosingType(change.getMode())) {
+ hasPausingTasks = true;
+ continue;
+ }
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_RECENTS) {
+ mRecentsTask = taskInfo.token;
+ mRecentsTaskId = taskInfo.taskId;
+ } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
+ mRecentsTask = taskInfo.token;
+ mRecentsTaskId = taskInfo.taskId;
+ }
+ }
+ if (mRecentsTask == null || !hasPausingTasks) {
+ // Recents is already running apparently, so this is a no-op.
+ Slog.e(TAG, "Tried to start recents while it is already running. recents="
+ + mRecentsTask);
+ cleanUp();
+ return false;
+ }
+
+ mInfo = info;
+ mFinishCB = finishCB;
+ mFinishTransaction = finishT;
+ mPausingTasks = new ArrayList<>();
+ mOpeningTasks = new ArrayList<>();
+ mLeashMap = new ArrayMap<>();
+ mKeyguardLocked = (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0;
+
+ final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
+ final ArrayList<RemoteAnimationTarget> wallpapers = new ArrayList<>();
+ TransitionUtil.LeafTaskFilter leafTaskFilter = new TransitionUtil.LeafTaskFilter();
+ // About layering: we divide up the "layer space" into 3 regions (each the size of
+ // the change count). This lets us categorize things into above/below/between
+ // while maintaining their relative ordering.
+ for (int i = 0; i < info.getChanges().size(); ++i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (TransitionUtil.isWallpaper(change)) {
+ final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
+ // wallpapers go into the "below" layer space
+ info.getChanges().size() - i, info, t, mLeashMap);
+ wallpapers.add(target);
+ // Make all the wallpapers opaque since we want them visible from the start
+ t.setAlpha(target.leash, 1);
+ } else if (leafTaskFilter.test(change)) {
+ // start by putting everything into the "below" layer space.
+ final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
+ info.getChanges().size() - i, info, t, mLeashMap);
+ apps.add(target);
+ if (TransitionUtil.isClosingType(change.getMode())) {
+ // raise closing (pausing) task to "above" layer so it isn't covered
+ t.setLayer(target.leash, info.getChanges().size() * 3 - i);
+ mPausingTasks.add(new TaskState(change, target.leash));
+ if (taskInfo.pictureInPictureParams != null
+ && taskInfo.pictureInPictureParams.isAutoEnterEnabled()) {
+ mPipTask = taskInfo.token;
+ }
+ } else if (taskInfo != null
+ && taskInfo.topActivityType == ACTIVITY_TYPE_RECENTS) {
+ // There's a 3p launcher, so make sure recents goes above that.
+ t.setLayer(target.leash, info.getChanges().size() * 3 - i);
+ } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
+ // do nothing
+ } else if (TransitionUtil.isOpeningType(change.getMode())) {
+ mOpeningTasks.add(new TaskState(change, target.leash));
+ }
+ }
+ }
+ t.apply();
+ try {
+ mListener.onAnimationStart(this,
+ apps.toArray(new RemoteAnimationTarget[apps.size()]),
+ wallpapers.toArray(new RemoteAnimationTarget[wallpapers.size()]),
+ new Rect(0, 0, 0, 0), new Rect());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting recents animation", e);
+ cancel();
+ }
+ return true;
+ }
+
+ @SuppressLint("NewApi")
+ void merge(TransitionInfo info, SurfaceControl.Transaction t,
+ Transitions.TransitionFinishCallback finishCallback) {
+ if (mFinishCB == null) {
+ // This was no-op'd (likely a repeated start) and we've already sent finish.
+ return;
+ }
+ if (info.getType() == TRANSIT_SLEEP) {
+ // A sleep event means we need to stop animations immediately, so cancel here.
+ cancel();
+ return;
+ }
+ ArrayList<TransitionInfo.Change> openingTasks = null;
+ ArrayList<TransitionInfo.Change> closingTasks = null;
+ mOpeningSeparateHome = false;
+ TransitionInfo.Change recentsOpening = null;
+ boolean foundRecentsClosing = false;
+ boolean hasChangingApp = false;
+ final TransitionUtil.LeafTaskFilter leafTaskFilter =
+ new TransitionUtil.LeafTaskFilter();
+ for (int i = 0; i < info.getChanges().size(); ++i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ final boolean isLeafTask = leafTaskFilter.test(change);
+ if (TransitionUtil.isOpeningType(change.getMode())) {
+ if (mRecentsTask.equals(change.getContainer())) {
+ recentsOpening = change;
+ } else if (isLeafTask) {
+ if (taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
+ // This is usually a 3p launcher
+ mOpeningSeparateHome = true;
+ }
+ if (openingTasks == null) {
+ openingTasks = new ArrayList<>();
+ }
+ openingTasks.add(change);
+ }
+ } else if (TransitionUtil.isClosingType(change.getMode())) {
+ if (mRecentsTask.equals(change.getContainer())) {
+ foundRecentsClosing = true;
+ } else if (isLeafTask) {
+ if (closingTasks == null) {
+ closingTasks = new ArrayList<>();
+ }
+ closingTasks.add(change);
+ }
+ } else if (change.getMode() == TRANSIT_CHANGE) {
+ // Finish recents animation if the display is changed, so the default
+ // transition handler can play the animation such as rotation effect.
+ if (change.hasFlags(TransitionInfo.FLAG_IS_DISPLAY)) {
+ cancel(mWillFinishToHome);
+ return;
+ }
+ hasChangingApp = true;
+ }
+ }
+ if (hasChangingApp && foundRecentsClosing) {
+ // This happens when a visible app is expanding (usually PiP). In this case,
+ // that transition probably has a special-purpose animation, so finish recents
+ // now and let it do its animation (since recents is going to be occluded).
+ sendCancelWithSnapshots();
+ mExecutor.executeDelayed(
+ () -> finishInner(true /* toHome */, false /* userLeaveHint */), 0);
+ return;
+ }
+ if (recentsOpening != null) {
+ // the recents task re-appeared. This happens if the user gestures before the
+ // task-switch (NEW_TASK) animation finishes.
+ if (mState == STATE_NORMAL) {
+ Slog.e(TAG, "Returning to recents while recents is already idle.");
+ }
+ if (closingTasks == null || closingTasks.size() == 0) {
+ Slog.e(TAG, "Returning to recents without closing any opening tasks.");
+ }
+ // Setup may hide it initially since it doesn't know that overview was still active.
+ t.show(recentsOpening.getLeash());
+ t.setAlpha(recentsOpening.getLeash(), 1.f);
+ mState = STATE_NORMAL;
+ }
+ boolean didMergeThings = false;
+ if (closingTasks != null) {
+ // Cancelling a task-switch. Move the tasks back to mPausing from mOpening
+ for (int i = 0; i < closingTasks.size(); ++i) {
+ final TransitionInfo.Change change = closingTasks.get(i);
+ int openingIdx = TaskState.indexOf(mOpeningTasks, change);
+ if (openingIdx < 0) {
+ Slog.e(TAG, "Back to existing recents animation from an unrecognized "
+ + "task: " + change.getTaskInfo().taskId);
+ continue;
+ }
+ mPausingTasks.add(mOpeningTasks.remove(openingIdx));
+ didMergeThings = true;
+ }
+ }
+ RemoteAnimationTarget[] appearedTargets = null;
+ if (openingTasks != null && openingTasks.size() > 0) {
+ // Switching to some new tasks, add to mOpening and remove from mPausing. Also,
+ // enter NEW_TASK state since this will start the switch-to animation.
+ final int layer = mInfo.getChanges().size() * 3;
+ appearedTargets = new RemoteAnimationTarget[openingTasks.size()];
+ for (int i = 0; i < openingTasks.size(); ++i) {
+ final TransitionInfo.Change change = openingTasks.get(i);
+ int pausingIdx = TaskState.indexOf(mPausingTasks, change);
+ if (pausingIdx >= 0) {
+ // Something is showing/opening a previously-pausing app.
+ appearedTargets[i] = TransitionUtil.newTarget(
+ change, layer, mPausingTasks.get(pausingIdx).mLeash);
+ mOpeningTasks.add(mPausingTasks.remove(pausingIdx));
+ // Setup hides opening tasks initially, so make it visible again (since we
+ // are already showing it).
+ t.show(change.getLeash());
+ t.setAlpha(change.getLeash(), 1.f);
+ } else {
+ // We are receiving new opening tasks, so convert to onTasksAppeared.
+ appearedTargets[i] = TransitionUtil.newTarget(
+ change, layer, info, t, mLeashMap);
+ // reparent into the original `mInfo` since that's where we are animating.
+ final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo);
+ t.reparent(appearedTargets[i].leash, mInfo.getRoot(rootIdx).getLeash());
+ t.setLayer(appearedTargets[i].leash, layer);
+ mOpeningTasks.add(new TaskState(change, appearedTargets[i].leash));
+ }
+ }
+ didMergeThings = true;
+ mState = STATE_NEW_TASK;
+ }
+ if (!didMergeThings) {
+ // Didn't recognize anything in incoming transition so don't merge it.
+ Slog.w(TAG, "Don't know how to merge this transition.");
+ return;
+ }
+ // At this point, we are accepting the merge.
+ t.apply();
+ // not using the incoming anim-only surfaces
+ info.releaseAnimSurfaces();
+ finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+ if (appearedTargets == null) return;
+ try {
+ mListener.onTasksAppeared(appearedTargets);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending appeared tasks to recents animation", e);
+ }
+ }
+
+ @Override
+ public TaskSnapshot screenshotTask(int taskId) {
+ try {
+ return ActivityTaskManager.getService().takeTaskSnapshot(taskId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to screenshot task", e);
+ }
+ return null;
+ }
+
+ @Override
+ public void setInputConsumerEnabled(boolean enabled) {
+ mExecutor.execute(() -> {
+ if (mFinishCB == null || !enabled) return;
+ // transient launches don't receive focus automatically. Since we are taking over
+ // the gesture now, take focus explicitly.
+ // This also moves recents back to top if the user gestured before a switch
+ // animation finished.
+ try {
+ ActivityTaskManager.getService().setFocusedTask(mRecentsTaskId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set focused task", e);
+ }
+ });
+ }
+
+ @Override
+ public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars) {
+ }
+
+ @Override
+ public void setFinishTaskTransaction(int taskId,
+ PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay) {
+ mExecutor.execute(() -> {
+ if (mFinishCB == null) return;
+ mPipTransaction = finishTransaction;
+ });
+ }
+
+ @Override
+ @SuppressLint("NewApi")
+ public void finish(boolean toHome, boolean sendUserLeaveHint) {
+ mExecutor.execute(() -> finishInner(toHome, sendUserLeaveHint));
+ }
+
+ private void finishInner(boolean toHome, boolean sendUserLeaveHint) {
+ if (mFinishCB == null) {
+ Slog.e(TAG, "Duplicate call to finish");
+ return;
+ }
+ final Transitions.TransitionFinishCallback finishCB = mFinishCB;
+ mFinishCB = null;
+
+ final SurfaceControl.Transaction t = mFinishTransaction;
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+ if (mKeyguardLocked && mRecentsTask != null) {
+ if (toHome) wct.reorder(mRecentsTask, true /* toTop */);
+ else wct.restoreTransientOrder(mRecentsTask);
+ }
+ if (!toHome && !mWillFinishToHome && mPausingTasks != null && mState == STATE_NORMAL) {
+ // The gesture is returning to the pausing-task(s) rather than continuing with
+ // recents, so end the transition by moving the app back to the top (and also
+ // re-showing it's task).
+ for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
+ // reverse order so that index 0 ends up on top
+ wct.reorder(mPausingTasks.get(i).mToken, true /* onTop */);
+ t.show(mPausingTasks.get(i).mTaskSurface);
+ }
+ if (!mKeyguardLocked && mRecentsTask != null) {
+ wct.restoreTransientOrder(mRecentsTask);
+ }
+ } else if (toHome && mOpeningSeparateHome && mPausingTasks != null) {
+ // Special situation where 3p launcher was changed during recents (this happens
+ // during tapltests...). Here we get both "return to home" AND "home opening".
+ // This is basically going home, but we have to restore the recents and home order.
+ for (int i = 0; i < mOpeningTasks.size(); ++i) {
+ final TaskState state = mOpeningTasks.get(i);
+ if (state.mTaskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
+ // Make sure it is on top.
+ wct.reorder(state.mToken, true /* onTop */);
+ }
+ t.show(state.mTaskSurface);
+ }
+ for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
+ t.hide(mPausingTasks.get(i).mTaskSurface);
+ }
+ if (!mKeyguardLocked && mRecentsTask != null) {
+ wct.restoreTransientOrder(mRecentsTask);
+ }
+ } else {
+ // The general case: committing to recents, going home, or switching tasks.
+ for (int i = 0; i < mOpeningTasks.size(); ++i) {
+ t.show(mOpeningTasks.get(i).mTaskSurface);
+ }
+ for (int i = 0; i < mPausingTasks.size(); ++i) {
+ if (!sendUserLeaveHint) {
+ // This means recents is not *actually* finishing, so of course we gotta
+ // do special stuff in WMCore to accommodate.
+ wct.setDoNotPip(mPausingTasks.get(i).mToken);
+ }
+ // Since we will reparent out of the leashes, pre-emptively hide the child
+ // surface to match the leash. Otherwise, there will be a flicker before the
+ // visibility gets committed in Core when using split-screen (in splitscreen,
+ // the leaf-tasks are not "independent" so aren't hidden by normal setup).
+ t.hide(mPausingTasks.get(i).mTaskSurface);
+ }
+ if (mPipTask != null && mPipTransaction != null && sendUserLeaveHint) {
+ t.show(mInfo.getChange(mPipTask).getLeash());
+ PictureInPictureSurfaceTransaction.apply(mPipTransaction,
+ mInfo.getChange(mPipTask).getLeash(), t);
+ mPipTask = null;
+ mPipTransaction = null;
+ }
+ }
+ cleanUp();
+ finishCB.onTransitionFinished(wct.isEmpty() ? null : wct, null /* wctCB */);
+ }
+
+ @Override
+ public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
+ }
+
+ @Override
+ public void cleanupScreenshot() {
+ }
+
+ @Override
+ public void setWillFinishToHome(boolean willFinishToHome) {
+ mExecutor.execute(() -> {
+ mWillFinishToHome = willFinishToHome;
+ });
+ }
+
+ /**
+ * @see IRecentsAnimationController#removeTask
+ */
+ @Override
+ public boolean removeTask(int taskId) {
+ return false;
+ }
+
+ /**
+ * @see IRecentsAnimationController#detachNavigationBarFromApp
+ */
+ @Override
+ public void detachNavigationBarFromApp(boolean moveHomeToTop) {
+ mExecutor.execute(() -> {
+ if (mTransition == null) return;
+ try {
+ ActivityTaskManager.getService().detachNavigationBarFromApp(mTransition);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to detach the navigation bar from app", e);
+ }
+ });
+ }
+
+ /**
+ * @see IRecentsAnimationController#animateNavigationBarToApp(long)
+ */
+ @Override
+ public void animateNavigationBarToApp(long duration) {
+ }
+ };
+
+ /** Utility class to track the state of a task as-seen by recents. */
+ private static class TaskState {
+ WindowContainerToken mToken;
+ ActivityManager.RunningTaskInfo mTaskInfo;
+
+ /** The surface/leash of the task provided by Core. */
+ SurfaceControl mTaskSurface;
+
+ /** The (local) animation-leash created for this task. */
+ SurfaceControl mLeash;
+
+ TaskState(TransitionInfo.Change change, SurfaceControl leash) {
+ mToken = change.getContainer();
+ mTaskInfo = change.getTaskInfo();
+ mTaskSurface = change.getLeash();
+ mLeash = leash;
+ }
+
+ static int indexOf(ArrayList<TaskState> list, TransitionInfo.Change change) {
+ for (int i = list.size() - 1; i >= 0; --i) {
+ if (list.get(i).mToken.equals(change.getContainer())) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public String toString() {
+ return "" + mToken + " : " + mLeash;
+ }
+ }
+
+ /**
+ * An interface for a mixed handler to receive information about recents requests (since these
+ * come into this handler directly vs from WMCore request).
+ */
+ public interface RecentsMixedHandler {
+ /**
+ * Called when a recents request comes in. The handler can add operations to outWCT. If
+ * the handler wants to "accept" the transition, it should return itself; otherwise, it
+ * should return `null`.
+ *
+ * If a mixed-handler accepts this recents, it will be the de-facto handler for this
+ * transition and is required to call the associated {@link #startAnimation},
+ * {@link #mergeAnimation}, and {@link #onTransitionConsumed} methods.
+ */
+ Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT);
+
+ /**
+ * Reports the transition token associated with the accepted recents request. If there was
+ * a problem starting the request, this will be called with `null`.
+ */
+ void setRecentsTransition(@Nullable IBinder transition);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index d0948923dc6e..aa1e6ed8bcf3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -43,6 +43,7 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.recents.RecentsTransitionHandler;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.splitscreen.StageCoordinator;
import com.android.wm.shell.sysui.ShellInit;
@@ -55,10 +56,12 @@ import java.util.Optional;
* A handler for dealing with transitions involving multiple other handlers. For example: an
* activity in split-screen going into PiP.
*/
-public class DefaultMixedHandler implements Transitions.TransitionHandler {
+public class DefaultMixedHandler implements Transitions.TransitionHandler,
+ RecentsTransitionHandler.RecentsMixedHandler {
private final Transitions mPlayer;
private PipTransitionController mPipHandler;
+ private RecentsTransitionHandler mRecentsHandler;
private StageCoordinator mSplitHandler;
private static class MixedTransition {
@@ -122,7 +125,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
public DefaultMixedHandler(@NonNull ShellInit shellInit, @NonNull Transitions player,
Optional<SplitScreenController> splitScreenControllerOptional,
- Optional<PipTouchHandler> pipTouchHandlerOptional) {
+ Optional<PipTouchHandler> pipTouchHandlerOptional,
+ Optional<RecentsTransitionHandler> recentsHandlerOptional) {
mPlayer = player;
if (Transitions.ENABLE_SHELL_TRANSITIONS && pipTouchHandlerOptional.isPresent()
&& splitScreenControllerOptional.isPresent()) {
@@ -134,6 +138,10 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
if (mSplitHandler != null) {
mSplitHandler.setMixedHandler(this);
}
+ mRecentsHandler = recentsHandlerOptional.orElse(null);
+ if (mRecentsHandler != null) {
+ mRecentsHandler.addMixer(this);
+ }
}, this);
}
}
@@ -200,6 +208,29 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
return null;
}
+ @Override
+ public Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT) {
+ if (mRecentsHandler != null && mSplitHandler.isSplitActive()) {
+ return this;
+ }
+ return null;
+ }
+
+ @Override
+ public void setRecentsTransition(IBinder transition) {
+ if (mSplitHandler.isSplitActive()) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+ + "Split-Screen is active, so treat it as Mixed.");
+ final MixedTransition mixed = new MixedTransition(
+ MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
+ mixed.mLeftoversHandler = mRecentsHandler;
+ mActiveTransitions.add(mixed);
+ } else {
+ throw new IllegalStateException("Accepted a recents transition but don't know how to"
+ + " handle it");
+ }
+ }
+
private TransitionInfo subCopy(@NonNull TransitionInfo info,
@WindowManager.TransitionType int newType, boolean withChanges) {
final TransitionInfo out = new TransitionInfo(newType, withChanges ? info.getFlags() : 0);
@@ -563,6 +594,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
mPipHandler.onTransitionConsumed(transition, aborted, finishT);
} else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
+ } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
+ mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index 11bb0cc1306e..f52e877ec2b1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -74,10 +74,18 @@ open class EnterPipOnUserLeaveHintTest(flicker: FlickerTest) : EnterPipTransitio
@Presubmit
@Test
- override fun pipAppLayerOrOverlayAlwaysVisible() {
+ override fun pipAppLayerAlwaysVisible() {
// pip layer in gesture nav will disappear during transition
Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
- super.pipAppLayerOrOverlayAlwaysVisible()
+ super.pipAppLayerAlwaysVisible()
+ }
+
+ @Presubmit
+ @Test
+ override fun pipOverlayLayerAppearThenDisappear() {
+ // no overlay in gesture nav for non-auto enter PiP transition
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ super.pipOverlayLayerAppearThenDisappear()
}
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
index 327225421580..e40e5eaad9e2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
@@ -43,13 +43,23 @@ abstract class EnterPipTransition(flicker: FlickerTest) : PipTransition(flicker)
/** Checks [pipApp] layer remains visible throughout the animation */
@Presubmit
@Test
- open fun pipAppLayerOrOverlayAlwaysVisible() {
+ open fun pipAppLayerAlwaysVisible() {
flicker.assertLayers {
this.isVisible(pipApp)
+ }
+ }
+
+ /** Checks the content overlay appears then disappears during the animation */
+ @Presubmit
+ @Test
+ open fun pipOverlayLayerAppearThenDisappear() {
+ val overlay = ComponentNameMatcher.PIP_CONTENT_OVERLAY
+ flicker.assertLayers {
+ this.notContains(overlay)
.then()
- .isVisible(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
+ .contains(overlay)
.then()
- .isVisible(pipApp)
+ .notContains(overlay)
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index 2ac1dc0c4838..57a698128d77 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -69,6 +69,8 @@ android_test {
enabled: false,
},
+ test_suites: ["device-tests"],
+
platform_apis: true,
certificate: "platform",
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 6dae479ae7a7..169b9bd4dea7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -411,6 +411,53 @@ public class BackAnimationControllerTest extends ShellTestCase {
verify(mAnimatorCallback, never()).onBackInvoked();
}
+ @Test
+ public void testBackToActivity() throws RemoteException {
+ final CrossActivityAnimation animation = new CrossActivityAnimation(mContext,
+ mAnimationBackground);
+ verifySystemBackBehavior(
+ BackNavigationInfo.TYPE_CROSS_ACTIVITY, animation.mBackAnimationRunner);
+ }
+
+ @Test
+ public void testBackToTask() throws RemoteException {
+ final CrossTaskBackAnimation animation = new CrossTaskBackAnimation(mContext,
+ mAnimationBackground);
+ verifySystemBackBehavior(
+ BackNavigationInfo.TYPE_CROSS_TASK, animation.mBackAnimationRunner);
+ }
+
+ private void verifySystemBackBehavior(int type, BackAnimationRunner animation)
+ throws RemoteException {
+ final BackAnimationRunner animationRunner = spy(animation);
+ final IRemoteAnimationRunner runner = spy(animationRunner.getRunner());
+ final IOnBackInvokedCallback callback = spy(animationRunner.getCallback());
+
+ // Set up the monitoring objects.
+ doNothing().when(runner).onAnimationStart(anyInt(), any(), any(), any(), any());
+ doReturn(runner).when(animationRunner).getRunner();
+ doReturn(callback).when(animationRunner).getCallback();
+
+ mController.registerAnimation(type, animationRunner);
+
+ createNavigationInfo(type, true);
+
+ doMotionEvent(MotionEvent.ACTION_DOWN, 0);
+
+ // Check that back start and progress is dispatched when first move.
+ doMotionEvent(MotionEvent.ACTION_MOVE, 100);
+
+ simulateRemoteAnimationStart(type);
+
+ verify(callback).onBackStarted(any(BackMotionEvent.class));
+ verify(animationRunner).startAnimation(any(), any(), any(), any());
+
+ // Check that back invocation is dispatched.
+ mController.setTriggerBack(true); // Fake trigger back
+ doMotionEvent(MotionEvent.ACTION_UP, 0);
+ verify(callback).onBackInvoked();
+ }
+
private void doMotionEvent(int actionDown, int coordinate) {
mController.onMotionEvent(
coordinate, coordinate,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
index ecfb427dbced..58e91cb50c7a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
@@ -31,6 +31,7 @@ import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ParceledListSlice;
+import android.content.res.Resources;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
@@ -77,6 +78,7 @@ public class KidsModeTaskOrganizerTest extends ShellTestCase {
@Mock private ShellInit mShellInit;
@Mock private ShellCommandHandler mShellCommandHandler;
@Mock private DisplayInsetsController mDisplayInsetsController;
+ @Mock private Resources mResources;
KidsModeTaskOrganizer mOrganizer;
@@ -89,10 +91,12 @@ public class KidsModeTaskOrganizerTest extends ShellTestCase {
} catch (RemoteException e) {
}
// NOTE: KidsModeTaskOrganizer should have a null CompatUIController.
- mOrganizer = spy(new KidsModeTaskOrganizer(mContext, mShellInit, mShellCommandHandler,
- mTaskOrganizerController, mSyncTransactionQueue, mDisplayController,
- mDisplayInsetsController, Optional.empty(), Optional.empty(), mObserver,
- mTestExecutor, mHandler));
+ doReturn(mResources).when(mContext).getResources();
+ final KidsModeTaskOrganizer kidsModeTaskOrganizer = new KidsModeTaskOrganizer(mContext,
+ mShellInit, mShellCommandHandler, mTaskOrganizerController, mSyncTransactionQueue,
+ mDisplayController, mDisplayInsetsController, Optional.empty(), Optional.empty(),
+ mObserver, mTestExecutor, mHandler);
+ mOrganizer = spy(kidsModeTaskOrganizer);
doReturn(mTransaction).when(mOrganizer).getWindowContainerTransaction();
doReturn(new InsetsState()).when(mDisplayController).getInsetsState(DEFAULT_DISPLAY);
}
@@ -112,6 +116,8 @@ public class KidsModeTaskOrganizerTest extends ShellTestCase {
verify(mOrganizer, times(1)).registerOrganizer();
verify(mOrganizer, times(1)).createRootTask(
eq(DEFAULT_DISPLAY), eq(WINDOWING_MODE_FULLSCREEN), eq(mOrganizer.mCookie));
+ verify(mOrganizer, times(1))
+ .setOrientationRequestPolicy(eq(true), any(), any());
final ActivityManager.RunningTaskInfo rootTask = createTaskInfo(12,
WINDOWING_MODE_FULLSCREEN, mOrganizer.mCookie);
@@ -132,10 +138,11 @@ public class KidsModeTaskOrganizerTest extends ShellTestCase {
doReturn(false).when(mObserver).isEnabled();
mOrganizer.updateKidsModeState();
-
verify(mOrganizer, times(1)).disable();
verify(mOrganizer, times(1)).unregisterOrganizer();
verify(mOrganizer, times(1)).deleteRootTask(rootTask.token);
+ verify(mOrganizer, times(1))
+ .setOrientationRequestPolicy(eq(false), any(), any());
assertThat(mOrganizer.mLaunchRootLeash).isNull();
assertThat(mOrganizer.mLaunchRootTask).isNull();
}
diff --git a/packages/CompanionDeviceManager/res/drawable/btn_negative_multiple_devices.xml b/packages/CompanionDeviceManager/res/drawable/btn_negative_multiple_devices.xml
index ebe16a7a14e5..e6ac209e7add 100644
--- a/packages/CompanionDeviceManager/res/drawable/btn_negative_multiple_devices.xml
+++ b/packages/CompanionDeviceManager/res/drawable/btn_negative_multiple_devices.xml
@@ -18,8 +18,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/transparent" />
- <corners android:topLeftRadius="16dp" android:topRightRadius="16dp"
- android:bottomLeftRadius="16dp" android:bottomRightRadius="16dp"/>
+ <corners android:radius="24dp" />
<stroke
android:width="1dp"
android:color="@android:color/system_accent1_600" />
diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
index 22805f62c449..d1d2c70134c6 100644
--- a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
@@ -123,21 +123,30 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:orientation="horizontal"
android:gravity="bottom|end"
- android:orientation="vertical"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp">
<!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. -->
+ <LinearLayout
+ android:id="@+id/negative_multiple_devices_layout"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:gravity="center"
+ android:visibility="gone">
+
+ <Button
+ android:id="@+id/btn_negative_multiple_devices"
+ style="@style/NegativeButtonMultipleDevices"
+ android:textColor="?android:textColorPrimary"
+ android:visibility="gone"
+ android:duplicateParentState="true"
+ android:clickable="false"
+ android:text="@string/consent_no" />
+
+ </LinearLayout>
- <Button
- android:id="@+id/btn_negative_multiple_devices"
- style="@style/NegativeButtonMultipleDevices"
- android:textColor="?android:textColorPrimary"
- android:visibility="gone"
- android:layout_marginTop="12dp"
- android:layout_marginBottom="12dp"
- android:text="@string/consent_no" />
</LinearLayout>
</LinearLayout>
diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml
index 3c75cd5b8668..b167377eabf7 100644
--- a/packages/CompanionDeviceManager/res/values/styles.xml
+++ b/packages/CompanionDeviceManager/res/values/styles.xml
@@ -94,12 +94,12 @@
<style name="NegativeButtonMultipleDevices"
parent="@android:style/Widget.Material.Button.Colored">
- <item name="android:layout_width">100dp</item>
+ <item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">36dp</item>
<item name="android:textAllCaps">false</item>
<item name="android:textSize">14sp</item>
- <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
<item name="android:background">@drawable/btn_negative_multiple_devices</item>
+ <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
</style>
<style name="DeviceListBorder">
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 8316f9df323f..99b776cb939f 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -156,6 +156,9 @@ public class CompanionDeviceActivity extends FragmentActivity implements
private ConstraintLayout mConstraintList;
// Only present for self-managed association requests.
private RelativeLayout mVendorHeader;
+ // A linearLayout for mButtonNotAllowMultipleDevices, user will press this layout instead
+ // of the button for accessibility.
+ private LinearLayout mNotAllowMultipleDevicesLayout;
// The recycler view is only shown for multiple-device regular association request, after
// at least one matching device is found.
@@ -327,10 +330,11 @@ public class CompanionDeviceActivity extends FragmentActivity implements
mButtonAllow = findViewById(R.id.btn_positive);
mButtonNotAllow = findViewById(R.id.btn_negative);
mButtonNotAllowMultipleDevices = findViewById(R.id.btn_negative_multiple_devices);
+ mNotAllowMultipleDevicesLayout = findViewById(R.id.negative_multiple_devices_layout);
mButtonAllow.setOnClickListener(this::onPositiveButtonClick);
mButtonNotAllow.setOnClickListener(this::onNegativeButtonClick);
- mButtonNotAllowMultipleDevices.setOnClickListener(this::onNegativeButtonClick);
+ mNotAllowMultipleDevicesLayout.setOnClickListener(this::onNegativeButtonClick);
mVendorHeaderButton.setOnClickListener(this::onShowHelperDialog);
@@ -617,6 +621,7 @@ public class CompanionDeviceActivity extends FragmentActivity implements
mButtonNotAllow.setVisibility(View.GONE);
mDeviceListRecyclerView.setVisibility(View.VISIBLE);
mButtonNotAllowMultipleDevices.setVisibility(View.VISIBLE);
+ mNotAllowMultipleDevicesLayout.setVisibility(View.VISIBLE);
mConstraintList.setVisibility(View.VISIBLE);
mMultipleDeviceSpinner.setVisibility(View.VISIBLE);
}
diff --git a/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java b/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java
index a45e853a3255..60ec91508930 100644
--- a/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java
+++ b/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java
@@ -55,11 +55,15 @@ public class EmergencyNumberUtils {
public static final String METHOD_NAME_SET_EMERGENCY_NUMBER_OVERRIDE =
"SET_EMERGENCY_NUMBER_OVERRIDE";
public static final String METHOD_NAME_SET_EMERGENCY_GESTURE = "SET_EMERGENCY_GESTURE";
+ public static final String METHOD_NAME_SET_EMERGENCY_GESTURE_UI_SHOWING =
+ "SET_EMERGENCY_GESTURE_UI_SHOWING";
public static final String METHOD_NAME_SET_EMERGENCY_SOUND = "SET_EMERGENCY_SOUND";
public static final String METHOD_NAME_GET_EMERGENCY_GESTURE_ENABLED = "GET_EMERGENCY_GESTURE";
public static final String METHOD_NAME_GET_EMERGENCY_GESTURE_SOUND_ENABLED =
"GET_EMERGENCY_SOUND";
public static final String EMERGENCY_GESTURE_CALL_NUMBER = "emergency_gesture_call_number";
+ public static final String EMERGENCY_GESTURE_UI_SHOWING_VALUE =
+ "emergency_gesture_ui_showing_value";
public static final String EMERGENCY_SETTING_VALUE = "emergency_setting_value";
public static final int EMERGENCY_SETTING_ON = 1;
public static final int EMERGENCY_SETTING_OFF = 0;
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 9dc37c9267db..8b38debef942 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -195,7 +195,6 @@ android_library {
"lottie",
"LowLightDreamLib",
"motion_tool_lib",
- "renderscript_toolkit",
],
manifest: "AndroidManifest.xml",
@@ -329,7 +328,6 @@ android_library {
"WindowManager-Shell",
"LowLightDreamLib",
"motion_tool_lib",
- "renderscript_toolkit",
"androidx.core_core-animation-testing-nodeps",
"androidx.compose.ui_ui",
],
@@ -370,6 +368,7 @@ android_library {
plugins: ["dagger2-compiler"],
lint: {
test: true,
+ extra_check_modules: ["SystemUILintChecker"],
},
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 4290ca0d0982..650d5fabeb85 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -27,6 +27,7 @@
<!-- Used to read wallpaper -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.READ_WALLPAPER_INTERNAL" />
<!-- Used to read storage for all users -->
<uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" />
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
index 0e89dcdaf142..7277392f1841 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
@@ -29,6 +29,8 @@ import static com.android.systemui.accessibility.accessibilitymenu.Accessibility
import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_TOGGLE_MENU;
import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.PACKAGE_NAME;
+import static com.google.common.truth.Truth.assertThat;
+
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Instrumentation;
import android.app.UiAutomation;
@@ -39,6 +41,7 @@ import android.content.IntentFilter;
import android.hardware.display.BrightnessInfo;
import android.hardware.display.DisplayManager;
import android.media.AudioManager;
+import android.os.PowerManager;
import android.provider.Settings;
import android.util.Log;
import android.view.accessibility.AccessibilityManager;
@@ -375,4 +378,26 @@ public class AccessibilityMenuServiceTest {
() -> sLastGlobalAction.compareAndSet(
GLOBAL_ACTION_TAKE_SCREENSHOT, NO_GLOBAL_ACTION));
}
+
+ @Test
+ public void testOnScreenLock_closesMenu() throws Throwable {
+ openMenu();
+ Context context = sInstrumentation.getTargetContext();
+ PowerManager powerManager = context.getSystemService(PowerManager.class);
+
+ assertThat(powerManager).isNotNull();
+ assertThat(powerManager.isInteractive()).isTrue();
+
+ sUiAutomation.performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN);
+ TestUtils.waitUntil("Screen did not become locked",
+ TIMEOUT_UI_CHANGE_S,
+ () -> !powerManager.isInteractive());
+
+ sUiAutomation.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+ TestUtils.waitUntil("Screen did not wake up",
+ TIMEOUT_UI_CHANGE_S,
+ () -> powerManager.isInteractive());
+
+ assertThat(isMenuVisible()).isFalse();
+ }
}
diff --git a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
index 5b2ec483f50e..0cd062383570 100644
--- a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
@@ -14,22 +14,15 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<FrameLayout
+<com.android.systemui.animation.view.LaunchableImageView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:paddingVertical="@dimen/dream_overlay_complication_home_controls_padding">
-
- <com.android.systemui.animation.view.LaunchableImageView
- android:id="@+id/home_controls_chip"
- android:layout_height="@dimen/dream_overlay_bottom_affordance_height"
- android:layout_width="@dimen/dream_overlay_bottom_affordance_width"
- android:layout_gravity="bottom|start"
- android:padding="@dimen/dream_overlay_bottom_affordance_padding"
- android:background="@drawable/dream_overlay_bottom_affordance_bg"
- android:scaleType="fitCenter"
- android:tint="?android:attr/textColorPrimary"
- android:src="@drawable/controls_icon"
- android:contentDescription="@string/quick_controls_title" />
-
-</FrameLayout>
+ android:id="@+id/home_controls_chip"
+ android:layout_height="@dimen/dream_overlay_bottom_affordance_height"
+ android:layout_width="@dimen/dream_overlay_bottom_affordance_width"
+ android:layout_gravity="bottom|start"
+ android:padding="@dimen/dream_overlay_bottom_affordance_padding"
+ android:background="@drawable/dream_overlay_bottom_affordance_bg"
+ android:scaleType="fitCenter"
+ android:tint="?android:attr/textColorPrimary"
+ android:src="@drawable/controls_icon"
+ android:contentDescription="@string/quick_controls_title" />
diff --git a/packages/SystemUI/res/layout/dream_overlay_media_entry_chip.xml b/packages/SystemUI/res/layout/dream_overlay_media_entry_chip.xml
index 50f3ffcaf968..b75c638904d8 100644
--- a/packages/SystemUI/res/layout/dream_overlay_media_entry_chip.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_media_entry_chip.xml
@@ -17,13 +17,12 @@
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/media_entry_chip"
- android:layout_height="@dimen/keyguard_affordance_fixed_height"
- android:layout_width="@dimen/keyguard_affordance_fixed_width"
+ android:layout_height="@dimen/dream_overlay_bottom_affordance_height"
+ android:layout_width="@dimen/dream_overlay_bottom_affordance_width"
android:layout_gravity="bottom|start"
- android:scaleType="center"
+ android:scaleType="fitCenter"
+ android:padding="@dimen/dream_overlay_bottom_affordance_padding"
android:tint="?android:attr/textColorPrimary"
android:src="@drawable/ic_music_note"
- android:background="@drawable/keyguard_bottom_affordance_bg"
- android:layout_marginStart="@dimen/keyguard_affordance_horizontal_offset"
- android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
+ android:background="@drawable/dream_overlay_bottom_affordance_bg"
android:contentDescription="@string/controls_media_title" />
diff --git a/packages/SystemUI/res/layout/media_recommendation_view.xml b/packages/SystemUI/res/layout/media_recommendation_view.xml
index a4aeba1dbcd6..e63aa211f9f1 100644
--- a/packages/SystemUI/res/layout/media_recommendation_view.xml
+++ b/packages/SystemUI/res/layout/media_recommendation_view.xml
@@ -31,8 +31,10 @@
<!-- App icon -->
<com.android.internal.widget.CachingIconView
android:id="@+id/media_rec_app_icon"
- android:layout_width="@dimen/qs_media_rec_icon_top_margin"
- android:layout_height="@dimen/qs_media_rec_icon_top_margin"
+ android:layout_width="@dimen/qs_media_rec_album_icon_size"
+ android:layout_height="@dimen/qs_media_rec_album_icon_size"
+ android:minWidth="@dimen/qs_media_rec_album_icon_size"
+ android:minHeight="@dimen/qs_media_rec_album_icon_size"
android:layout_marginStart="@dimen/qs_media_info_spacing"
android:layout_marginTop="@dimen/qs_media_info_spacing"/>
diff --git a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
index aa655e6b3564..9304ff72f054 100644
--- a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
+++ b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
@@ -40,6 +40,8 @@
android:id="@+id/recommendation_card_icon"
android:layout_width="@dimen/qs_media_app_icon_size"
android:layout_height="@dimen/qs_media_app_icon_size"
+ android:minWidth="@dimen/qs_media_app_icon_size"
+ android:minHeight="@dimen/qs_media_app_icon_size"
android:layout_marginStart="@dimen/qs_media_padding"
android:layout_marginTop="@dimen/qs_media_rec_icon_top_margin"
app:layout_constraintStart_toStartOf="parent"
@@ -53,6 +55,8 @@
android:id="@+id/media_cover1"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:minWidth="@dimen/qs_media_rec_album_size"
+ android:minHeight="@dimen/qs_media_rec_album_size"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:adjustViewBounds="true"
@@ -80,6 +84,8 @@
android:id="@+id/media_cover2"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:minWidth="@dimen/qs_media_rec_album_size"
+ android:minHeight="@dimen/qs_media_rec_album_size"
android:adjustViewBounds="true"
android:background="@drawable/bg_smartspace_media_item"
style="@style/MediaPlayer.Recommendation.Album"
@@ -105,6 +111,8 @@
android:id="@+id/media_cover3"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:minWidth="@dimen/qs_media_rec_album_size"
+ android:minHeight="@dimen/qs_media_rec_album_size"
android:adjustViewBounds="true"
android:background="@drawable/bg_smartspace_media_item"
style="@style/MediaPlayer.Recommendation.Album"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ab304aae39f9..ff86c595b19b 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1095,6 +1095,7 @@
<!-- Size of Smartspace media recommendations cards in the QSPanel carousel -->
<dimen name="qs_media_rec_icon_top_margin">16dp</dimen>
+ <dimen name="qs_media_rec_album_icon_size">16dp</dimen>
<dimen name="qs_media_rec_album_size">88dp</dimen>
<dimen name="qs_media_rec_album_width">110dp</dimen>
<dimen name="qs_media_rec_album_height_expanded">108dp</dimen>
@@ -1632,7 +1633,6 @@
<dimen name="dream_overlay_bottom_affordance_padding">14dp</dimen>
<dimen name="dream_overlay_complication_clock_time_text_size">86dp</dimen>
<dimen name="dream_overlay_complication_clock_time_translation_y">28dp</dimen>
- <dimen name="dream_overlay_complication_home_controls_padding">28dp</dimen>
<dimen name="dream_overlay_complication_clock_subtitle_text_size">24sp</dimen>
<dimen name="dream_overlay_complication_preview_text_size">36sp</dimen>
<dimen name="dream_overlay_complication_preview_icon_padding">28dp</dimen>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 6596ed2cbb05..2fb1592dfe15 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -695,6 +695,8 @@
<style name="MediaPlayer.Recommendation.AlbumContainer.Updated">
<item name="android:layout_width">@dimen/qs_media_rec_album_width</item>
+ <item name="android:minWidth">@dimen/qs_media_rec_album_width</item>
+ <item name="android:minHeight">@dimen/qs_media_rec_album_height_collapsed</item>
<item name="android:background">@drawable/qs_media_light_source</item>
<item name="android:layout_marginTop">@dimen/qs_media_info_spacing</item>
</style>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index b3a641cbe93a..0e2f8f07cb01 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -4125,6 +4125,19 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
KeyguardFingerprintListenModel.TABLE_HEADERS,
mFingerprintListenBuffer.toList()
).printTableData(pw);
+ } else if (mFpm != null && mFingerprintSensorProperties.isEmpty()) {
+ final int userId = mUserTracker.getUserId();
+ pw.println(" Fingerprint state (user=" + userId + ")");
+ pw.println(" mFingerprintSensorProperties.isEmpty="
+ + mFingerprintSensorProperties.isEmpty());
+ pw.println(" mFpm.isHardwareDetected="
+ + mFpm.isHardwareDetected());
+
+ new DumpsysTableLogger(
+ "KeyguardFingerprintListen",
+ KeyguardFingerprintListenModel.TABLE_HEADERS,
+ mFingerprintListenBuffer.toList()
+ ).printTableData(pw);
}
if (mFaceManager != null && !mFaceSensorProperties.isEmpty()) {
final int userId = mUserTracker.getUserId();
@@ -4155,6 +4168,19 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
KeyguardFaceListenModel.TABLE_HEADERS,
mFaceListenBuffer.toList()
).printTableData(pw);
+ } else if (mFaceManager != null && mFaceSensorProperties.isEmpty()) {
+ final int userId = mUserTracker.getUserId();
+ pw.println(" Face state (user=" + userId + ")");
+ pw.println(" mFaceSensorProperties.isEmpty="
+ + mFaceSensorProperties.isEmpty());
+ pw.println(" mFaceManager.isHardwareDetected="
+ + mFaceManager.isHardwareDetected());
+
+ new DumpsysTableLogger(
+ "KeyguardFaceListen",
+ KeyguardFingerprintListenModel.TABLE_HEADERS,
+ mFingerprintListenBuffer.toList()
+ ).printTableData(pw);
}
new DumpsysTableLogger(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 4aa985b50967..705fc8c1a8fd 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -823,7 +823,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
final Rect overlayBounds = new Rect(
0, /* left */
- mCachedDisplayInfo.getNaturalHeight() / 2, /* top */
+ 0, /* top */
mCachedDisplayInfo.getNaturalWidth(), /* right */
mCachedDisplayInfo.getNaturalHeight() /* botom */);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 19bd86aba177..e7ec3eb7e81a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -607,7 +607,6 @@ public class UdfpsController implements DozeReceiver, Dumpable {
case UNCHANGED:
if (!isWithinSensorArea(mOverlay.getOverlayView(), event.getRawX(), event.getRawY(),
true) && mActivePointerId == MotionEvent.INVALID_POINTER_ID
- && event.getActionMasked() == MotionEvent.ACTION_DOWN
&& mAlternateBouncerInteractor.isVisibleState()) {
// No pointer on sensor, forward to keyguard if alternateBouncer is visible
mKeyguardViewManager.onTouch(event);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
index 92a7094c22bf..9a0792ee7923 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
@@ -37,12 +37,12 @@ class UdfpsOverlayInteractor
@Inject
constructor(private val authController: AuthController, @Application scope: CoroutineScope) {
- /** Whether a touch should be intercepted or allowed to pass to the UdfpsOverlay */
- fun canInterceptTouchInUdfpsBounds(ev: MotionEvent): Boolean {
+ /** Whether a touch is within the under-display fingerprint sensor area */
+ fun isTouchWithinUdfpsArea(ev: MotionEvent): Boolean {
val isUdfpsEnrolled = authController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())
- val isWithinUdfpsOverlayBounds =
+ val isWithinOverlayBounds =
udfpsOverlayParams.value.overlayBounds.contains(ev.rawX.toInt(), ev.rawY.toInt())
- return !isUdfpsEnrolled || !isWithinUdfpsOverlayBounds
+ return isUdfpsEnrolled && isWithinOverlayBounds
}
/** Returns the current udfpsOverlayParams */
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
index 7f395d863c3f..82a885892b75 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
@@ -33,7 +33,6 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.CoreStartable;
-import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.controls.ControlsServiceInfo;
import com.android.systemui.controls.dagger.ControlsComponent;
@@ -157,14 +156,14 @@ public class DreamHomeControlsComplication implements Complication {
* Contains values/logic associated with the dream complication view.
*/
public static class DreamHomeControlsChipViewHolder implements ViewHolder {
- private final View mView;
+ private final ImageView mView;
private final ComplicationLayoutParams mLayoutParams;
private final DreamHomeControlsChipViewController mViewController;
@Inject
DreamHomeControlsChipViewHolder(
DreamHomeControlsChipViewController dreamHomeControlsChipViewController,
- @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) View view,
+ @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view,
@Named(DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS) ComplicationLayoutParams layoutParams
) {
mView = view;
@@ -174,7 +173,7 @@ public class DreamHomeControlsComplication implements Complication {
}
@Override
- public View getView() {
+ public ImageView getView() {
return mView;
}
@@ -187,7 +186,7 @@ public class DreamHomeControlsComplication implements Complication {
/**
* Controls behavior of the dream complication.
*/
- static class DreamHomeControlsChipViewController extends ViewController<View> {
+ static class DreamHomeControlsChipViewController extends ViewController<ImageView> {
private static final String TAG = "DreamHomeControlsCtrl";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -216,7 +215,7 @@ public class DreamHomeControlsComplication implements Complication {
@Inject
DreamHomeControlsChipViewController(
- @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) View view,
+ @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view,
ActivityStarter activityStarter,
Context context,
ControlsComponent controlsComponent,
@@ -231,10 +230,9 @@ public class DreamHomeControlsComplication implements Complication {
@Override
protected void onViewAttached() {
- final ImageView chip = mView.findViewById(R.id.home_controls_chip);
- chip.setImageResource(mControlsComponent.getTileImageId());
- chip.setContentDescription(mContext.getString(mControlsComponent.getTileTitleId()));
- chip.setOnClickListener(this::onClickHomeControls);
+ mView.setImageResource(mControlsComponent.getTileImageId());
+ mView.setContentDescription(mContext.getString(mControlsComponent.getTileTitleId()));
+ mView.setOnClickListener(this::onClickHomeControls);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java
index a7aa97f74e31..cf05d2d9cda0 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java
@@ -19,7 +19,7 @@ package com.android.systemui.dreams.complication.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import android.view.LayoutInflater;
-import android.view.View;
+import android.widget.ImageView;
import com.android.systemui.R;
import com.android.systemui.dreams.complication.DreamHomeControlsComplication;
@@ -74,8 +74,8 @@ public interface DreamHomeControlsComplicationComponent {
@Provides
@DreamHomeControlsComplicationScope
@Named(DREAM_HOME_CONTROLS_CHIP_VIEW)
- static View provideHomeControlsChipView(LayoutInflater layoutInflater) {
- return layoutInflater.inflate(R.layout.dream_overlay_home_controls_chip,
+ static ImageView provideHomeControlsChipView(LayoutInflater layoutInflater) {
+ return (ImageView) layoutInflater.inflate(R.layout.dream_overlay_home_controls_chip,
null, false);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
index 616bd81abe4d..3be42cb58f11 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
@@ -101,8 +101,8 @@ public interface RegisteredComplicationsModule {
@Named(DREAM_MEDIA_ENTRY_LAYOUT_PARAMS)
static ComplicationLayoutParams provideMediaEntryLayoutParams(@Main Resources res) {
return new ComplicationLayoutParams(
- res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
- res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
ComplicationLayoutParams.POSITION_BOTTOM
| ComplicationLayoutParams.POSITION_START,
ComplicationLayoutParams.DIRECTION_END,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 8bdd86e8c324..b08822d97b71 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -540,7 +540,7 @@ object Flags {
// TODO(b/270987164): Tracking Bug
@JvmField
- val TRACKPAD_GESTURE_FEATURES = unreleasedFlag(1205, "trackpad_gesture_features", teamfood = true)
+ val TRACKPAD_GESTURE_FEATURES = releasedFlag(1205, "trackpad_gesture_features")
// TODO(b/263826204): Tracking Bug
@JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
index eae40d61cdb6..d745a19e8549 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
@@ -46,6 +46,7 @@ constructor(
var legacyAlternateBouncer: LegacyAlternateBouncer? = null
var legacyAlternateBouncerVisibleTime: Long = NOT_VISIBLE
+ var receivedDownTouch = false
val isVisible: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
/**
@@ -79,6 +80,7 @@ constructor(
* @return true if the alternate bouncer was newly hidden, else false.
*/
fun hide(): Boolean {
+ receivedDownTouch = false
return if (isModernAlternateBouncerEnabled) {
val wasAlternateBouncerVisible = isVisibleState()
bouncerRepository.setAlternateVisible(false)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 00e5aacdedae..5c2758bd4cfc 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -151,7 +151,7 @@ public class MediaControlPanel {
private static final float REC_MEDIA_COVER_SCALE_FACTOR = 1.25f;
private static final float MEDIA_SCRIM_START_ALPHA = 0.25f;
private static final float MEDIA_REC_SCRIM_START_ALPHA = 0.15f;
- private static final float MEDIA_PLAYER_SCRIM_END_ALPHA = 0.9f;
+ private static final float MEDIA_PLAYER_SCRIM_END_ALPHA = 1.0f;
private static final float MEDIA_REC_SCRIM_END_ALPHA = 1.0f;
private static final Intent SETTINGS_INTENT = new Intent(ACTION_MEDIA_CONTROLS_SETTINGS);
@@ -1373,7 +1373,8 @@ public class MediaControlPanel {
itemIndex
);
} else {
- mediaCoverImageView.setImageIcon(recommendation.getIcon());
+ mediaCoverImageView.post(
+ () -> mediaCoverImageView.setImageIcon(recommendation.getIcon()));
}
// Set up the media item's click listener if applicable.
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index ade743b3843e..5f6f158277d7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -37,14 +37,12 @@ import com.android.keyguard.AuthKeyguardMessageArea;
import com.android.keyguard.LockIconViewController;
import com.android.keyguard.dagger.KeyguardBouncerComponent;
import com.android.systemui.R;
-import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.compose.ComposeFacade;
import com.android.systemui.dock.DockManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
@@ -94,8 +92,6 @@ public class NotificationShadeWindowViewController {
private final AmbientState mAmbientState;
private final PulsingGestureListener mPulsingGestureListener;
private final NotificationInsetsController mNotificationInsetsController;
- private final AlternateBouncerInteractor mAlternateBouncerInteractor;
- private final UdfpsOverlayInteractor mUdfpsOverlayInteractor;
private final boolean mIsTrackpadCommonEnabled;
private GestureDetector mPulsingWakeupGestureHandler;
private View mBrightnessMirror;
@@ -145,8 +141,6 @@ public class NotificationShadeWindowViewController {
PulsingGestureListener pulsingGestureListener,
KeyguardBouncerViewModel keyguardBouncerViewModel,
KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory,
- AlternateBouncerInteractor alternateBouncerInteractor,
- UdfpsOverlayInteractor udfpsOverlayInteractor,
KeyguardTransitionInteractor keyguardTransitionInteractor,
PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
FeatureFlags featureFlags,
@@ -170,8 +164,6 @@ public class NotificationShadeWindowViewController {
mAmbientState = ambientState;
mPulsingGestureListener = pulsingGestureListener;
mNotificationInsetsController = notificationInsetsController;
- mAlternateBouncerInteractor = alternateBouncerInteractor;
- mUdfpsOverlayInteractor = udfpsOverlayInteractor;
mIsTrackpadCommonEnabled = featureFlags.isEnabled(TRACKPAD_GESTURE_COMMON);
// This view is not part of the newly inflated expanded status bar.
@@ -269,6 +261,9 @@ public class NotificationShadeWindowViewController {
mFalsingCollector.onTouchEvent(ev);
mPulsingWakeupGestureHandler.onTouchEvent(ev);
+ if (mStatusBarKeyguardViewManager.dispatchTouchEvent(ev)) {
+ return true;
+ }
if (mBrightnessMirror != null
&& mBrightnessMirror.getVisibility() == View.VISIBLE) {
// Disallow new pointers while the brightness mirror is visible. This is so that
@@ -343,9 +338,10 @@ public class NotificationShadeWindowViewController {
return true;
}
- if (mAlternateBouncerInteractor.isVisibleState()) {
- // If using UDFPS, don't intercept touches that are within its overlay bounds
- return mUdfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(ev);
+ if (mStatusBarKeyguardViewManager.shouldInterceptTouchEvent(ev)) {
+ // Don't allow touches to proceed to underlying views if alternate
+ // bouncer is showing
+ return true;
}
if (mLockIconViewController.onInterceptTouchEvent(ev)) {
@@ -381,10 +377,8 @@ public class NotificationShadeWindowViewController {
handled = !mService.isPulsing();
}
- if (mAlternateBouncerInteractor.isVisibleState()) {
- // eat the touch
- mStatusBarKeyguardViewManager.onTouch(ev);
- handled = true;
+ if (mStatusBarKeyguardViewManager.onTouch(ev)) {
+ return true;
}
if ((mDragDownHelper.isDragDownEnabled() && !handled)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
index dd713ae1c637..750272d65659 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
@@ -21,17 +21,20 @@ import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Point
import android.graphics.Rect
+import android.renderscript.Allocation
+import android.renderscript.Element
+import android.renderscript.RenderScript
+import android.renderscript.ScriptIntrinsicBlur
import android.util.Log
import android.util.MathUtils
import com.android.internal.graphics.ColorUtils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.MediaNotificationProcessor
-import com.google.android.renderscript.Toolkit
import javax.inject.Inject
private const val TAG = "MediaArtworkProcessor"
private const val COLOR_ALPHA = (255 * 0.7f).toInt()
-private const val BLUR_RADIUS = 25
+private const val BLUR_RADIUS = 25f
private const val DOWNSAMPLE = 6
@SysUISingleton
@@ -44,6 +47,10 @@ class MediaArtworkProcessor @Inject constructor() {
if (mArtworkCache != null) {
return mArtworkCache
}
+ val renderScript = RenderScript.create(context)
+ val blur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript))
+ var input: Allocation? = null
+ var output: Allocation? = null
var inBitmap: Bitmap? = null
try {
@Suppress("DEPRECATION")
@@ -59,8 +66,18 @@ class MediaArtworkProcessor @Inject constructor() {
inBitmap = oldIn.copy(Bitmap.Config.ARGB_8888, false /* isMutable */)
oldIn.recycle()
}
+ val outBitmap = Bitmap.createBitmap(inBitmap.width, inBitmap.height,
+ Bitmap.Config.ARGB_8888)
+
+ input = Allocation.createFromBitmap(renderScript, inBitmap,
+ Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_GRAPHICS_TEXTURE)
+ output = Allocation.createFromBitmap(renderScript, outBitmap)
+
+ blur.setRadius(BLUR_RADIUS)
+ blur.setInput(input)
+ blur.forEach(output)
+ output.copyTo(outBitmap)
- val outBitmap = Toolkit.blur(inBitmap, BLUR_RADIUS)
val swatch = MediaNotificationProcessor.findBackgroundSwatch(artwork)
val canvas = Canvas(outBitmap)
@@ -70,6 +87,9 @@ class MediaArtworkProcessor @Inject constructor() {
Log.e(TAG, "error while processing artwork", ex)
return null
} finally {
+ input?.destroy()
+ output?.destroy()
+ blur.destroy()
inBitmap?.recycle()
}
}
@@ -78,4 +98,4 @@ class MediaArtworkProcessor @Inject constructor() {
mArtworkCache?.recycle()
mArtworkCache = null
}
-}
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 69b683b9d054..06d0758c90eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -54,6 +54,7 @@ import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.KeyguardViewController;
import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dreams.DreamOverlayStateController;
@@ -282,6 +283,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
private boolean mIsModernAlternateBouncerEnabled;
private boolean mIsBackAnimationEnabled;
+ private final boolean mUdfpsNewTouchDetectionEnabled;
+ private final UdfpsOverlayInteractor mUdfpsOverlayInteractor;
private OnDismissAction mAfterKeyguardGoneAction;
private Runnable mKeyguardGoneCancelAction;
@@ -336,7 +339,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
PrimaryBouncerCallbackInteractor primaryBouncerCallbackInteractor,
PrimaryBouncerInteractor primaryBouncerInteractor,
BouncerView primaryBouncerView,
- AlternateBouncerInteractor alternateBouncerInteractor) {
+ AlternateBouncerInteractor alternateBouncerInteractor,
+ UdfpsOverlayInteractor udfpsOverlayInteractor
+ ) {
mContext = context;
mViewMediatorCallback = callback;
mLockPatternUtils = lockPatternUtils;
@@ -362,6 +367,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mAlternateBouncerInteractor = alternateBouncerInteractor;
mIsBackAnimationEnabled =
featureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM);
+ mUdfpsNewTouchDetectionEnabled = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION);
+ mUdfpsOverlayInteractor = udfpsOverlayInteractor;
}
@Override
@@ -1443,16 +1450,48 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
}
/**
+ * An opportunity for the AlternateBouncer to handle the touch instead of sending
+ * the touch to NPVC child views.
+ * @return true if the alternate bouncer should consime the touch and prevent it from
+ * going to its child views
+ */
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ if (shouldInterceptTouchEvent(event)
+ && !mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(event)) {
+ onTouch(event);
+ }
+ return shouldInterceptTouchEvent(event);
+ }
+
+ /**
+ * Whether the touch should be intercepted by the AlternateBouncer before going to the
+ * notification shade's child views.
+ */
+ public boolean shouldInterceptTouchEvent(MotionEvent event) {
+ return mAlternateBouncerInteractor.isVisibleState();
+ }
+
+ /**
* For any touches on the NPVC, show the primary bouncer if the alternate bouncer is currently
* showing.
*/
public boolean onTouch(MotionEvent event) {
- boolean handledTouch = false;
- if (event.getAction() == MotionEvent.ACTION_UP
- && mAlternateBouncerInteractor.isVisibleState()
- && mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()) {
- showPrimaryBouncer(true);
- handledTouch = true;
+ boolean handleTouch = shouldInterceptTouchEvent(event);
+ if (handleTouch) {
+ final boolean actionDown = event.getActionMasked() == MotionEvent.ACTION_DOWN;
+ final boolean actionDownThenUp = mAlternateBouncerInteractor.getReceivedDownTouch()
+ && event.getActionMasked() == MotionEvent.ACTION_UP;
+ final boolean udfpsOverlayWillForwardEventsOutsideNotificationShade =
+ mUdfpsNewTouchDetectionEnabled && mKeyguardUpdateManager.isUdfpsEnrolled();
+ final boolean actionOutsideShouldDismissAlternateBouncer =
+ event.getActionMasked() == MotionEvent.ACTION_OUTSIDE
+ && !udfpsOverlayWillForwardEventsOutsideNotificationShade;
+ if (actionDown) {
+ mAlternateBouncerInteractor.setReceivedDownTouch(true);
+ } else if ((actionDownThenUp || actionOutsideShouldDismissAlternateBouncer)
+ && mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()) {
+ showPrimaryBouncer(true);
+ }
}
// Forward NPVC touches to callbacks in case they want to respond to touches
@@ -1460,7 +1499,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
callback.onTouch(event);
}
- return handledTouch;
+ return handleTouch;
}
/** Update keyguard position based on a tapped X coordinate. */
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
index a062e7b2db50..492f2318fec6 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
@@ -77,8 +77,11 @@ public class WalletScreenController implements
private final FalsingManager mFalsingManager;
private final UiEventLogger mUiEventLogger;
- @VisibleForTesting String mSelectedCardId;
- @VisibleForTesting boolean mIsDismissed;
+
+ @VisibleForTesting
+ String mSelectedCardId;
+ @VisibleForTesting
+ boolean mIsDismissed;
public WalletScreenController(
Context context,
@@ -124,9 +127,20 @@ public class WalletScreenController implements
}
Log.i(TAG, "Successfully retrieved wallet cards.");
List<WalletCard> walletCards = response.getWalletCards();
- List<WalletCardViewInfo> data = new ArrayList<>(walletCards.size());
+
+ boolean allUnknown = true;
+ for (WalletCard card : walletCards) {
+ if (card.getCardType() != WalletCard.CARD_TYPE_UNKNOWN) {
+ allUnknown = false;
+ break;
+ }
+ }
+
+ List<WalletCardViewInfo> paymentCardData = new ArrayList<>();
for (WalletCard card : walletCards) {
- data.add(new QAWalletCardViewInfo(mContext, card));
+ if (allUnknown || card.getCardType() == WalletCard.CARD_TYPE_PAYMENT) {
+ paymentCardData.add(new QAWalletCardViewInfo(mContext, card));
+ }
}
// Get on main thread for UI updates.
@@ -134,18 +148,18 @@ public class WalletScreenController implements
if (mIsDismissed) {
return;
}
- if (data.isEmpty()) {
+ if (paymentCardData.isEmpty()) {
showEmptyStateView();
} else {
int selectedIndex = response.getSelectedIndex();
- if (selectedIndex >= data.size()) {
+ if (selectedIndex >= paymentCardData.size()) {
Log.w(TAG, "Invalid selected card index, showing empty state.");
showEmptyStateView();
} else {
boolean isUdfpsEnabled = mKeyguardUpdateMonitor.isUdfpsEnrolled()
&& mKeyguardUpdateMonitor.isFingerprintDetectionRunning();
mWalletView.showCardCarousel(
- data,
+ paymentCardData,
selectedIndex,
!mKeyguardStateController.isUnlocked(),
isUdfpsEnabled);
@@ -213,7 +227,6 @@ public class WalletScreenController implements
}
-
@Override
public void onCardClicked(@NonNull WalletCardViewInfo cardInfo) {
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
new file mode 100644
index 000000000000..eb86c0590018
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -0,0 +1,131 @@
+/*
+ * 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.keyguard
+
+import android.telephony.PinResult
+import android.telephony.TelephonyManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.LayoutInflater
+import androidx.test.filters.SmallTest
+import com.android.internal.util.LatencyTracker
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.util.mockito.any
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class KeyguardSimPinViewControllerTest : SysuiTestCase() {
+ private lateinit var simPinView: KeyguardSimPinView
+ private lateinit var underTest: KeyguardSimPinViewController
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var securityMode: KeyguardSecurityModel.SecurityMode
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
+ @Mock private lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
+ @Mock private lateinit var latencyTracker: LatencyTracker
+ @Mock private lateinit var liftToActivateListener: LiftToActivateListener
+ @Mock private lateinit var telephonyManager: TelephonyManager
+ @Mock private lateinit var falsingCollector: FalsingCollector
+ @Mock private lateinit var emergencyButtonController: EmergencyButtonController
+ @Mock
+ private lateinit var keyguardMessageAreaController:
+ KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ `when`(messageAreaControllerFactory.create(Mockito.any(KeyguardMessageArea::class.java)))
+ .thenReturn(keyguardMessageAreaController)
+ `when`(telephonyManager.createForSubscriptionId(anyInt())).thenReturn(telephonyManager)
+ `when`(telephonyManager.supplyIccLockPin(anyString()))
+ .thenReturn(mock(PinResult::class.java))
+ simPinView =
+ LayoutInflater.from(context).inflate(R.layout.keyguard_sim_pin_view, null)
+ as KeyguardSimPinView
+ underTest =
+ KeyguardSimPinViewController(
+ simPinView,
+ keyguardUpdateMonitor,
+ securityMode,
+ lockPatternUtils,
+ keyguardSecurityCallback,
+ messageAreaControllerFactory,
+ latencyTracker,
+ liftToActivateListener,
+ telephonyManager,
+ falsingCollector,
+ emergencyButtonController
+ )
+ underTest.init()
+ }
+
+ @Test
+ fun onViewAttached() {
+ underTest.onViewAttached()
+ }
+
+ @Test
+ fun onViewDetached() {
+ underTest.onViewDetached()
+ }
+
+ @Test
+ fun onResume() {
+ underTest.onResume(KeyguardSecurityView.VIEW_REVEALED)
+ verify(keyguardUpdateMonitor)
+ .registerCallback(any(KeyguardUpdateMonitorCallback::class.java))
+ }
+
+ @Test
+ fun onPause() {
+ underTest.onPause()
+ verify(keyguardUpdateMonitor).removeCallback(any(KeyguardUpdateMonitorCallback::class.java))
+ }
+
+ @Test
+ fun startAppearAnimation() {
+ underTest.startAppearAnimation()
+ verify(keyguardMessageAreaController)
+ .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
+ }
+
+ @Test
+ fun startDisappearAnimation() {
+ underTest.startDisappearAnimation {}
+ }
+
+ @Test
+ fun resetState() {
+ underTest.resetState()
+ verify(keyguardMessageAreaController).setMessage("")
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
new file mode 100644
index 000000000000..2dcca55b9318
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
@@ -0,0 +1,132 @@
+/*
+ * 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.keyguard
+
+import android.telephony.PinResult
+import android.telephony.TelephonyManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.LayoutInflater
+import androidx.test.filters.SmallTest
+import com.android.internal.util.LatencyTracker
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.util.mockito.any
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class KeyguardSimPukViewControllerTest : SysuiTestCase() {
+ private lateinit var simPukView: KeyguardSimPukView
+ private lateinit var underTest: KeyguardSimPukViewController
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var securityMode: KeyguardSecurityModel.SecurityMode
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
+ @Mock private lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
+ @Mock private lateinit var latencyTracker: LatencyTracker
+ @Mock private lateinit var liftToActivateListener: LiftToActivateListener
+ @Mock private lateinit var telephonyManager: TelephonyManager
+ @Mock private lateinit var falsingCollector: FalsingCollector
+ @Mock private lateinit var emergencyButtonController: EmergencyButtonController
+ @Mock
+ private lateinit var keyguardMessageAreaController:
+ KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ Mockito.`when`(
+ messageAreaControllerFactory.create(Mockito.any(KeyguardMessageArea::class.java))
+ )
+ .thenReturn(keyguardMessageAreaController)
+ Mockito.`when`(telephonyManager.createForSubscriptionId(Mockito.anyInt()))
+ .thenReturn(telephonyManager)
+ Mockito.`when`(telephonyManager.supplyIccLockPuk(anyString(), anyString()))
+ .thenReturn(Mockito.mock(PinResult::class.java))
+ simPukView =
+ LayoutInflater.from(context).inflate(R.layout.keyguard_sim_puk_view, null)
+ as KeyguardSimPukView
+ underTest =
+ KeyguardSimPukViewController(
+ simPukView,
+ keyguardUpdateMonitor,
+ securityMode,
+ lockPatternUtils,
+ keyguardSecurityCallback,
+ messageAreaControllerFactory,
+ latencyTracker,
+ liftToActivateListener,
+ telephonyManager,
+ falsingCollector,
+ emergencyButtonController
+ )
+ underTest.init()
+ }
+
+ @Test
+ fun onViewAttached() {
+ underTest.onViewAttached()
+ Mockito.verify(keyguardUpdateMonitor)
+ .registerCallback(any(KeyguardUpdateMonitorCallback::class.java))
+ }
+
+ @Test
+ fun onViewDetached() {
+ underTest.onViewDetached()
+ Mockito.verify(keyguardUpdateMonitor)
+ .removeCallback(any(KeyguardUpdateMonitorCallback::class.java))
+ }
+
+ @Test
+ fun onResume() {
+ underTest.onResume(KeyguardSecurityView.VIEW_REVEALED)
+ }
+
+ @Test
+ fun onPause() {
+ underTest.onPause()
+ }
+
+ @Test
+ fun startAppearAnimation() {
+ underTest.startAppearAnimation()
+ Mockito.verify(keyguardMessageAreaController)
+ .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
+ }
+
+ @Test
+ fun startDisappearAnimation() {
+ underTest.startDisappearAnimation {}
+ }
+
+ @Test
+ fun resetState() {
+ underTest.resetState()
+ Mockito.verify(keyguardMessageAreaController)
+ .setMessage(context.resources.getString(R.string.kg_puk_enter_puk_hint))
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
index 87d5ae64dee8..9431d86f63e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
@@ -77,16 +77,14 @@ class UdfpsOverlayInteractorTest : SysuiTestCase() {
runCurrent()
- // Then touch should not be intercepted
- val canInterceptTrue = underTest.canInterceptTouchInUdfpsBounds(downEv)
- assertThat(canInterceptTrue).isFalse()
+ // Then touch is within udfps area
+ assertThat(underTest.isTouchWithinUdfpsArea(downEv)).isTrue()
// When touch is outside of bounds
whenever(overlayBounds.contains(downEv.x.toInt(), downEv.y.toInt())).thenReturn(false)
- // Then touch should be intercepted
- val canInterceptFalse = underTest.canInterceptTouchInUdfpsBounds(downEv)
- assertThat(canInterceptFalse).isTrue()
+ // Then touch is not within udfps area
+ assertThat(underTest.isTouchWithinUdfpsArea(downEv)).isFalse()
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
index 3312c4335ab4..aad49f9b8069 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
@@ -35,7 +35,6 @@ import android.view.View;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.view.LaunchableImageView;
import com.android.systemui.condition.SelfExecutingMonitor;
@@ -89,9 +88,6 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase {
private ArgumentCaptor<ControlsListingController.ControlsListingCallback> mCallbackCaptor;
@Mock
- private View mView;
-
- @Mock
private LaunchableImageView mHomeControlsView;
@Mock
@@ -115,7 +111,6 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase {
when(mControlsComponent.getControlsListingController()).thenReturn(
Optional.of(mControlsListingController));
when(mControlsComponent.getVisibility()).thenReturn(AVAILABLE);
- when(mView.findViewById(R.id.home_controls_chip)).thenReturn(mHomeControlsView);
mMonitor = SelfExecutingMonitor.createInstance();
}
@@ -223,7 +218,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase {
public void testClick_logsUiEvent() {
final DreamHomeControlsComplication.DreamHomeControlsChipViewController viewController =
new DreamHomeControlsComplication.DreamHomeControlsChipViewController(
- mView,
+ mHomeControlsView,
mActivityStarter,
mContext,
mControlsComponent,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index e65e903ef055..629208e130af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -26,13 +26,11 @@ import com.android.keyguard.LockIconViewController
import com.android.keyguard.dagger.KeyguardBouncerComponent
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.dock.DockManager
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
@@ -69,8 +67,8 @@ import org.mockito.Mockito.anyFloat
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -97,8 +95,6 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
@Mock private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController
@Mock private lateinit var pulsingGestureListener: PulsingGestureListener
@Mock private lateinit var notificationInsetsController: NotificationInsetsController
- @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
- @Mock private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
@Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
@Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
@Mock lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController
@@ -155,8 +151,6 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
pulsingGestureListener,
keyguardBouncerViewModel,
keyguardBouncerComponentFactory,
- alternateBouncerInteractor,
- udfpsOverlayInteractor,
keyguardTransitionInteractor,
primaryBouncerToGoneTransitionViewModel,
featureFlags,
@@ -311,17 +305,15 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
}
@Test
- fun shouldInterceptTouchEvent_downEventAlternateBouncer_ignoreIfInUdfpsOverlay() =
- testScope.runTest {
- // Down event within udfpsOverlay bounds while alternateBouncer is showing
- whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(DOWN_EVENT))
- .thenReturn(false)
- whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true)
+ fun shouldInterceptTouchEvent_statusBarKeyguardViewManagerShouldIntercept() {
+ // down event should be intercepted by keyguardViewManager
+ whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT))
+ .thenReturn(true)
- // Then touch should not be intercepted
- val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)
- assertThat(shouldIntercept).isFalse()
- }
+ // Then touch should not be intercepted
+ val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)
+ assertThat(shouldIntercept).isTrue()
+ }
@Test
fun testGetBouncerContainer() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 2dcfdde8d74c..b4b5ec126234 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -26,13 +26,11 @@ import com.android.keyguard.LockIconViewController
import com.android.keyguard.dagger.KeyguardBouncerComponent
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.dock.DockManager
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
@@ -103,14 +101,12 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
@Mock
private lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController
@Mock private lateinit var notificationInsetsController: NotificationInsetsController
- @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
@Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock
private lateinit var primaryBouncerToGoneTransitionViewModel:
PrimaryBouncerToGoneTransitionViewModel
@Captor
private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
- @Mock private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
private lateinit var underTest: NotificationShadeWindowView
private lateinit var controller: NotificationShadeWindowViewController
@@ -166,8 +162,6 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
pulsingGestureListener,
keyguardBouncerViewModel,
keyguardBouncerComponentFactory,
- alternateBouncerInteractor,
- udfpsOverlayInteractor,
keyguardTransitionInteractor,
primaryBouncerToGoneTransitionViewModel,
featureFlags,
@@ -207,8 +201,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
// WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
whenever(statusBarStateController.isDozing).thenReturn(false)
- whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true)
- whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(any())).thenReturn(true)
+ whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(any())).thenReturn(true)
whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false)
// THEN we should intercept touch
@@ -222,7 +215,8 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
// WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept
whenever(statusBarStateController.isDozing).thenReturn(false)
- whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(false)
+ whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(any()))
+ .thenReturn(false)
whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false)
// THEN we shouldn't intercept touch
@@ -236,7 +230,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
// WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
whenever(statusBarStateController.isDozing).thenReturn(false)
- whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true)
+ whenever(statusBarKeyguardViewManager.onTouch(any())).thenReturn(true)
whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false)
// THEN we should handle the touch
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 31462623ce2d..d9546877a861 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -31,6 +31,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -58,6 +59,7 @@ import com.android.keyguard.KeyguardSecurityModel;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.flags.FeatureFlags;
@@ -126,12 +128,14 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
@Mock private PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
@Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
@Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
+ @Mock private UdfpsOverlayInteractor mUdfpsOverlayInteractor;
@Mock private BouncerView mBouncerView;
@Mock private BouncerViewDelegate mBouncerViewDelegate;
@Mock private OnBackAnimationCallback mBouncerViewDelegateBackCallback;
@Mock private NotificationShadeWindowView mNotificationShadeWindowView;
@Mock private WindowInsetsController mWindowInsetsController;
@Mock private TaskbarDelegate mTaskbarDelegate;
+ @Mock private StatusBarKeyguardViewManager.KeyguardViewManagerCallback mCallback;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback
@@ -188,7 +192,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mPrimaryBouncerCallbackInteractor,
mPrimaryBouncerInteractor,
mBouncerView,
- mAlternateBouncerInteractor) {
+ mAlternateBouncerInteractor,
+ mUdfpsOverlayInteractor) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
@@ -675,7 +680,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mPrimaryBouncerCallbackInteractor,
mPrimaryBouncerInteractor,
mBouncerView,
- mAlternateBouncerInteractor) {
+ mAlternateBouncerInteractor,
+ mUdfpsOverlayInteractor) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
@@ -713,7 +719,115 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
}
@Test
- public void testAlternateBouncerToShowPrimaryBouncer_updatesScrimControllerOnce() {
+ public void handleDispatchTouchEvent_alternateBouncerNotVisible() {
+ mStatusBarKeyguardViewManager.addCallback(mCallback);
+
+ // GIVEN the alternate bouncer is visible
+ when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false);
+
+ // THEN handleDispatchTouchEvent doesn't use the touches
+ assertFalse(mStatusBarKeyguardViewManager.dispatchTouchEvent(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+ ));
+ assertFalse(mStatusBarKeyguardViewManager.dispatchTouchEvent(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)
+ ));
+ assertFalse(mStatusBarKeyguardViewManager.dispatchTouchEvent(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
+ ));
+
+ // THEN the touch is not acted upon
+ verify(mCallback, never()).onTouch(any());
+ }
+
+ @Test
+ public void handleDispatchTouchEvent_shouldInterceptTouchAndHandleTouch() {
+ mStatusBarKeyguardViewManager.addCallback(mCallback);
+
+ // GIVEN the alternate bouncer is visible
+ when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+
+ // GIVEN all touches are NOT the udfps overlay
+ when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(false);
+
+ // THEN handleDispatchTouchEvent eats/intercepts the touches so motion events aren't sent
+ // to its child views (handleDispatchTouchEvent returns true)
+ assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+ ));
+ assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)
+ ));
+ assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
+ ));
+
+ // THEN the touch is acted upon once for each dispatchTOuchEvent call
+ verify(mCallback, times(3)).onTouch(any());
+ }
+
+ @Test
+ public void handleDispatchTouchEvent_shouldInterceptTouchButNotHandleTouch() {
+ mStatusBarKeyguardViewManager.addCallback(mCallback);
+
+ // GIVEN the alternate bouncer is visible
+ when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+
+ // GIVEN all touches are within the udfps overlay
+ when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(true);
+
+ // THEN handleDispatchTouchEvent eats/intercepts the touches so motion events aren't sent
+ // to its child views (handleDispatchTouchEvent returns true)
+ assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+ ));
+ assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)
+ ));
+ assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
+ ));
+
+ // THEN the touch is NOT acted upon at the moment
+ verify(mCallback, never()).onTouch(any());
+ }
+
+ @Test
+ public void shouldInterceptTouch_alternateBouncerNotVisible() {
+ // GIVEN the alternate bouncer is not visible
+ when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false);
+
+ // THEN no motion events are intercepted
+ assertFalse(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+ ));
+ assertFalse(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)
+ ));
+ assertFalse(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
+ ));
+ }
+
+ @Test
+ public void shouldInterceptTouch_alternateBouncerVisible() {
+ // GIVEN the alternate bouncer is visible
+ when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+
+ // THEN all motion events are intercepted
+ assertTrue(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+ ));
+ assertTrue(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)
+ ));
+ assertTrue(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
+ ));
+ }
+
+ @Test
+ public void alternateBouncerToShowPrimaryBouncer_updatesScrimControllerOnce() {
// GIVEN the alternate bouncer has shown and calls to hide() will result in successfully
// hiding it
when(mAlternateBouncerInteractor.hide()).thenReturn(true);
@@ -729,30 +843,67 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
}
@Test
- public void testAlternateBouncerOnTouch_actionDown_doesNotHandleTouch() {
+ public void alternateBouncerOnTouch_actionDownThenUp_noMinTimeShown_noHideAltBouncer() {
+ reset(mAlternateBouncerInteractor);
+
+ // GIVEN the alternate bouncer has shown for a minimum amount of time
+ when(mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()).thenReturn(false);
+ when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+ when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(false);
+
+ // WHEN ACTION_DOWN and ACTION_UP touch event comes
+ boolean touchHandledDown = mStatusBarKeyguardViewManager.onTouch(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0));
+ when(mAlternateBouncerInteractor.getReceivedDownTouch()).thenReturn(true);
+ boolean touchHandledUp = mStatusBarKeyguardViewManager.onTouch(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0));
+
+ // THEN the touches are handled (doesn't let touches through to underlying views)
+ assertTrue(touchHandledDown);
+ assertTrue(touchHandledUp);
+
+ // THEN alternate bouncer does NOT attempt to hide since min showing time wasn't met
+ verify(mAlternateBouncerInteractor, never()).hide();
+ }
+
+ @Test
+ public void alternateBouncerOnTouch_actionDownThenUp_handlesTouch_hidesAltBouncer() {
+ reset(mAlternateBouncerInteractor);
+
// GIVEN the alternate bouncer has shown for a minimum amount of time
when(mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()).thenReturn(true);
when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+ when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(false);
- // WHEN ACTION_DOWN touch event comes
- boolean touchHandled = mStatusBarKeyguardViewManager.onTouch(
+ // WHEN ACTION_DOWN and ACTION_UP touch event comes
+ boolean touchHandledDown = mStatusBarKeyguardViewManager.onTouch(
MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0));
+ when(mAlternateBouncerInteractor.getReceivedDownTouch()).thenReturn(true);
+ boolean touchHandledUp = mStatusBarKeyguardViewManager.onTouch(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0));
+
+ // THEN the touches are handled
+ assertTrue(touchHandledDown);
+ assertTrue(touchHandledUp);
- // THEN the touch is not handled
- assertFalse(touchHandled);
+ // THEN alternate bouncer attempts to hide
+ verify(mAlternateBouncerInteractor).hide();
}
@Test
- public void testAlternateBouncerOnTouch_actionUp_handlesTouch() {
+ public void alternateBouncerOnTouch_actionUp_doesNotHideAlternateBouncer() {
+ reset(mAlternateBouncerInteractor);
+
// GIVEN the alternate bouncer has shown for a minimum amount of time
when(mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()).thenReturn(true);
when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+ when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(false);
- // WHEN ACTION_UP touch event comes
- boolean touchHandled = mStatusBarKeyguardViewManager.onTouch(
+ // WHEN only ACTION_UP touch event comes
+ mStatusBarKeyguardViewManager.onTouch(
MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0));
- // THEN the touch is handled
- assertTrue(touchHandled);
+ // THEN the alternateBouncer doesn't hide
+ verify(mAlternateBouncerInteractor, never()).hide();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java
index b1950eac9846..692af6a9a37b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java
@@ -22,6 +22,9 @@ import static android.view.View.VISIBLE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -63,6 +66,7 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Collections;
+import java.util.List;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -99,6 +103,8 @@ public class WalletScreenControllerTest extends SysuiTestCase {
ArgumentCaptor<PendingIntent> mIntentCaptor;
@Captor
ArgumentCaptor<QuickAccessWalletClient.OnWalletCardsRetrievedCallback> mCallbackCaptor;
+ @Captor
+ ArgumentCaptor<List<WalletCardViewInfo>> mPaymentCardDataCaptor;
private WalletScreenController mController;
private TestableLooper mTestableLooper;
@@ -107,7 +113,7 @@ public class WalletScreenControllerTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
mTestableLooper = TestableLooper.get(this);
when(mUserTracker.getUserContext()).thenReturn(mContext);
- mWalletView = new WalletView(mContext);
+ mWalletView = spy(new WalletView(mContext));
mWalletView.getCardCarousel().setExpectedViewWidth(CARD_CAROUSEL_WIDTH);
when(mWalletClient.getLogo()).thenReturn(mWalletLogo);
when(mWalletClient.getShortcutLongLabel()).thenReturn(SHORTCUT_LONG_LABEL);
@@ -430,6 +436,41 @@ public class WalletScreenControllerTest extends SysuiTestCase {
assertEquals(GONE, mWalletView.getVisibility());
}
+ @Test
+ public void onWalletCardsRetrieved_cardDataAllUnknown_showsAllCards() {
+ List<WalletCard> walletCardList = List.of(
+ createWalletCardWithType(mContext, WalletCard.CARD_TYPE_UNKNOWN),
+ createWalletCardWithType(mContext, WalletCard.CARD_TYPE_UNKNOWN),
+ createWalletCardWithType(mContext, WalletCard.CARD_TYPE_UNKNOWN));
+ GetWalletCardsResponse response = new GetWalletCardsResponse(walletCardList, 0);
+ mController.onWalletCardsRetrieved(response);
+ mTestableLooper.processAllMessages();
+
+ verify(mWalletView).showCardCarousel(mPaymentCardDataCaptor.capture(), anyInt(),
+ anyBoolean(),
+ anyBoolean());
+ List<WalletCardViewInfo> paymentCardData = mPaymentCardDataCaptor.getValue();
+ assertEquals(paymentCardData.size(), walletCardList.size());
+ }
+
+ @Test
+ public void onWalletCardsRetrieved_cardDataDifferentTypes_onlyShowsPayment() {
+ List<WalletCard> walletCardList = List.of(createWalletCardWithType(mContext,
+ WalletCard.CARD_TYPE_UNKNOWN),
+ createWalletCardWithType(mContext, WalletCard.CARD_TYPE_PAYMENT),
+ createWalletCardWithType(mContext, WalletCard.CARD_TYPE_NON_PAYMENT)
+ );
+ GetWalletCardsResponse response = new GetWalletCardsResponse(walletCardList, 0);
+ mController.onWalletCardsRetrieved(response);
+ mTestableLooper.processAllMessages();
+
+ verify(mWalletView).showCardCarousel(mPaymentCardDataCaptor.capture(), anyInt(),
+ anyBoolean(),
+ anyBoolean());
+ List<WalletCardViewInfo> paymentCardData = mPaymentCardDataCaptor.getValue();
+ assertEquals(paymentCardData.size(), 1);
+ }
+
private WalletCard createNonActiveWalletCard(Context context) {
PendingIntent pendingIntent =
PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE);
@@ -457,6 +498,15 @@ public class WalletScreenControllerTest extends SysuiTestCase {
.build();
}
+ private WalletCard createWalletCardWithType(Context context, int cardType) {
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE);
+ return new WalletCard.Builder(CARD_ID_1, cardType, createIcon(), "•••• 1234", pendingIntent)
+ .setCardIcon(createIcon())
+ .setCardLabel("Hold to reader")
+ .build();
+ }
+
private WalletCard createCrazyWalletCard(Context context, boolean hasLabel) {
PendingIntent pendingIntent =
PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE);
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 7f6ad431c601..ead59b69cbb5 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -16,6 +16,7 @@
package com.android.server.autofill;
+import static android.Manifest.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS;
import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
import static android.service.autofill.AutofillService.EXTRA_FILL_RESPONSE;
import static android.service.autofill.FillEventHistory.Event.UI_TYPE_DIALOG;
@@ -72,6 +73,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.graphics.Bitmap;
import android.graphics.Rect;
@@ -1286,8 +1288,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mSessionFlags = new SessionFlags();
mSessionFlags.mAugmentedAutofillOnly = forAugmentedAutofillOnly;
mSessionFlags.mInlineSupportedByService = mService.isInlineSuggestionsEnabledLocked();
- mSessionFlags.mClientSuggestionsEnabled =
- (mFlags & FLAG_ENABLED_CLIENT_SUGGESTIONS) != 0;
+ if (mContext.checkCallingPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS)
+ == PackageManager.PERMISSION_GRANTED) {
+ mSessionFlags.mClientSuggestionsEnabled =
+ (mFlags & FLAG_ENABLED_CLIENT_SUGGESTIONS) != 0;
+ }
setClientLocked(client);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b4e75e193b65..59a41399e18c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -176,6 +176,7 @@ import android.app.ActivityManager.PendingIntentInfo;
import android.app.ActivityManager.ProcessCapability;
import android.app.ActivityManager.RestrictionLevel;
import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityManager.UidFrozenStateChangedCallback.UidFrozenState;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerInternal.BindServiceEventListener;
import android.app.ActivityManagerInternal.BroadcastEventListener;
@@ -208,6 +209,7 @@ import android.app.IServiceConnection;
import android.app.IStopUserCallback;
import android.app.ITaskStackListener;
import android.app.IUiAutomationConnection;
+import android.app.IUidFrozenStateChangedCallback;
import android.app.IUidObserver;
import android.app.IUnsafeIntentStrictModeCallback;
import android.app.IUserSwitchObserver;
@@ -316,6 +318,7 @@ import android.os.PowerManager.ServiceType;
import android.os.PowerManagerInternal;
import android.os.Process;
import android.os.RemoteCallback;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
@@ -6527,8 +6530,12 @@ public class ActivityManagerService extends IActivityManager.Stub
@NonNull
private BackgroundStartPrivileges getBackgroundStartPrivileges(int uid) {
synchronized (mProcLock) {
+ final UidRecord uidRecord = mProcessList.getUidRecordLOSP(uid);
+ if (uidRecord == null) {
+ return BackgroundStartPrivileges.NONE;
+ }
mGetBackgroundStartPrivilegesFunctor.prepare(uid);
- mProcessList.forEachLruProcessesLOSP(false, mGetBackgroundStartPrivilegesFunctor);
+ uidRecord.forEachProcess(mGetBackgroundStartPrivilegesFunctor);
return mGetBackgroundStartPrivilegesFunctor.getResult();
}
}
@@ -7757,6 +7764,66 @@ public class ActivityManagerService extends IActivityManager.Stub
return uidRecord != null && !uidRecord.isSetIdle();
}
+ @GuardedBy("mUidFrozenStateChangedCallbackList")
+ private final RemoteCallbackList<IUidFrozenStateChangedCallback>
+ mUidFrozenStateChangedCallbackList = new RemoteCallbackList<>();
+
+ /**
+ * Register a {@link IUidFrozenStateChangedCallback} to receive Uid frozen state events.
+ *
+ * @param callback remote callback object to be registered
+ */
+ public void registerUidFrozenStateChangedCallback(
+ @NonNull IUidFrozenStateChangedCallback callback) {
+ synchronized (mUidFrozenStateChangedCallbackList) {
+ boolean registered = mUidFrozenStateChangedCallbackList.register(callback);
+ if (!registered) {
+ Slog.w(TAG, "Failed to register with RemoteCallbackList!");
+ }
+ }
+ }
+
+ /**
+ * Unregister a {@link IUidFrozenStateChangedCallback}.
+ *
+ * @param callback remote callback object to be unregistered
+ */
+ public void unregisterUidFrozenStateChangedCallback(
+ @NonNull IUidFrozenStateChangedCallback callback) {
+ synchronized (mUidFrozenStateChangedCallbackList) {
+ mUidFrozenStateChangedCallbackList.unregister(callback);
+ }
+ }
+
+ /**
+ * Notify the system that a UID has been frozen or unfrozen.
+ *
+ * @param uids The Uid(s) in question
+ * @param frozenStates Frozen state for each UID index
+ *
+ * @hide
+ */
+ public void reportUidFrozenStateChanged(@NonNull int[] uids,
+ @UidFrozenState int[] frozenStates) {
+ synchronized (mUidFrozenStateChangedCallbackList) {
+ final int n = mUidFrozenStateChangedCallbackList.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ try {
+ mUidFrozenStateChangedCallbackList.getBroadcastItem(i).onUidFrozenStateChanged(
+ uids, frozenStates);
+ } catch (RemoteException e) {
+ /*
+ * The process at the other end has died or otherwise gone away.
+ * According to spec, RemoteCallbacklist will take care of unregistering any
+ * object associated with that process - we are safe to ignore the exception
+ * here.
+ */
+ }
+ }
+ mUidFrozenStateChangedCallbackList.finishBroadcast();
+ }
+ }
+
@Override
public void setPersistentVrThread(int tid) {
mActivityTaskManager.setPersistentVrThread(tid);
@@ -16849,7 +16916,7 @@ public class ActivityManagerService extends IActivityManager.Stub
*/
public void dumpAllResources(ParcelFileDescriptor fd, PrintWriter pw) throws RemoteException {
final ArrayList<ProcessRecord> processes = new ArrayList<>();
- synchronized (mPidsSelfLocked) {
+ synchronized (this) {
processes.addAll(mProcessList.getLruProcessesLOSP());
}
for (int i = 0, size = processes.size(); i < size; i++) {
@@ -18738,13 +18805,17 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public void logFgsApiBegin(@ForegroundServiceApiType int apiType,
int uid, int pid) {
- ActivityManagerService.this.logFgsApiBegin(apiType, uid, pid);
+ synchronized (this) {
+ mServices.logFgsApiBeginLocked(apiType, uid, pid);
+ }
}
@Override
public void logFgsApiEnd(@ForegroundServiceApiType int apiType,
int uid, int pid) {
- ActivityManagerService.this.logFgsApiEnd(apiType, uid, pid);
+ synchronized (this) {
+ mServices.logFgsApiEndLocked(apiType, uid, pid);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 94d08bf7b59d..72e17d8764aa 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -1066,16 +1066,26 @@ final class ActivityManagerShellCommand extends ShellCommand {
}
@NeverCompile
- int runCompact(PrintWriter pw) {
+ int runCompact(PrintWriter pw) throws RemoteException {
ProcessRecord app;
String op = getNextArgRequired();
boolean isFullCompact = op.equals("full");
boolean isSomeCompact = op.equals("some");
if (isFullCompact || isSomeCompact) {
String processName = getNextArgRequired();
- String uid = getNextArgRequired();
synchronized (mInternal.mProcLock) {
- app = mInternal.getProcessRecordLocked(processName, Integer.parseInt(uid));
+ // Default to current user
+ int userId = mInterface.getCurrentUserId();
+ String userOpt = getNextOption();
+ if (userOpt != null && "--user".equals(userOpt)) {
+ int inputUserId = UserHandle.parseUserArg(getNextArgRequired());
+ if (inputUserId != UserHandle.USER_CURRENT) {
+ userId = inputUserId;
+ }
+ }
+ final int uid =
+ mInternal.getPackageManagerInternal().getPackageUid(processName, 0, userId);
+ app = mInternal.getProcessRecordLocked(processName, uid);
}
pw.println("Process record found pid: " + app.mPid);
if (isFullCompact) {
@@ -1101,6 +1111,28 @@ final class ActivityManagerShellCommand extends ShellCommand {
mInternal.mOomAdjuster.mCachedAppOptimizer.compactAllSystem();
}
pw.println("Finished system compaction");
+ } else if (op.equals("native")) {
+ op = getNextArgRequired();
+ isFullCompact = op.equals("full");
+ isSomeCompact = op.equals("some");
+ int pid;
+ String pidStr = getNextArgRequired();
+ try {
+ pid = Integer.parseInt(pidStr);
+ } catch (Exception e) {
+ getErrPrintWriter().println("Error: failed to parse '" + pidStr + "' as a PID");
+ return -1;
+ }
+ if (isFullCompact) {
+ mInternal.mOomAdjuster.mCachedAppOptimizer.compactNative(
+ CachedAppOptimizer.CompactProfile.FULL, pid);
+ } else if (isSomeCompact) {
+ mInternal.mOomAdjuster.mCachedAppOptimizer.compactNative(
+ CachedAppOptimizer.CompactProfile.SOME, pid);
+ } else {
+ getErrPrintWriter().println("Error: unknown compaction type '" + op + "'");
+ return -1;
+ }
} else {
getErrPrintWriter().println("Error: unknown compact command '" + op + "'");
return -1;
@@ -4018,11 +4050,17 @@ final class ActivityManagerShellCommand extends ShellCommand {
pw.println(" --allow-background-activity-starts: The receiver may start activities");
pw.println(" even if in the background.");
pw.println(" --async: Send without waiting for the completion of the receiver.");
- pw.println(" compact [some|full|system] <process_name> <Package UID>");
- pw.println(" Force process compaction.");
+ pw.println(" compact [some|full] <process_name> [--user <USER_ID>]");
+ pw.println(" Perform a single process compaction.");
pw.println(" some: execute file compaction.");
pw.println(" full: execute anon + file compaction.");
pw.println(" system: system compaction.");
+ pw.println(" compact system");
+ pw.println(" Perform a full system compaction.");
+ pw.println(" compact native [some|full] <pid>");
+ pw.println(" Perform a native compaction for process with <pid>.");
+ pw.println(" some: execute file compaction.");
+ pw.println(" full: execute anon + file compaction.");
pw.println(" instrument [-r] [-e <NAME> <VALUE>] [-p <FILE>] [-w]");
pw.println(" [--user <USER_ID> | current]");
pw.println(" [--no-hidden-api-checks [--no-test-api-access]]");
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index d09ca5cadfe7..7c84b7230816 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -843,7 +843,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub
final long sessionStart = mBatteryUsageStatsStore
.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp();
- final long sessionEnd = mStats.getStartClockTime();
+ final long sessionEnd;
+ synchronized (mStats) {
+ sessionEnd = mStats.getStartClockTime();
+ }
final BatteryUsageStatsQuery queryBeforeReset =
new BatteryUsageStatsQuery.Builder()
.setMaxStatsAgeMs(0)
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 7591057f414a..1f6573076d51 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -385,9 +385,11 @@ class BroadcastProcessQueue {
public void setProcess(@Nullable ProcessRecord app) {
this.app = app;
if (app != null) {
+ setProcessCached(app.isCached());
setProcessInstrumented(app.getActiveInstrumentation() != null);
setProcessPersistent(app.isPersistent());
} else {
+ setProcessCached(false);
setProcessInstrumented(false);
setProcessPersistent(false);
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index bef16b6f081c..bc331588a987 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -1328,7 +1328,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
synchronized (mService) {
BroadcastProcessQueue leaf = mProcessQueues.get(uid);
while (leaf != null) {
- leaf.setProcessCached(cached);
+ // Update internal state by refreshing values previously
+ // read from any known running process
+ leaf.setProcess(leaf.app);
updateQueueDeferred(leaf);
updateRunnableList(leaf);
leaf = leaf.processNameNext;
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index f4685f0f6125..3ab9e7171429 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -16,6 +16,9 @@
package com.android.server.am;
+import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN;
+import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN;
+
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_COMPACTION;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FREEZER;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
@@ -66,8 +69,6 @@ public final class CachedAppOptimizer {
// Flags stored in the DeviceConfig API.
@VisibleForTesting static final String KEY_USE_COMPACTION = "use_compaction";
@VisibleForTesting static final String KEY_USE_FREEZER = "use_freezer";
- @VisibleForTesting static final String KEY_COMPACT_ACTION_1 = "compact_action_1";
- @VisibleForTesting static final String KEY_COMPACT_ACTION_2 = "compact_action_2";
@VisibleForTesting static final String KEY_COMPACT_THROTTLE_1 = "compact_throttle_1";
@VisibleForTesting static final String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2";
@VisibleForTesting static final String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3";
@@ -99,15 +100,6 @@ public final class CachedAppOptimizer {
private static final int RSS_ANON_INDEX = 2;
private static final int RSS_SWAP_INDEX = 3;
- // Phenotype sends int configurations and we map them to the strings we'll use on device,
- // preventing a weird string value entering the kernel.
- private static final int COMPACT_ACTION_NONE = 0;
- private static final int COMPACT_ACTION_FILE = 1;
- private static final int COMPACT_ACTION_ANON = 2;
- private static final int COMPACT_ACTION_ALL = 3;
-
- private static final String COMPACT_ACTION_STRING[] = {"", "file", "anon", "all"};
-
// Keeps these flags in sync with services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
private static final int COMPACT_ACTION_FILE_FLAG = 1;
private static final int COMPACT_ACTION_ANON_FLAG = 2;
@@ -117,11 +109,11 @@ public final class CachedAppOptimizer {
private static final int FREEZE_BINDER_TIMEOUT_MS = 100;
+ @VisibleForTesting static final boolean ENABLE_FILE_COMPACT = false;
+
// Defaults for phenotype flags.
@VisibleForTesting static final Boolean DEFAULT_USE_COMPACTION = true;
@VisibleForTesting static final Boolean DEFAULT_USE_FREEZER = true;
- @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_2 = COMPACT_ACTION_ALL;
- @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_1 = COMPACT_ACTION_FILE;
@VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_1 = 5_000;
@VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_2 = 10_000;
@VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_3 = 500;
@@ -156,24 +148,17 @@ public final class CachedAppOptimizer {
@VisibleForTesting
interface ProcessDependencies {
long[] getRss(int pid);
- void performCompaction(CompactAction action, int pid) throws IOException;
+ void performCompaction(CompactProfile action, int pid) throws IOException;
}
// This indicates the compaction we want to perform
public enum CompactProfile {
+ NONE, // No compaction
SOME, // File compaction
+ ANON, // Anon compaction
FULL // File+anon compaction
}
- // Low level actions that can be performed for compaction
- // currently determined by the compaction profile
- public enum CompactAction {
- NONE, // No compaction
- FILE, // File+anon compaction
- ANON,
- ALL
- }
-
// This indicates the process OOM memory state that initiated the compaction request
public enum CompactSource { APP, PERSISTENT, BFGS }
@@ -187,6 +172,8 @@ public final class CachedAppOptimizer {
static final int COMPACT_SYSTEM_MSG = 2;
static final int SET_FROZEN_PROCESS_MSG = 3;
static final int REPORT_UNFREEZE_MSG = 4;
+ static final int COMPACT_NATIVE_MSG = 5;
+ static final int UID_FROZEN_STATE_CHANGED_MSG = 6;
// When free swap falls below this percentage threshold any full (file + anon)
// compactions will be downgraded to file only compactions to reduce pressure
@@ -240,9 +227,6 @@ public final class CachedAppOptimizer {
for (String name : properties.getKeyset()) {
if (KEY_USE_COMPACTION.equals(name)) {
updateUseCompaction();
- } else if (KEY_COMPACT_ACTION_1.equals(name)
- || KEY_COMPACT_ACTION_2.equals(name)) {
- updateCompactionActions();
} else if (KEY_COMPACT_THROTTLE_1.equals(name)
|| KEY_COMPACT_THROTTLE_2.equals(name)
|| KEY_COMPACT_THROTTLE_3.equals(name)
@@ -314,12 +298,6 @@ public final class CachedAppOptimizer {
// Configured by phenotype. Updates from the server take effect immediately.
@GuardedBy("mPhenotypeFlagLock")
- @VisibleForTesting
- volatile CompactAction mCompactActionSome = compactActionIntToAction(DEFAULT_COMPACT_ACTION_1);
- @GuardedBy("mPhenotypeFlagLock")
- @VisibleForTesting
- volatile CompactAction mCompactActionFull = compactActionIntToAction(DEFAULT_COMPACT_ACTION_2);
- @GuardedBy("mPhenotypeFlagLock")
@VisibleForTesting volatile long mCompactThrottleSomeSome = DEFAULT_COMPACT_THROTTLE_1;
@GuardedBy("mPhenotypeFlagLock")
@VisibleForTesting volatile long mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2;
@@ -542,7 +520,6 @@ public final class CachedAppOptimizer {
CACHED_APP_FREEZER_ENABLED_URI, false, mSettingsObserver);
synchronized (mPhenotypeFlagLock) {
updateUseCompaction();
- updateCompactionActions();
updateCompactionThrottles();
updateCompactStatsdSampleRate();
updateFreezerStatsdSampleRate();
@@ -587,8 +564,6 @@ public final class CachedAppOptimizer {
pw.println("CachedAppOptimizer settings");
synchronized (mPhenotypeFlagLock) {
pw.println(" " + KEY_USE_COMPACTION + "=" + mUseCompaction);
- pw.println(" " + KEY_COMPACT_ACTION_1 + "=" + mCompactActionSome);
- pw.println(" " + KEY_COMPACT_ACTION_2 + "=" + mCompactActionFull);
pw.println(" " + KEY_COMPACT_THROTTLE_1 + "=" + mCompactThrottleSomeSome);
pw.println(" " + KEY_COMPACT_THROTTLE_2 + "=" + mCompactThrottleSomeFull);
pw.println(" " + KEY_COMPACT_THROTTLE_3 + "=" + mCompactThrottleFullSome);
@@ -761,19 +736,9 @@ public final class CachedAppOptimizer {
return false;
}
- private CompactAction resolveCompactActionForProfile(CompactProfile profile) {
- CompactAction action;
- switch (profile) {
- case SOME:
- action = CompactAction.FILE;
- break;
- case FULL:
- action = CompactAction.ALL;
- break;
- default:
- action = CompactAction.NONE;
- }
- return action;
+ void compactNative(CompactProfile compactProfile, int pid) {
+ mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
+ COMPACT_NATIVE_MSG, pid, compactProfile.ordinal()));
}
private AggregatedProcessCompactionStats getPerProcessAggregatedCompactStat(
@@ -1051,18 +1016,6 @@ public final class CachedAppOptimizer {
}
@GuardedBy("mPhenotypeFlagLock")
- private void updateCompactionActions() {
- int compactAction1 = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- KEY_COMPACT_ACTION_1, DEFAULT_COMPACT_ACTION_1);
-
- int compactAction2 = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- KEY_COMPACT_ACTION_2, DEFAULT_COMPACT_ACTION_2);
-
- mCompactActionSome = compactActionIntToAction(compactAction1);
- mCompactActionFull = compactActionIntToAction(compactAction2);
- }
-
- @GuardedBy("mPhenotypeFlagLock")
private void updateCompactionThrottles() {
boolean useThrottleDefaults = false;
// TODO: improve efficiency by calling DeviceConfig only once for all flags.
@@ -1235,14 +1188,6 @@ public final class CachedAppOptimizer {
return true;
}
- static CompactAction compactActionIntToAction(int action) {
- if (action < 0 || action >= CompactAction.values().length) {
- return CompactAction.NONE;
- }
-
- return CompactAction.values()[action];
- }
-
// This will ensure app will be out of the freezer for at least mFreezerDebounceTimeout.
@GuardedBy("mAm")
void unfreezeTemporarily(ProcessRecord app, @OomAdjuster.OomAdjReason int reason) {
@@ -1287,6 +1232,13 @@ public final class CachedAppOptimizer {
}
}
+ UidRecord uidRec = app.getUidRecord();
+ if (uidRec.isFrozen()) {
+ uidRec.setFrozen(false);
+ mFreezeHandler.removeMessages(UID_FROZEN_STATE_CHANGED_MSG, app);
+ reportOneUidFrozenStateChanged(app.uid, false);
+ }
+
opt.setFreezerOverride(false);
if (pid == 0 || !opt.isFrozen()) {
return;
@@ -1416,6 +1368,13 @@ public final class CachedAppOptimizer {
opt.setPendingFreeze(false);
}
+ UidRecord uidRec = app.getUidRecord();
+ if (uidRec != null && uidRec.isFrozen()) {
+ uidRec.setFrozen(false);
+ mFreezeHandler.removeMessages(UID_FROZEN_STATE_CHANGED_MSG, app);
+ reportOneUidFrozenStateChanged(app.uid, false);
+ }
+
mFrozenProcesses.delete(app.getPid());
}
}
@@ -1475,8 +1434,10 @@ public final class CachedAppOptimizer {
if (oldAdj <= ProcessList.PERCEPTIBLE_APP_ADJ
&& (newAdj == ProcessList.PREVIOUS_APP_ADJ || newAdj == ProcessList.HOME_APP_ADJ)) {
- // Perform a minor compaction when a perceptible app becomes the prev/home app
- compactApp(app, CompactProfile.SOME, CompactSource.APP, false);
+ if (ENABLE_FILE_COMPACT) {
+ // Perform a minor compaction when a perceptible app becomes the prev/home app
+ compactApp(app, CompactProfile.SOME, CompactSource.APP, false);
+ }
} else if (oldAdj < ProcessList.CACHED_APP_MIN_ADJ
&& newAdj >= ProcessList.CACHED_APP_MIN_ADJ
&& newAdj <= ProcessList.CACHED_APP_MAX_ADJ) {
@@ -1486,23 +1447,37 @@ public final class CachedAppOptimizer {
}
/**
- * Applies a compaction downgrade when swap is low.
+ * Computes the final compaction profile to be used which depends on compaction
+ * features enabled and swap usage.
*/
- CompactProfile downgradeCompactionIfRequired(CompactProfile profile) {
- // Downgrade compaction under swap memory pressure
+ CompactProfile resolveCompactionProfile(CompactProfile profile) {
if (profile == CompactProfile.FULL) {
double swapFreePercent = getFreeSwapPercent();
+ // Downgrade compaction under swap memory pressure
if (swapFreePercent < COMPACT_DOWNGRADE_FREE_SWAP_THRESHOLD) {
profile = CompactProfile.SOME;
+
++mTotalCompactionDowngrades;
if (DEBUG_COMPACTION) {
Slog.d(TAG_AM,
- "Downgraded compaction to file only due to low swap."
+ "Downgraded compaction to "+ profile +" due to low swap."
+ " Swap Free% " + swapFreePercent);
}
}
}
+ if (!ENABLE_FILE_COMPACT) {
+ if (profile == CompactProfile.SOME) {
+ profile = CompactProfile.NONE;
+ } else if (profile == CompactProfile.FULL) {
+ profile = CompactProfile.ANON;
+ }
+ if (DEBUG_COMPACTION) {
+ Slog.d(TAG_AM,
+ "Final compaction profile "+ profile +" due to file compact disabled");
+ }
+ }
+
return profile;
}
@@ -1733,7 +1708,6 @@ public final class CachedAppOptimizer {
ProcessRecord proc;
final ProcessCachedOptimizerRecord opt;
int pid;
- CompactAction resolvedAction;
final String name;
CompactProfile lastCompactProfile;
long lastCompactTime;
@@ -1811,17 +1785,24 @@ public final class CachedAppOptimizer {
}
CompactProfile resolvedProfile =
- downgradeCompactionIfRequired(requestedProfile);
- resolvedAction = resolveCompactActionForProfile(resolvedProfile);
+ resolveCompactionProfile(requestedProfile);
+ if (resolvedProfile == CompactProfile.NONE) {
+ // No point on issuing compaction call as we don't want to compact.
+ if (DEBUG_COMPACTION) {
+ Slog.d(TAG_AM, "Resolved no compaction for "+ name +
+ " requested profile="+requestedProfile);
+ }
+ return;
+ }
try {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- "Compact " + resolvedAction.name() + ": " + name
+ "Compact " + resolvedProfile.name() + ": " + name
+ " lastOomAdjReason: " + oomAdjReason
+ " source: " + compactSource.name());
long zramUsedKbBefore = getUsedZramMemory();
long startCpuTime = threadCpuTimeNs();
- mProcessDependencies.performCompaction(resolvedAction, pid);
+ mProcessDependencies.performCompaction(resolvedProfile, pid);
long endCpuTime = threadCpuTimeNs();
long[] rssAfter = mProcessDependencies.getRss(pid);
long end = SystemClock.uptimeMillis();
@@ -1877,7 +1858,7 @@ public final class CachedAppOptimizer {
return;
}
EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name,
- resolvedAction.name(), rssBefore[RSS_TOTAL_INDEX],
+ resolvedProfile.name(), rssBefore[RSS_TOTAL_INDEX],
rssBefore[RSS_FILE_INDEX], rssBefore[RSS_ANON_INDEX],
rssBefore[RSS_SWAP_INDEX], deltaTotalRss, deltaFileRss,
deltaAnonRss, deltaSwapRss, time, lastCompactProfile.name(),
@@ -1907,10 +1888,37 @@ public final class CachedAppOptimizer {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
}
+ case COMPACT_NATIVE_MSG: {
+ int pid = msg.arg1;
+ CompactProfile compactProfile = CompactProfile.values()[msg.arg2];
+ Slog.d(TAG_AM,
+ "Performing native compaction for pid=" + pid
+ + " type=" + compactProfile.name());
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "compactSystem");
+ try {
+ mProcessDependencies.performCompaction(compactProfile, pid);
+ } catch (Exception e) {
+ Slog.d(TAG_AM, "Failed compacting native pid= " + pid);
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ break;
+ }
}
}
}
+ private void reportOneUidFrozenStateChanged(int uid, boolean frozen) {
+ final int[] uids = new int[1];
+ final int[] frozenStates = new int[1];
+
+ uids[0] = uid;
+ frozenStates[0] = frozen ? UID_FROZEN_STATE_FROZEN : UID_FROZEN_STATE_UNFROZEN;
+
+ Slog.d(TAG_AM, "reportOneUidFrozenStateChanged uid " + uid + " frozen = " + frozen);
+
+ mAm.reportUidFrozenStateChanged(uids, frozenStates);
+ }
+
private final class FreezeHandler extends Handler implements
ProcLocksReader.ProcLocksReaderCallback {
private FreezeHandler() {
@@ -1951,6 +1959,10 @@ public final class CachedAppOptimizer {
reportUnfreeze(pid, frozenDuration, processName, reason);
break;
+ case UID_FROZEN_STATE_CHANGED_MSG:
+ ProcessRecord proc = (ProcessRecord) msg.obj;
+ reportOneUidFrozenStateChanged(proc.uid, true);
+ break;
default:
return;
}
@@ -2036,6 +2048,13 @@ public final class CachedAppOptimizer {
unfrozenDuration = opt.getFreezeUnfreezeTime() - unfreezeTime;
frozen = opt.isFrozen();
+
+ final UidRecord uidRec = proc.getUidRecord();
+ if (frozen && uidRec.areAllProcessesFrozen()) {
+ uidRec.setFrozen(true);
+ mFreezeHandler.sendMessage(mFreezeHandler.obtainMessage(
+ UID_FROZEN_STATE_CHANGED_MSG, proc));
+ }
}
if (!frozen) {
@@ -2170,14 +2189,14 @@ public final class CachedAppOptimizer {
// Compact process.
@Override
- public void performCompaction(CompactAction action, int pid) throws IOException {
+ public void performCompaction(CompactProfile profile, int pid) throws IOException {
mPidCompacting = pid;
- if (action == CompactAction.ALL) {
- compactProcess(pid, COMPACT_ACTION_FILE_FLAG | COMPACT_ACTION_ANON_FLAG);
- } else if (action == CompactAction.FILE) {
- compactProcess(pid, COMPACT_ACTION_FILE_FLAG);
- } else if (action == CompactAction.ANON) {
- compactProcess(pid, COMPACT_ACTION_ANON_FLAG);
+ if (profile == CompactProfile.FULL) {
+ compactProcess(pid, COMPACT_ACTION_FILE_FLAG | COMPACT_ACTION_ANON_FLAG);
+ } else if (profile == CompactProfile.SOME) {
+ compactProcess(pid, COMPACT_ACTION_FILE_FLAG);
+ } else if (profile == CompactProfile.ANON) {
+ compactProcess(pid, COMPACT_ACTION_ANON_FLAG);
}
mPidCompacting = -1;
}
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index fa3f684b034e..50d00b471f65 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1039,7 +1039,13 @@ class ProcessRecord implements WindowProcessListener {
mInFullBackup = inFullBackup;
}
+ @GuardedBy("mService")
+ public void setCached(boolean cached) {
+ mState.setCached(cached);
+ }
+
@Override
+ @GuardedBy("mService")
public boolean isCached() {
return mState.isCached();
}
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index bfc022ba8b9f..e38e8c1428ee 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -151,6 +151,14 @@ public final class UidRecord {
@GuardedBy("mService")
private int mLastReportedChange;
+ /**
+ * This indicates whether the entire Uid is frozen or not.
+ * It is used by CachedAppOptimizer to avoid sending multiple
+ * UID_FROZEN_STATE_UNFROZEN messages on process unfreeze.
+ */
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ private boolean mUidIsFrozen;
+
public UidRecord(int uid, ActivityManagerService service) {
mUid = uid;
mService = service;
@@ -313,6 +321,32 @@ public final class UidRecord {
return null;
}
+ /**
+ * @return true if all processes in the Uid are frozen, false otherwise.
+ */
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ public boolean areAllProcessesFrozen() {
+ for (int i = mProcRecords.size() - 1; i >= 0; i--) {
+ final ProcessRecord app = mProcRecords.valueAt(i);
+ final ProcessCachedOptimizerRecord opt = app.mOptRecord;
+
+ if (!opt.isFrozen()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ public void setFrozen(boolean frozen) {
+ mUidIsFrozen = frozen;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ public boolean isFrozen() {
+ return mUidIsFrozen;
+ }
+
@GuardedBy({"mService", "mProcLock"})
void addProcess(ProcessRecord app) {
mProcRecords.add(app);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index aae1d3884344..6758581d1fc0 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -597,7 +597,13 @@ public class AudioDeviceInventory {
if (wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED
&& AudioSystem.DEVICE_OUT_ALL_USB_SET.contains(
wdcs.mAttributes.getInternalType())) {
- mDeviceBroker.dispatchPreferredMixerAttributesChangedCausedByDeviceRemoved(info);
+ if (info != null) {
+ mDeviceBroker.dispatchPreferredMixerAttributesChangedCausedByDeviceRemoved(
+ info);
+ } else {
+ Log.e(TAG, "Didn't find AudioDeviceInfo to notify preferred mixer "
+ + "attributes change for type=" + wdcs.mAttributes.getType());
+ }
}
sendDeviceConnectionIntent(type, wdcs.mState,
wdcs.mAttributes.getAddress(), wdcs.mAttributes.getName());
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 46337a909539..bb79c99cc985 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2340,7 +2340,6 @@ public class NotificationManagerService extends SystemService {
mAppOps,
new SysUiStatsEvent.BuilderFactory(),
mShowReviewPermissionsNotification);
- mPreferencesHelper.updateFixedImportance(mUm.getUsers());
mRankingHelper = new RankingHelper(getContext(),
mRankingHandler,
mPreferencesHelper,
@@ -2771,6 +2770,9 @@ public class NotificationManagerService extends SystemService {
maybeShowInitialReviewPermissionsNotification();
} else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
mSnoozeHelper.scheduleRepostsForPersistedNotifications(System.currentTimeMillis());
+ } else if (phase == SystemService.PHASE_DEVICE_SPECIFIC_SERVICES_READY) {
+ mPreferencesHelper.updateFixedImportance(mUm.getUsers());
+ mPreferencesHelper.migrateNotificationPermissions(mUm.getUsers());
}
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 4bafbc73a96b..aa97aa3655e2 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -237,7 +237,6 @@ public class PreferencesHelper implements RankingConfig {
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
}
- ArrayList<PermissionHelper.PackagePermission> pkgPerms = new ArrayList<>();
synchronized (mPackagePreferences) {
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
tag = parser.getName();
@@ -255,27 +254,18 @@ public class PreferencesHelper implements RankingConfig {
String name = parser.getAttributeValue(null, ATT_NAME);
if (!TextUtils.isEmpty(name)) {
restorePackage(parser, forRestore, userId, name, upgradeForBubbles,
- migrateToPermission, pkgPerms);
+ migrateToPermission);
}
}
}
}
}
- if (migrateToPermission) {
- for (PackagePermission p : pkgPerms) {
- try {
- mPermissionHelper.setNotificationPermission(p);
- } catch (Exception e) {
- Slog.e(TAG, "could not migrate setting for " + p.packageName, e);
- }
- }
- }
}
@GuardedBy("mPackagePreferences")
private void restorePackage(TypedXmlPullParser parser, boolean forRestore,
@UserIdInt int userId, String name, boolean upgradeForBubbles,
- boolean migrateToPermission, ArrayList<PermissionHelper.PackagePermission> pkgPerms) {
+ boolean migrateToPermission) {
try {
int uid = parser.getAttributeInt(null, ATT_UID, UNKNOWN_UID);
if (forRestore) {
@@ -379,14 +369,6 @@ public class PreferencesHelper implements RankingConfig {
if (migrateToPermission) {
r.importance = appImportance;
r.migrateToPm = true;
- if (r.uid != UNKNOWN_UID) {
- // Don't call into permission system until we have a valid uid
- PackagePermission pkgPerm = new PackagePermission(
- r.pkg, UserHandle.getUserId(r.uid),
- r.importance != IMPORTANCE_NONE,
- hasUserConfiguredSettings(r));
- pkgPerms.add(pkgPerm);
- }
}
} catch (Exception e) {
Slog.w(TAG, "Failed to restore pkg", e);
@@ -2663,6 +2645,31 @@ public class PreferencesHelper implements RankingConfig {
}
}
+ public void migrateNotificationPermissions(List<UserInfo> users) {
+ for (UserInfo user : users) {
+ List<PackageInfo> packages = mPm.getInstalledPackagesAsUser(
+ PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL),
+ user.getUserHandle().getIdentifier());
+ for (PackageInfo pi : packages) {
+ synchronized (mPackagePreferences) {
+ PackagePreferences p = getOrCreatePackagePreferencesLocked(
+ pi.packageName, pi.applicationInfo.uid);
+ if (p.migrateToPm && p.uid != UNKNOWN_UID) {
+ try {
+ PackagePermission pkgPerm = new PackagePermission(
+ p.pkg, UserHandle.getUserId(p.uid),
+ p.importance != IMPORTANCE_NONE,
+ hasUserConfiguredSettings(p));
+ mPermissionHelper.setNotificationPermission(pkgPerm);
+ } catch (Exception e) {
+ Slog.e(TAG, "could not migrate setting for " + p.pkg, e);
+ }
+ }
+ }
+ }
+ }
+ }
+
private void updateConfig() {
mRankingHandler.requestSort();
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index c9eef387eeb2..55060a677c8c 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -90,7 +90,6 @@ import android.os.ShellCallback;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
-import android.os.storage.StorageManager;
import android.service.wallpaper.IWallpaperConnection;
import android.service.wallpaper.IWallpaperEngine;
import android.service.wallpaper.IWallpaperService;
@@ -2210,12 +2209,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
public ParcelFileDescriptor getWallpaperWithFeature(String callingPkg, String callingFeatureId,
IWallpaperManagerCallback cb, final int which, Bundle outParams, int wallpaperUserId,
boolean getCropped) {
- final boolean hasPrivilege = hasPermission(READ_WALLPAPER_INTERNAL);
- if (!hasPrivilege) {
- mContext.getSystemService(StorageManager.class).checkPermissionReadImages(true,
- Binder.getCallingPid(), Binder.getCallingUid(), callingPkg, callingFeatureId);
- }
-
+ checkPermission(READ_WALLPAPER_INTERNAL);
wallpaperUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), wallpaperUserId, false, true, "getWallpaper", null);
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 5e066faf0e90..f8fb76acf81e 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -50,8 +50,6 @@ import android.provider.Settings;
import android.util.Slog;
import android.util.SparseArray;
import android.view.RemoteAnimationAdapter;
-import android.view.WindowManager;
-import android.window.RemoteTransition;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
@@ -562,9 +560,8 @@ public class ActivityStartController {
final Task rootTask = mService.mRootWindowContainer.getDefaultTaskDisplayArea()
.getRootTask(WINDOWING_MODE_UNDEFINED, activityType);
if (rootTask == null) return false;
- final RemoteTransition remote = options.getRemoteTransition();
final ActivityRecord r = rootTask.topRunningActivity();
- if (r == null || r.isVisibleRequested() || !r.attachedToProcess() || remote == null
+ if (r == null || r.isVisibleRequested() || !r.attachedToProcess()
|| !r.mActivityComponent.equals(intent.getComponent())
// Recents keeps invisible while device is locked.
|| r.mDisplayContent.isKeyguardLocked()) {
@@ -573,47 +570,13 @@ public class ActivityStartController {
mService.mRootWindowContainer.startPowerModeLaunchIfNeeded(true /* forceSend */, r);
final ActivityMetricsLogger.LaunchingState launchingState =
mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(intent);
- final Transition transition = new Transition(WindowManager.TRANSIT_TO_FRONT,
- 0 /* flags */, r.mTransitionController, mService.mWindowManager.mSyncEngine);
- if (r.mTransitionController.isCollecting()) {
- // Special case: we are entering recents while an existing transition is running. In
- // this case, we know it's safe to "defer" the activity launch, so lets do so now so
- // that it can get its own transition and thus update launcher correctly.
- mService.mWindowManager.mSyncEngine.queueSyncSet(
- () -> {
- if (r.isAttached()) {
- r.mTransitionController.moveToCollecting(transition);
- }
- },
- () -> {
- if (r.isAttached() && transition.isCollecting()) {
- startExistingRecentsIfPossibleInner(options, r, rootTask,
- launchingState, remote, transition);
- }
- });
- } else {
- r.mTransitionController.moveToCollecting(transition);
- startExistingRecentsIfPossibleInner(options, r, rootTask, launchingState, remote,
- transition);
- }
- return true;
- }
-
- private void startExistingRecentsIfPossibleInner(ActivityOptions options, ActivityRecord r,
- Task rootTask, ActivityMetricsLogger.LaunchingState launchingState,
- RemoteTransition remoteTransition, Transition transition) {
final Task task = r.getTask();
mService.deferWindowLayout();
try {
final TransitionController controller = r.mTransitionController;
if (controller.getTransitionPlayer() != null) {
- controller.requestStartTransition(transition, task, remoteTransition,
- null /* displayChange */);
controller.collect(task);
controller.setTransientLaunch(r, TaskDisplayArea.getRootTaskAbove(rootTask));
- } else {
- // The transition player might be died when executing the queued transition.
- transition.abort();
}
task.moveToFront("startExistingRecents");
task.mInResumeTopActivity = true;
@@ -624,6 +587,7 @@ public class ActivityStartController {
task.mInResumeTopActivity = false;
mService.continueWindowLayout();
}
+ return true;
}
void registerRemoteAnimationForNextActivityStart(String packageName,
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index ce29564d0b02..12be1d3186a1 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1582,19 +1582,19 @@ class ActivityStarter {
}
}
if (isTransientLaunch) {
- if (forceTransientTransition && newTransition != null) {
- newTransition.collect(mLastStartActivityRecord);
- newTransition.collect(mPriorAboveTask);
+ if (forceTransientTransition) {
+ transitionController.collect(mLastStartActivityRecord);
+ transitionController.collect(mPriorAboveTask);
}
// `started` isn't guaranteed to be the actual relevant activity, so we must wait
// until after we launched to identify the relevant activity.
transitionController.setTransientLaunch(mLastStartActivityRecord, mPriorAboveTask);
- if (forceTransientTransition && newTransition != null) {
+ if (forceTransientTransition) {
final DisplayContent dc = mLastStartActivityRecord.getDisplayContent();
// update wallpaper target to TransientHide
dc.mWallpaperController.adjustWallpaperWindows();
// execute transition because there is no change
- newTransition.setReady(dc, true /* ready */);
+ transitionController.setReady(dc, true /* ready */);
}
}
if (!userLeaving) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 992743ab8593..555cd38806e6 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1240,25 +1240,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
ProfilerInfo profilerInfo, Bundle bOptions, int userId, boolean validateIncomingUser) {
final SafeActivityOptions opts = SafeActivityOptions.fromBundle(bOptions);
- // A quick path (skip general intent/task resolving) to start recents animation if the
- // recents (or home) activity is available in background.
- if (opts != null && opts.getOriginalOptions().getTransientLaunch()
- && isCallerRecents(Binder.getCallingUid())) {
- final long origId = Binder.clearCallingIdentity();
- try {
- synchronized (mGlobalLock) {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "startExistingRecents");
- if (mActivityStartController.startExistingRecentsIfPossible(
- intent, opts.getOriginalOptions())) {
- return ActivityManager.START_TASK_TO_FRONT;
- }
- // Else follow the standard launch procedure.
- }
- } finally {
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- Binder.restoreCallingIdentity(origId);
- }
- }
assertPackageMatchesCallingUid(callingPackage);
enforceNotIsolatedCaller("startActivityAsUser");
@@ -5718,6 +5699,23 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent,
BackgroundStartPrivileges backgroundStartPrivileges) {
assertPackageMatchesCallingUid(callingPackage);
+ // A quick path (skip general intent/task resolving) to start recents animation if the
+ // recents (or home) activity is available in background.
+ if (options != null && options.getOriginalOptions() != null
+ && options.getOriginalOptions().getTransientLaunch() && isCallerRecents(uid)) {
+ try {
+ synchronized (mGlobalLock) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "startExistingRecents");
+ if (mActivityStartController.startExistingRecentsIfPossible(
+ intent, options.getOriginalOptions())) {
+ return ActivityManager.START_TASK_TO_FRONT;
+ }
+ // Else follow the standard launch procedure.
+ }
+ } finally {
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
return getActivityStartController().startActivityInPackage(uid, realCallingPid,
realCallingUid, callingPackage, callingFeatureId, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags, options, userId, inTask,
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 5db39fc8434c..980a9418725b 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -447,6 +447,10 @@ final class LetterboxUiController {
@ScreenOrientation
int overrideOrientationIfNeeded(@ScreenOrientation int candidate) {
+ // In some cases (e.g. Kids app) we need to map the candidate orientation to some other
+ // orientation.
+ candidate = mActivityRecord.mWmService.mapOrientationRequest(candidate);
+
if (FALSE.equals(mBooleanPropertyAllowOrientationOverride)) {
return candidate;
}
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index b13136534de3..93c8c3666706 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -1166,12 +1166,15 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
}
@Override
- public void setIsIgnoreOrientationRequestDisabled(boolean isDisabled) {
- enforceTaskPermission("setIsIgnoreOrientationRequestDisabled()");
+ public void setOrientationRequestPolicy(boolean isIgnoreOrientationRequestDisabled,
+ @Nullable int[] fromOrientations, @Nullable int[] toOrientations) {
+ enforceTaskPermission("setOrientationRequestPolicy()");
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
- mService.mWindowManager.setIsIgnoreOrientationRequestDisabled(isDisabled);
+ mService.mWindowManager
+ .setOrientationRequestPolicy(isIgnoreOrientationRequestDisabled,
+ fromOrientations, toOrientations);
}
} finally {
Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index be42f36f7d30..f7641f5bfb54 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -237,6 +237,7 @@ import android.util.MergedConfiguration;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
import android.util.TimeUtils;
import android.util.TypedValue;
import android.util.proto.ProtoOutputStream;
@@ -594,6 +595,13 @@ public class WindowManagerService extends IWindowManager.Stub
/** List of window currently causing non-system overlay windows to be hidden. */
private ArrayList<WindowState> mHidingNonSystemOverlayWindows = new ArrayList<>();
+ /**
+ * In some cases (e.g. when {@link R.bool.config_reverseDefaultRotation} has value
+ * {@value true}) we need to map some orientation to others. This {@link SparseIntArray}
+ * contains the relation between the source orientation and the one to use.
+ */
+ private final SparseIntArray mOrientationMapping = new SparseIntArray();
+
final AccessibilityController mAccessibilityController;
private RecentsAnimationController mRecentsAnimationController;
@@ -4111,25 +4119,52 @@ public class WindowManagerService extends IWindowManager.Stub
/**
* Controls whether ignore orientation request logic in {@link DisplayArea} is disabled
- * at runtime.
+ * at runtime and how to optionally map some requested orientations to others.
*
* <p>Note: this assumes that {@link #mGlobalLock} is held by the caller.
*
- * @param isDisabled when {@code true}, the system always ignores the value of {@link
- * DisplayArea#getIgnoreOrientationRequest} and app requested orientation is
- * respected.
+ * @param isIgnoreOrientationRequestDisabled when {@code true}, the system always ignores the
+ * value of {@link DisplayArea#getIgnoreOrientationRequest} and app requested
+ * orientation is respected.
+ * @param fromOrientations The orientations we want to map to the correspondent orientations
+ * in toOrientation.
+ * @param toOrientations The orientations we map to the ones in fromOrientations at the same
+ * index
*/
- void setIsIgnoreOrientationRequestDisabled(boolean isDisabled) {
- if (isDisabled == mIsIgnoreOrientationRequestDisabled) {
+ void setOrientationRequestPolicy(boolean isIgnoreOrientationRequestDisabled,
+ @Nullable int[] fromOrientations, @Nullable int[] toOrientations) {
+ mOrientationMapping.clear();
+ if (fromOrientations != null && toOrientations != null
+ && fromOrientations.length == toOrientations.length) {
+ for (int i = 0; i < fromOrientations.length; i++) {
+ mOrientationMapping.put(fromOrientations[i], toOrientations[i]);
+ }
+ }
+ if (isIgnoreOrientationRequestDisabled == mIsIgnoreOrientationRequestDisabled) {
return;
}
- mIsIgnoreOrientationRequestDisabled = isDisabled;
+ mIsIgnoreOrientationRequestDisabled = isIgnoreOrientationRequestDisabled;
for (int i = mRoot.getChildCount() - 1; i >= 0; i--) {
mRoot.getChildAt(i).onIsIgnoreOrientationRequestDisabledChanged();
}
}
/**
+ * When {@link mIsIgnoreOrientationRequestDisabled} is {@value true} this method returns the
+ * orientation to use in place of the one in input. It returns the same requestedOrientation in
+ * input otherwise.
+ *
+ * @param requestedOrientation The orientation that can be mapped.
+ * @return The orientation to use in place of requestedOrientation.
+ */
+ int mapOrientationRequest(int requestedOrientation) {
+ if (!mIsIgnoreOrientationRequestDisabled) {
+ return requestedOrientation;
+ }
+ return mOrientationMapping.get(requestedOrientation, requestedOrientation);
+ }
+
+ /**
* Whether the system ignores the value of {@link DisplayArea#getIgnoreOrientationRequest} and
* app requested orientation is respected.
*
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index cf8460bca1a6..2d4f5ca2f71d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -53,6 +53,7 @@ import android.app.AppOpsManager;
import android.app.BackgroundStartPrivileges;
import android.app.BroadcastOptions;
import android.app.IApplicationThread;
+import android.app.UidObserver;
import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
@@ -83,6 +84,7 @@ import android.util.proto.ProtoOutputStream;
import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.server.AlarmManagerInternal;
import com.android.server.DropBoxManagerInternal;
import com.android.server.LocalServices;
import com.android.server.am.ActivityManagerService.Injector;
@@ -153,11 +155,14 @@ public class BroadcastQueueTest {
private PackageManagerInternal mPackageManagerInt;
@Mock
private UsageStatsManagerInternal mUsageStatsManagerInt;
+ @Mock
+ private AlarmManagerInternal mAlarmManagerInt;
private ActivityManagerService mAms;
private BroadcastQueue mQueue;
BroadcastConstants mConstants;
private BroadcastSkipPolicy mSkipPolicy;
+ private UidObserver mUidObserver;
/**
* Desired behavior of the next
@@ -204,6 +209,8 @@ public class BroadcastQueueTest {
LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt);
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
+ LocalServices.removeServiceForTest(AlarmManagerInternal.class);
+ LocalServices.addService(AlarmManagerInternal.class, mAlarmManagerInt);
doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
doNothing().when(mPackageManagerInt).setPackageStoppedState(any(), anyBoolean(), anyInt());
doAnswer((invocation) -> {
@@ -281,6 +288,11 @@ public class BroadcastQueueTest {
}).when(mAms).getProcessRecordLocked(any(), anyInt());
doNothing().when(mAms).appNotResponding(any(), any());
+ doAnswer((invocation) -> {
+ mUidObserver = invocation.getArgument(0);
+ return null;
+ }).when(mAms).registerUidObserver(any(), anyInt(), anyInt(), any());
+
mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
mConstants.TIMEOUT = 100;
mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0;
@@ -305,6 +317,8 @@ public class BroadcastQueueTest {
} else {
throw new UnsupportedOperationException();
}
+
+ mQueue.start(mContext.getContentResolver());
}
@After
@@ -683,12 +697,22 @@ public class BroadcastQueueTest {
anyInt(), anyInt(), any());
}
- private void verifyScheduleRegisteredReceiver(ProcessRecord app,
+ private void verifyScheduleRegisteredReceiver(ProcessRecord app, Intent intent)
+ throws Exception {
+ verifyScheduleRegisteredReceiver(times(1), app, intent, UserHandle.USER_SYSTEM);
+ }
+
+ private void verifyScheduleRegisteredReceiver(VerificationMode mode, ProcessRecord app,
Intent intent) throws Exception {
- verify(app.getThread()).scheduleRegisteredReceiver(
+ verifyScheduleRegisteredReceiver(mode, app, intent, UserHandle.USER_SYSTEM);
+ }
+
+ private void verifyScheduleRegisteredReceiver(VerificationMode mode, ProcessRecord app,
+ Intent intent, int userId) throws Exception {
+ verify(app.getThread(), mode).scheduleRegisteredReceiver(
any(), argThat(filterEqualsIgnoringComponent(intent)),
anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyBoolean(),
- eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any());
+ eq(userId), anyInt(), anyInt(), any());
}
private void verifyScheduleRegisteredReceiver(VerificationMode mode, ProcessRecord app,
@@ -1934,4 +1958,49 @@ public class BroadcastQueueTest {
getUidForPackage(PACKAGE_ORANGE));
assertNull(receiverOrangeApp);
}
+
+ /**
+ * Verify broadcasts to runtime receivers in cached processes are deferred
+ * until that process leaves the cached state.
+ */
+ @Test
+ public void testDeferralPolicy_UntilActive() throws Exception {
+ // Legacy stack doesn't support deferral
+ Assume.assumeTrue(mImpl == Impl.MODERN);
+
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+ final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+ final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW);
+
+ receiverGreenApp.setCached(true);
+ receiverBlueApp.setCached(true);
+ receiverYellowApp.setCached(false);
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ final BroadcastOptions opts = BroadcastOptions.makeBasic()
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, opts,
+ List.of(makeRegisteredReceiver(receiverGreenApp),
+ makeRegisteredReceiver(receiverBlueApp),
+ makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
+ makeRegisteredReceiver(receiverYellowApp))));
+ waitForIdle();
+
+ // Green ignored since it's in cached state
+ verifyScheduleRegisteredReceiver(never(), receiverGreenApp, airplane);
+
+ // Blue delivered both since it has a manifest receiver
+ verifyScheduleReceiver(times(1), receiverBlueApp, airplane);
+ verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
+
+ // Yellow delivered since it's not cached
+ verifyScheduleRegisteredReceiver(times(1), receiverYellowApp, airplane);
+
+ // Shift green to be active and confirm that deferred broadcast is delivered
+ receiverGreenApp.setCached(false);
+ mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_GREEN), false);
+ waitForIdle();
+ verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane);
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
index ec9e5b50579c..1fbb8dd5ac9d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -19,7 +19,6 @@ package com.android.server.am;
import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
import static com.android.server.am.ActivityManagerService.Injector;
-import static com.android.server.am.CachedAppOptimizer.compactActionIntToAction;
import static com.google.common.truth.Truth.assertThat;
@@ -155,12 +154,6 @@ public final class CachedAppOptimizerTest {
synchronized (mCachedAppOptimizerUnderTest.mPhenotypeFlagLock) {
assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isEqualTo(
CachedAppOptimizer.DEFAULT_USE_COMPACTION);
- assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome)
- .isEqualTo(
- compactActionIntToAction(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1));
- assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull)
- .isEqualTo(
- compactActionIntToAction(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2));
assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
@@ -210,12 +203,6 @@ public final class CachedAppOptimizerTest {
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
CachedAppOptimizer.KEY_USE_COMPACTION, "true", false);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- CachedAppOptimizer.KEY_COMPACT_ACTION_1,
- Integer.toString((CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- CachedAppOptimizer.KEY_COMPACT_ACTION_2,
- Integer.toString((CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
CachedAppOptimizer.KEY_COMPACT_THROTTLE_1,
Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1), false);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -266,12 +253,6 @@ public final class CachedAppOptimizerTest {
assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isTrue();
assertThat(mCachedAppOptimizerUnderTest.mCachedAppOptimizerThread.isAlive()).isTrue();
- assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome)
- .isEqualTo(compactActionIntToAction(
- (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1));
- assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull)
- .isEqualTo(compactActionIntToAction(
- (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1));
assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1);
assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
@@ -404,72 +385,6 @@ public final class CachedAppOptimizerTest {
}
@Test
- public void compactAction_listensToDeviceConfigChanges() throws InterruptedException {
- mCachedAppOptimizerUnderTest.init();
-
- // When we override new values for the compaction action with reasonable values...
-
- // There are four possible values for compactAction[Some|Full].
- for (int i = 1; i < 5; i++) {
- mCountDown = new CountDownLatch(2);
- int expectedSome = (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1 + i) % 4 + 1;
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- CachedAppOptimizer.KEY_COMPACT_ACTION_1, Integer.toString(expectedSome), false);
- int expectedFull = (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2 + i) % 4 + 1;
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- CachedAppOptimizer.KEY_COMPACT_ACTION_2, Integer.toString(expectedFull), false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then the updates are reflected in the flags.
- synchronized (mCachedAppOptimizerUnderTest.mPhenotypeFlagLock) {
- assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome)
- .isEqualTo(compactActionIntToAction(expectedSome));
- assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull)
- .isEqualTo(compactActionIntToAction(expectedFull));
- }
- }
- }
-
- @Test
- public void compactAction_listensToDeviceConfigChangesBadValues() throws InterruptedException {
- mCachedAppOptimizerUnderTest.init();
-
- // When we override new values for the compaction action with bad values ...
- mCountDown = new CountDownLatch(2);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- CachedAppOptimizer.KEY_COMPACT_ACTION_1, "foo", false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- CachedAppOptimizer.KEY_COMPACT_ACTION_2, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- synchronized (mCachedAppOptimizerUnderTest.mPhenotypeFlagLock) {
- // Then the default values are reflected in the flag
- assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome)
- .isEqualTo(
- compactActionIntToAction(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1));
- assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull)
- .isEqualTo(
- compactActionIntToAction(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2));
- }
-
- mCountDown = new CountDownLatch(2);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- CachedAppOptimizer.KEY_COMPACT_ACTION_1, "", false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- CachedAppOptimizer.KEY_COMPACT_ACTION_2, "", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- synchronized (mCachedAppOptimizerUnderTest.mPhenotypeFlagLock) {
- assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome)
- .isEqualTo(
- compactActionIntToAction(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1));
- assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull)
- .isEqualTo(
- compactActionIntToAction(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2));
- }
- }
-
- @Test
public void compactThrottle_listensToDeviceConfigChanges() throws InterruptedException {
mCachedAppOptimizerUnderTest.init();
@@ -1108,14 +1023,17 @@ public final class CachedAppOptimizerTest {
mCachedAppOptimizerUnderTest.mLastCompactionStats.clear();
- // We force a some compaction
- mCachedAppOptimizerUnderTest.compactApp(processRecord,
- CachedAppOptimizer.CompactProfile.SOME, CachedAppOptimizer.CompactSource.APP, true);
- waitForHandler();
- // then process is compacted.
- CachedAppOptimizer.CompactProfile executedCompactProfile =
- processRecord.mOptRecord.getLastCompactProfile();
- assertThat(executedCompactProfile).isEqualTo(CachedAppOptimizer.CompactProfile.SOME);
+ if (CachedAppOptimizer.ENABLE_FILE_COMPACT) {
+ // We force a some compaction
+ mCachedAppOptimizerUnderTest.compactApp(processRecord,
+ CachedAppOptimizer.CompactProfile.SOME, CachedAppOptimizer.CompactSource.APP,
+ true);
+ waitForHandler();
+ // then process is compacted.
+ CachedAppOptimizer.CompactProfile executedCompactProfile =
+ processRecord.mOptRecord.getLastCompactProfile();
+ assertThat(executedCompactProfile).isEqualTo(CachedAppOptimizer.CompactProfile.SOME);
+ }
}
private void setFlag(String key, String value, boolean defaultValue) throws Exception {
@@ -1192,7 +1110,7 @@ public final class CachedAppOptimizerTest {
}
@Override
- public void performCompaction(CachedAppOptimizer.CompactAction action, int pid)
+ public void performCompaction(CachedAppOptimizer.CompactProfile profile, int pid)
throws IOException {
mRss = mRssAfterCompaction;
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 06bcb9134c07..50e5bbf4ba9b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -679,10 +679,6 @@ public class PreferencesHelperTest extends UiServiceTestCase {
compareChannels(ido, mHelper.getNotificationChannel(PKG_O, UID_O, ido.getId(), false));
compareChannels(idp, mHelper.getNotificationChannel(PKG_P, UID_P, idp.getId(), false));
- verify(mPermissionHelper).setNotificationPermission(nMr1Expected);
- verify(mPermissionHelper).setNotificationPermission(oExpected);
- verify(mPermissionHelper).setNotificationPermission(pExpected);
-
// verify that we also write a state for review_permissions_notification to eventually
// show a notification
assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW,
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index da078a22c5ff..9b4cb134e427 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -55,6 +55,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.verify;
import android.annotation.Nullable;
import android.compat.testing.PlatformCompatChangeRule;
@@ -526,6 +527,16 @@ public class LetterboxUiControllerTest extends WindowTestsBase {
// overrideOrientationIfNeeded
@Test
+ public void testOverrideOrientationIfNeeded_mapInvokedOnRequest() throws Exception {
+ mController = new LetterboxUiController(mWm, mActivity);
+ spyOn(mWm);
+
+ mController.overrideOrientationIfNeeded(SCREEN_ORIENTATION_PORTRAIT);
+
+ verify(mWm).mapOrientationRequest(SCREEN_ORIENTATION_PORTRAIT);
+ }
+
+ @Test
@EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT})
public void testOverrideOrientationIfNeeded_portraitOverrideEnabled_returnsPortrait()
throws Exception {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 677ec46007ff..ba6b3b6c9378 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -117,6 +117,20 @@ public class WindowManagerServiceTests extends WindowTestsBase {
ADD_TRUSTED_DISPLAY);
@Test
+ public void testIsRequestedOrientationMapped() {
+ mWm.setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled*/ true,
+ /* fromOrientations */ new int[]{1}, /* toOrientations */ new int[]{2});
+ assertThat(mWm.mapOrientationRequest(1)).isEqualTo(2);
+ assertThat(mWm.mapOrientationRequest(3)).isEqualTo(3);
+
+ // Mapping disabled
+ mWm.setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled*/ false,
+ /* fromOrientations */ null, /* toOrientations */ null);
+ assertThat(mWm.mapOrientationRequest(1)).isEqualTo(1);
+ assertThat(mWm.mapOrientationRequest(3)).isEqualTo(3);
+ }
+
+ @Test
public void testAddWindowToken() {
IBinder token = mock(IBinder.class);
mWm.addWindowToken(token, TYPE_TOAST, mDisplayContent.getDisplayId(), null /* options */);
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 5cbbe3792ab4..286e71c14726 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -4439,7 +4439,6 @@ public class SubscriptionManager {
*
* @throws IllegalArgumentException if subscription is invalid.
* @throws SecurityException if the caller doesn't have permissions required.
- * @throws IllegalStateException if subscription service is not available.
*
* @hide
*/
@@ -4456,8 +4455,8 @@ public class SubscriptionManager {
if (iSub != null) {
return iSub.isSubscriptionAssociatedWithUser(subscriptionId, userHandle);
} else {
- throw new IllegalStateException("[isSubscriptionAssociatedWithUser]: "
- + "subscription service unavailable");
+ Log.e(LOG_TAG, "[isSubscriptionAssociatedWithUser]: subscription service "
+ + "unavailable");
}
} catch (RemoteException ex) {
ex.rethrowAsRuntimeException();
@@ -4484,7 +4483,7 @@ public class SubscriptionManager {
if (iSub != null) {
return iSub.getSubscriptionInfoListAssociatedWithUser(userHandle);
} else {
- throw new IllegalStateException("[getSubscriptionInfoListAssociatedWithUser]: "
+ Log.e(LOG_TAG, "[getSubscriptionInfoListAssociatedWithUser]: "
+ "subscription service unavailable");
}
} catch (RemoteException ex) {
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
index 87ca99fd3e03..06a86ccb00ee 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
@@ -46,6 +46,7 @@ import com.android.internal.R;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
/**
@@ -77,6 +78,8 @@ public abstract class SharedConnectivityService extends Service {
new KnownNetworkConnectionStatus.Builder()
.setStatus(KnownNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN)
.setExtras(Bundle.EMPTY).build();
+ // Used for testing
+ private CountDownLatch mCountDownLatch;
@Override
@Nullable
@@ -265,12 +268,24 @@ public abstract class SharedConnectivityService extends Service {
public void onBind() {
}
+ /** @hide */
+ @TestApi
+ public final void setCountdownLatch(@Nullable CountDownLatch latch) {
+ mCountDownLatch = latch;
+ }
+
private void onRegisterCallback(ISharedConnectivityCallback callback) {
mRemoteCallbackList.register(callback);
+ if (mCountDownLatch != null) {
+ mCountDownLatch.countDown();
+ }
}
private void onUnregisterCallback(ISharedConnectivityCallback callback) {
mRemoteCallbackList.unregister(callback);
+ if (mCountDownLatch != null) {
+ mCountDownLatch.countDown();
+ }
}
/**
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
index 514ba3c0472e..4a293cb5c8db 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
@@ -26,7 +26,10 @@ import static android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.DEVICE
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -39,6 +42,7 @@ import android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus;
import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo;
import android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
@@ -51,12 +55,16 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* Unit tests for {@link SharedConnectivityService}.
*/
@SmallTest
public class SharedConnectivityServiceTest {
+ private static final int LATCH_TIMEOUT = 2;
+
private static final NetworkProviderInfo NETWORK_PROVIDER_INFO =
new NetworkProviderInfo.Builder("TEST_NAME", "TEST_MODEL")
.setDeviceType(DEVICE_TYPE_TABLET).setConnectionStrength(2)
@@ -75,7 +83,7 @@ public class SharedConnectivityServiceTest {
.addSecurityType(SECURITY_TYPE_EAP).setNetworkProviderInfo(
NETWORK_PROVIDER_INFO).build();
private static final List<KnownNetwork> KNOWN_NETWORKS = List.of(KNOWN_NETWORK);
- private static final HotspotNetworkConnectionStatus TETHER_NETWORK_CONNECTION_STATUS =
+ private static final HotspotNetworkConnectionStatus HOTSPOT_NETWORK_CONNECTION_STATUS =
new HotspotNetworkConnectionStatus.Builder().setStatus(CONNECTION_STATUS_UNKNOWN)
.setHotspotNetwork(HOTSPOT_NETWORK).setExtras(Bundle.EMPTY).build();
private static final KnownNetworkConnectionStatus KNOWN_NETWORK_CONNECTION_STATUS =
@@ -88,25 +96,77 @@ public class SharedConnectivityServiceTest {
@Mock
Resources mResources;
+ @Mock
+ ISharedConnectivityCallback mCallback;
+
+ @Mock
+ IBinder mBinder;
+
static class FakeSharedConnectivityService extends SharedConnectivityService {
public void attachBaseContext(Context context) {
super.attachBaseContext(context);
}
+ private HotspotNetwork mConnectedHotspotNetwork;
+ private HotspotNetwork mDisconnectedHotspotNetwork;
+ private KnownNetwork mConnectedKnownNetwork;
+ private KnownNetwork mForgottenKnownNetwork;
+ private CountDownLatch mLatch;
+
+ public HotspotNetwork getConnectedHotspotNetwork() {
+ return mConnectedHotspotNetwork;
+ }
+
+ public HotspotNetwork getDisconnectedHotspotNetwork() {
+ return mDisconnectedHotspotNetwork;
+ }
+
+ public KnownNetwork getConnectedKnownNetwork() {
+ return mConnectedKnownNetwork;
+ }
+
+ public KnownNetwork getForgottenKnownNetwork() {
+ return mForgottenKnownNetwork;
+ }
+
+ public void initializeLatch() {
+ mLatch = new CountDownLatch(1);
+ }
+
+ public CountDownLatch getLatch() {
+ return mLatch;
+ }
+
@Override
public void onConnectHotspotNetwork(@NonNull HotspotNetwork network) {
+ mConnectedHotspotNetwork = network;
+ if (mLatch != null) {
+ mLatch.countDown();
+ }
}
@Override
public void onDisconnectHotspotNetwork(@NonNull HotspotNetwork network) {
+ mDisconnectedHotspotNetwork = network;
+ if (mLatch != null) {
+ mLatch.countDown();
+ }
}
@Override
public void onConnectKnownNetwork(@NonNull KnownNetwork network) {
+ mConnectedKnownNetwork = network;
+ if (mLatch != null) {
+ mLatch.countDown();
+ }
}
@Override
public void onForgetKnownNetwork(@NonNull KnownNetwork network) {
+ mForgottenKnownNetwork = network;
+ if (mLatch != null) {
+ mLatch.countDown();
+ }
}
}
@@ -165,10 +225,10 @@ public class SharedConnectivityServiceTest {
ISharedConnectivityService.Stub binder =
(ISharedConnectivityService.Stub) service.onBind(new Intent());
- service.updateHotspotNetworkConnectionStatus(TETHER_NETWORK_CONNECTION_STATUS);
+ service.updateHotspotNetworkConnectionStatus(HOTSPOT_NETWORK_CONNECTION_STATUS);
assertThat(binder.getHotspotNetworkConnectionStatus())
- .isEqualTo(TETHER_NETWORK_CONNECTION_STATUS);
+ .isEqualTo(HOTSPOT_NETWORK_CONNECTION_STATUS);
}
@Test
@@ -225,7 +285,115 @@ public class SharedConnectivityServiceTest {
assertThat(SharedConnectivityService.areKnownNetworksEnabledForService(mContext)).isFalse();
}
- private SharedConnectivityService createService() {
+ @Test
+ public void connectHotspotNetwork() throws RemoteException, InterruptedException {
+ FakeSharedConnectivityService service = createService();
+ ISharedConnectivityService.Stub binder =
+ (ISharedConnectivityService.Stub) service.onBind(new Intent());
+ service.initializeLatch();
+
+ binder.connectHotspotNetwork(HOTSPOT_NETWORK);
+
+ assertThat(service.getLatch().await(LATCH_TIMEOUT, TimeUnit.SECONDS)).isTrue();
+ assertThat(service.getConnectedHotspotNetwork()).isEqualTo(HOTSPOT_NETWORK);
+ }
+
+ @Test
+ public void disconnectHotspotNetwork() throws RemoteException, InterruptedException {
+ FakeSharedConnectivityService service = createService();
+ ISharedConnectivityService.Stub binder =
+ (ISharedConnectivityService.Stub) service.onBind(new Intent());
+ service.initializeLatch();
+
+ binder.disconnectHotspotNetwork(HOTSPOT_NETWORK);
+
+ assertThat(service.getLatch().await(LATCH_TIMEOUT, TimeUnit.SECONDS)).isTrue();
+ assertThat(service.getDisconnectedHotspotNetwork()).isEqualTo(HOTSPOT_NETWORK);
+ }
+
+ @Test
+ public void connectKnownNetwork() throws RemoteException , InterruptedException {
+ FakeSharedConnectivityService service = createService();
+ ISharedConnectivityService.Stub binder =
+ (ISharedConnectivityService.Stub) service.onBind(new Intent());
+ service.initializeLatch();
+
+ binder.connectKnownNetwork(KNOWN_NETWORK);
+
+ assertThat(service.getLatch().await(LATCH_TIMEOUT, TimeUnit.SECONDS)).isTrue();
+ assertThat(service.getConnectedKnownNetwork()).isEqualTo(KNOWN_NETWORK);
+ }
+
+ @Test
+ public void forgetKnownNetwork() throws RemoteException, InterruptedException {
+ FakeSharedConnectivityService service = createService();
+ ISharedConnectivityService.Stub binder =
+ (ISharedConnectivityService.Stub) service.onBind(new Intent());
+ service.initializeLatch();
+
+ binder.forgetKnownNetwork(KNOWN_NETWORK);
+
+ assertThat(service.getLatch().await(LATCH_TIMEOUT, TimeUnit.SECONDS)).isTrue();
+ assertThat(service.getForgottenKnownNetwork()).isEqualTo(KNOWN_NETWORK);
+ }
+
+ @Test
+ public void registerCallback() throws RemoteException, InterruptedException {
+ SharedConnectivityService service = createService();
+ ISharedConnectivityService.Stub binder =
+ (ISharedConnectivityService.Stub) service.onBind(new Intent());
+ when(mCallback.asBinder()).thenReturn(mBinder);
+ when(mContext.getPackageName()).thenReturn("android.net.wifi.nonupdatable.test");
+ SharedConnectivitySettingsState state = buildSettingsState();
+
+ CountDownLatch latch = new CountDownLatch(1);
+ service.setCountdownLatch(latch);
+ binder.registerCallback(mCallback);
+ assertThat(latch.await(LATCH_TIMEOUT, TimeUnit.SECONDS)).isTrue();
+ service.setHotspotNetworks(HOTSPOT_NETWORKS);
+ service.setKnownNetworks(KNOWN_NETWORKS);
+ service.setSettingsState(state);
+ service.updateHotspotNetworkConnectionStatus(HOTSPOT_NETWORK_CONNECTION_STATUS);
+ service.updateKnownNetworkConnectionStatus(KNOWN_NETWORK_CONNECTION_STATUS);
+
+ verify(mCallback).onHotspotNetworksUpdated(HOTSPOT_NETWORKS);
+ verify(mCallback).onKnownNetworksUpdated(KNOWN_NETWORKS);
+ verify(mCallback).onSharedConnectivitySettingsChanged(state);
+ verify(mCallback).onHotspotNetworkConnectionStatusChanged(
+ HOTSPOT_NETWORK_CONNECTION_STATUS);
+ verify(mCallback).onKnownNetworkConnectionStatusChanged(KNOWN_NETWORK_CONNECTION_STATUS);
+ }
+
+ @Test
+ public void unregisterCallback() throws RemoteException, InterruptedException {
+ SharedConnectivityService service = createService();
+ ISharedConnectivityService.Stub binder =
+ (ISharedConnectivityService.Stub) service.onBind(new Intent());
+ when(mCallback.asBinder()).thenReturn(mBinder);
+ when(mContext.getPackageName()).thenReturn("android.net.wifi.nonupdatable.test");
+
+ CountDownLatch latch = new CountDownLatch(1);
+ service.setCountdownLatch(latch);
+ binder.registerCallback(mCallback);
+ assertThat(latch.await(LATCH_TIMEOUT, TimeUnit.SECONDS)).isTrue();
+ latch = new CountDownLatch(1);
+ service.setCountdownLatch(latch);
+ binder.unregisterCallback(mCallback);
+ assertThat(latch.await(LATCH_TIMEOUT, TimeUnit.SECONDS)).isTrue();
+ service.setHotspotNetworks(HOTSPOT_NETWORKS);
+ service.setKnownNetworks(KNOWN_NETWORKS);
+ service.setSettingsState(buildSettingsState());
+ service.updateHotspotNetworkConnectionStatus(HOTSPOT_NETWORK_CONNECTION_STATUS);
+ service.updateKnownNetworkConnectionStatus(KNOWN_NETWORK_CONNECTION_STATUS);
+
+ verify(mCallback, never()).onHotspotNetworksUpdated(any());
+ verify(mCallback, never()).onKnownNetworksUpdated(any());
+ verify(mCallback, never()).onSharedConnectivitySettingsChanged(any());
+ verify(mCallback, never()).onHotspotNetworkConnectionStatusChanged(any());
+ verify(mCallback, never()).onKnownNetworkConnectionStatusChanged(any());
+ }
+
+ private FakeSharedConnectivityService createService() {
FakeSharedConnectivityService service = new FakeSharedConnectivityService();
service.attachBaseContext(mContext);
return service;