diff options
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; |