diff options
73 files changed, 1612 insertions, 829 deletions
diff --git a/apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java b/apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java index 7a7250b9e910..8e3ed6d9931c 100644 --- a/apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java +++ b/apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java @@ -19,24 +19,27 @@ package android.view; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import android.content.Context; +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; -import androidx.benchmark.BenchmarkState; -import androidx.benchmark.junit4.BenchmarkRule; -import androidx.test.filters.SmallTest; +import androidx.test.filters.LargeTest; +import androidx.test.runner.AndroidJUnit4; import org.junit.Rule; import org.junit.Test; +import org.junit.runner.RunWith; -@SmallTest +@LargeTest +@RunWith(AndroidJUnit4.class) public class ViewConfigurationPerfTest { @Rule - public final BenchmarkRule mBenchmarkRule = new BenchmarkRule(); + public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); private final Context mContext = getInstrumentation().getTargetContext(); @Test public void testGet_newViewConfiguration() { - final BenchmarkState state = mBenchmarkRule.getState(); + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); @@ -50,7 +53,7 @@ public class ViewConfigurationPerfTest { @Test public void testGet_cachedViewConfiguration() { - final BenchmarkState state = mBenchmarkRule.getState(); + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); // Do `get` once to make sure there's something cached. ViewConfiguration.get(mContext); @@ -58,4 +61,265 @@ public class ViewConfigurationPerfTest { ViewConfiguration.get(mContext); } } + + @Test + public void testGetPressedStateDuration_unCached() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + + while (state.keepRunning()) { + state.pauseTiming(); + // Reset any caches. + ViewConfiguration.resetCacheForTesting(); + state.resumeTiming(); + + ViewConfiguration.getPressedStateDuration(); + } + } + + @Test + public void testGetPressedStateDuration_cached() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + // Do `get` once to make sure the value gets cached. + ViewConfiguration.getPressedStateDuration(); + + while (state.keepRunning()) { + ViewConfiguration.getPressedStateDuration(); + } + } + + @Test + public void testGetTapTimeout_unCached() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + + while (state.keepRunning()) { + state.pauseTiming(); + // Reset any caches. + ViewConfiguration.resetCacheForTesting(); + state.resumeTiming(); + + ViewConfiguration.getTapTimeout(); + } + } + + @Test + public void testGetTapTimeout_cached() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + // Do `get` once to make sure the value gets cached. + ViewConfiguration.getTapTimeout(); + + while (state.keepRunning()) { + ViewConfiguration.getTapTimeout(); + } + } + + @Test + public void testGetJumpTapTimeout_unCached() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + + while (state.keepRunning()) { + state.pauseTiming(); + // Reset any caches. + ViewConfiguration.resetCacheForTesting(); + state.resumeTiming(); + + ViewConfiguration.getJumpTapTimeout(); + } + } + + @Test + public void testGetJumpTapTimeout_cached() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + // Do `get` once to make sure the value gets cached. + ViewConfiguration.getJumpTapTimeout(); + + while (state.keepRunning()) { + ViewConfiguration.getJumpTapTimeout(); + } + } + + @Test + public void testGetDoubleTapTimeout_unCached() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + + while (state.keepRunning()) { + state.pauseTiming(); + // Reset any caches. + ViewConfiguration.resetCacheForTesting(); + state.resumeTiming(); + + ViewConfiguration.getDoubleTapTimeout(); + } + } + + @Test + public void testGetDoubleTapTimeout_cached() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + // Do `get` once to make sure the value gets cached. + ViewConfiguration.getDoubleTapTimeout(); + + while (state.keepRunning()) { + ViewConfiguration.getDoubleTapTimeout(); + } + } + + @Test + public void testGetDoubleTapMinTime_unCached() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + + while (state.keepRunning()) { + state.pauseTiming(); + // Reset any caches. + ViewConfiguration.resetCacheForTesting(); + state.resumeTiming(); + + ViewConfiguration.getDoubleTapMinTime(); + } + } + + @Test + public void testGetDoubleTapMinTime_cached() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + // Do `get` once to make sure the value gets cached. + ViewConfiguration.getDoubleTapMinTime(); + + while (state.keepRunning()) { + ViewConfiguration.getDoubleTapMinTime(); + } + } + + @Test + public void testGetZoomControlsTimeout_unCached() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + + while (state.keepRunning()) { + state.pauseTiming(); + // Reset any caches. + ViewConfiguration.resetCacheForTesting(); + state.resumeTiming(); + + ViewConfiguration.getZoomControlsTimeout(); + } + } + + @Test + public void testGetZoomControlsTimeout_cached() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + // Do `get` once to make sure the value gets cached. + ViewConfiguration.getZoomControlsTimeout(); + + while (state.keepRunning()) { + ViewConfiguration.getZoomControlsTimeout(); + } + } + + @Test + public void testGetLongPressTimeout() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + + while (state.keepRunning()) { + ViewConfiguration.getLongPressTimeout(); + } + } + + @Test + public void testGetMultiPressTimeout() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + + while (state.keepRunning()) { + ViewConfiguration.getMultiPressTimeout(); + } + } + + @Test + public void testGetKeyRepeatTimeout() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + + while (state.keepRunning()) { + ViewConfiguration.getKeyRepeatTimeout(); + } + } + + @Test + public void testGetKeyRepeatDelay() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + + while (state.keepRunning()) { + ViewConfiguration.getKeyRepeatDelay(); + } + } + + @Test + public void testGetHoverTapSlop_unCached() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + + while (state.keepRunning()) { + state.pauseTiming(); + // Reset any caches. + ViewConfiguration.resetCacheForTesting(); + state.resumeTiming(); + + ViewConfiguration.getHoverTapSlop(); + } + } + + @Test + public void testGetHoverTapSlop_cached() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + // Do `get` once to make sure the value gets cached. + ViewConfiguration.getHoverTapSlop(); + + while (state.keepRunning()) { + ViewConfiguration.getHoverTapSlop(); + } + } + + @Test + public void testGetScrollFriction_unCached() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + + while (state.keepRunning()) { + state.pauseTiming(); + // Reset any caches. + ViewConfiguration.resetCacheForTesting(); + state.resumeTiming(); + + ViewConfiguration.getScrollFriction(); + } + } + + @Test + public void testGetScrollFriction_cached() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + // Do `get` once to make sure the value gets cached. + ViewConfiguration.getScrollFriction(); + + while (state.keepRunning()) { + ViewConfiguration.getScrollFriction(); + } + } + + @Test + public void testGetDefaultActionModeHideDuration_unCached() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + + while (state.keepRunning()) { + state.pauseTiming(); + // Reset any caches. + ViewConfiguration.resetCacheForTesting(); + state.resumeTiming(); + + ViewConfiguration.getDefaultActionModeHideDuration(); + } + } + + @Test + public void testGetDefaultActionModeHideDuration_cached() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + // Do `get` once to make sure the value gets cached. + ViewConfiguration.getDefaultActionModeHideDuration(); + + while (state.keepRunning()) { + ViewConfiguration.getDefaultActionModeHideDuration(); + } + } } diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index 9871d713178e..ab8131ba5126 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -264,6 +264,8 @@ public class AppStandbyController @GuardedBy("mCarrierPrivilegedLock") private boolean mHaveCarrierPrivilegedApps; + private final boolean mHasFeatureTelephonySubscription; + /** List of carrier-privileged apps that should be excluded from standby */ @GuardedBy("mCarrierPrivilegedLock") private List<String> mCarrierPrivilegedApps; @@ -603,6 +605,8 @@ public class AppStandbyController mContext = mInjector.getContext(); mHandler = new AppStandbyHandler(mInjector.getLooper()); mPackageManager = mContext.getPackageManager(); + mHasFeatureTelephonySubscription = mPackageManager.hasSystemFeature( + PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION); DeviceStateReceiver deviceStateReceiver = new DeviceStateReceiver(); IntentFilter deviceStates = new IntentFilter(BatteryManager.ACTION_CHARGING); @@ -1515,7 +1519,7 @@ public class AppStandbyController } // Check this last, as it can be the most expensive check - if (isCarrierApp(packageName)) { + if (mHasFeatureTelephonySubscription && isCarrierApp(packageName)) { return STANDBY_BUCKET_EXEMPTED; } diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 2dead565fa85..f2e7e8513116 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -17,7 +17,6 @@ package android.app; import static android.app.PropertyInvalidatedCache.MODULE_SYSTEM; -import static android.app.PropertyInvalidatedCache.createSystemCacheKey; import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED; import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_NOT_COLORED; import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON; @@ -1146,12 +1145,16 @@ public class ApplicationPackageManager extends PackageManager { } } - private static final String CACHE_KEY_PACKAGES_FOR_UID_PROPERTY = - createSystemCacheKey("get_packages_for_uid"); - private static final PropertyInvalidatedCache<Integer, GetPackagesForUidResult> - mGetPackagesForUidCache = - new PropertyInvalidatedCache<Integer, GetPackagesForUidResult>( - 1024, CACHE_KEY_PACKAGES_FOR_UID_PROPERTY) { + private static final String CACHE_KEY_PACKAGES_FOR_UID_API = "get_packages_for_uid"; + + /** @hide */ + @VisibleForTesting + public static final PropertyInvalidatedCache<Integer, GetPackagesForUidResult> + sGetPackagesForUidCache = new PropertyInvalidatedCache<>( + new PropertyInvalidatedCache.Args(MODULE_SYSTEM) + .maxEntries(1024).api(CACHE_KEY_PACKAGES_FOR_UID_API).cacheNulls(true), + CACHE_KEY_PACKAGES_FOR_UID_API, null) { + @Override public GetPackagesForUidResult recompute(Integer uid) { try { @@ -1170,17 +1173,17 @@ public class ApplicationPackageManager extends PackageManager { @Override public String[] getPackagesForUid(int uid) { - return mGetPackagesForUidCache.query(uid).value(); + return sGetPackagesForUidCache.query(uid).value(); } /** @hide */ public static void disableGetPackagesForUidCache() { - mGetPackagesForUidCache.disableLocal(); + sGetPackagesForUidCache.disableLocal(); } /** @hide */ public static void invalidateGetPackagesForUidCache() { - PropertyInvalidatedCache.invalidateCache(CACHE_KEY_PACKAGES_FOR_UID_PROPERTY); + sGetPackagesForUidCache.invalidateCache(); } @Override diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index bfb33f2b7cb1..c573161f30cc 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -1163,6 +1163,17 @@ public class PropertyInvalidatedCache<Query, Result> { } /** + * Return the current cache nonce. + * @hide + */ + @VisibleForTesting + public long getNonce() { + synchronized (mLock) { + return mNonce.getNonce(); + } + } + + /** * Complete key prefixes. */ private static final String PREFIX_TEST = CACHE_KEY_PREFIX + "." + MODULE_TEST + "."; @@ -1314,7 +1325,7 @@ public class PropertyInvalidatedCache<Query, Result> { /** * Burst a property name into module and api. Throw if the key is invalid. This method is - * used in to transition legacy cache constructors to the args constructor. + * used to transition legacy cache constructors to the Args constructor. */ private static Args argsFromProperty(@NonNull String name) { throwIfInvalidCacheKey(name); @@ -1327,6 +1338,15 @@ public class PropertyInvalidatedCache<Query, Result> { } /** + * Return the API porting of a legacy property. This method is used to transition caches to + * the Args constructor. + * @hide + */ + public static String apiFromProperty(@NonNull String name) { + return argsFromProperty(name).mApi; + } + + /** * Make a new property invalidated cache. This constructor names the cache after the * property name. New clients should prefer the constructor that takes an explicit * cache name. @@ -2036,11 +2056,11 @@ public class PropertyInvalidatedCache<Query, Result> { } /** - * Disable all caches in the local process. This is primarily useful for testing when - * the test needs to bypass the cache or when the test is for a server, and the test - * process does not have privileges to write SystemProperties. Once disabled it is not - * possible to re-enable caching in the current process. If a client wants to - * temporarily disable caching, use the corking mechanism. + * Disable all caches in the local process. This is primarily useful for testing when the + * test needs to bypass the cache or when the test is for a server, and the test process does + * not have privileges to write the nonce. Once disabled it is not possible to re-enable + * caching in the current process. See {@link #testPropertyName} for a more focused way to + * bypass caches when the test is for a server. * @hide */ public static void disableForTestMode() { diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java index 0a3891fe47a1..a66d59ba9cb6 100644 --- a/core/java/android/app/appfunctions/AppFunctionManager.java +++ b/core/java/android/app/appfunctions/AppFunctionManager.java @@ -27,6 +27,7 @@ import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.annotation.UserHandleAware; +import android.app.appfunctions.AppFunctionManagerHelper.AppFunctionNotFoundException; import android.app.appsearch.AppSearchManager; import android.content.Context; import android.os.CancellationSignal; @@ -325,8 +326,28 @@ public final class AppFunctionManager { return; } + // Wrap the callback to convert AppFunctionNotFoundException to IllegalArgumentException + // to match the documentation. + OutcomeReceiver<Boolean, Exception> callbackWithExceptionInterceptor = + new OutcomeReceiver<>() { + @Override + public void onResult(@NonNull Boolean result) { + callback.onResult(result); + } + + @Override + public void onError(@NonNull Exception exception) { + if (exception instanceof AppFunctionNotFoundException) { + exception = new IllegalArgumentException(exception); + } + callback.onError(exception); + } + }; + AppFunctionManagerHelper.isAppFunctionEnabled( - functionIdentifier, targetPackage, appSearchManager, executor, callback); + functionIdentifier, targetPackage, appSearchManager, executor, + callbackWithExceptionInterceptor); + } private static class CallbackWrapper extends IAppFunctionEnabledCallback.Stub { diff --git a/core/java/android/app/appfunctions/AppFunctionManagerHelper.java b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java index cc3ca03f423d..83abc048af8a 100644 --- a/core/java/android/app/appfunctions/AppFunctionManagerHelper.java +++ b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java @@ -60,8 +60,8 @@ public class AppFunctionManagerHelper { * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors: * * <ul> - * <li>{@link IllegalArgumentException}, if the function is not found or the caller does not - * have access to it. + * <li>{@link AppFunctionNotFoundException}, if the function is not found or the caller does + * not have access to it. * </ul> * * @param functionIdentifier the identifier of the app function to check (unique within the @@ -216,7 +216,7 @@ public class AppFunctionManagerHelper { private static @NonNull Exception failedResultToException( @NonNull AppSearchResult appSearchResult) { return switch (appSearchResult.getResultCode()) { - case AppSearchResult.RESULT_INVALID_ARGUMENT -> new IllegalArgumentException( + case AppSearchResult.RESULT_INVALID_ARGUMENT -> new AppFunctionNotFoundException( appSearchResult.getErrorMessage()); case AppSearchResult.RESULT_IO_ERROR -> new IOException( appSearchResult.getErrorMessage()); @@ -225,4 +225,15 @@ public class AppFunctionManagerHelper { default -> new IllegalStateException(appSearchResult.getErrorMessage()); }; } + + /** + * Throws when the app function is not found. + * + * @hide + */ + public static class AppFunctionNotFoundException extends RuntimeException { + private AppFunctionNotFoundException(@NonNull String errorMessage) { + super(errorMessage); + } + } } diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig index fcdb02ab5da2..c3dc257e6535 100644 --- a/core/java/android/companion/virtual/flags/flags.aconfig +++ b/core/java/android/companion/virtual/flags/flags.aconfig @@ -125,3 +125,11 @@ flag { description: "Show virtual devices in Settings" bug: "338974320" } + +flag { + name: "migrate_viewconfiguration_constants_to_resources" + namespace: "virtual_devices" + description: "Use resources instead of constants in ViewConfiguration" + is_fixed_read_only: true + bug: "370928384" +} diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 0369b7d9bc28..6ae2df2cd7a2 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -16,6 +16,7 @@ package android.content.pm; +import static android.app.PropertyInvalidatedCache.MODULE_SYSTEM; import static android.content.pm.SigningInfo.AppSigningSchemeVersion; import static android.media.audio.Flags.FLAG_FEATURE_SPATIAL_AUDIO_HEADTRACKING_LOW_LATENCY; @@ -11659,11 +11660,22 @@ public abstract class PackageManager { } } - private static final PropertyInvalidatedCache<ApplicationInfoQuery, ApplicationInfo> - sApplicationInfoCache = - new PropertyInvalidatedCache<ApplicationInfoQuery, ApplicationInfo>( - 2048, PermissionManager.CACHE_KEY_PACKAGE_INFO_CACHE, - "getApplicationInfo") { + private static String packageInfoApi() { + return PropertyInvalidatedCache.apiFromProperty( + PermissionManager.CACHE_KEY_PACKAGE_INFO_CACHE); + } + + // The maximum number of entries to keep in the packageInfo and applicationInfo caches. + private final static int MAX_INFO_CACHE_ENTRIES = 2048; + + /** @hide */ + @VisibleForTesting + public static final PropertyInvalidatedCache<ApplicationInfoQuery, ApplicationInfo> + sApplicationInfoCache = new PropertyInvalidatedCache<>( + new PropertyInvalidatedCache.Args(MODULE_SYSTEM) + .maxEntries(MAX_INFO_CACHE_ENTRIES).api(packageInfoApi()).cacheNulls(true), + "getApplicationInfo", null) { + @Override public ApplicationInfo recompute(ApplicationInfoQuery query) { return getApplicationInfoAsUserUncached( @@ -11749,10 +11761,11 @@ public abstract class PackageManager { } private static final PropertyInvalidatedCache<PackageInfoQuery, PackageInfo> - sPackageInfoCache = - new PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>( - 2048, PermissionManager.CACHE_KEY_PACKAGE_INFO_CACHE, - "getPackageInfo") { + sPackageInfoCache = new PropertyInvalidatedCache<>( + new PropertyInvalidatedCache.Args(MODULE_SYSTEM) + .maxEntries(MAX_INFO_CACHE_ENTRIES).api(packageInfoApi()).cacheNulls(true), + "getPackageInfo", null) { + @Override public PackageInfo recompute(PackageInfoQuery query) { return getPackageInfoAsUserUncached( diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 507f8f4fecad..bcb7ebfb286f 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -67,6 +67,7 @@ import android.os.Handler; import android.os.HandlerExecutor; import android.os.HandlerThread; import android.os.IBinder; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceSpecificException; @@ -1704,7 +1705,9 @@ public final class CameraManager { return ICameraService.ROTATION_OVERRIDE_NONE; } - if (context != null) { + // Isolated process does not have access to ActivityTaskManager service, which is used + // indirectly in `ActivityManager.getAppTasks()`. + if (context != null && !Process.isIsolated()) { final ActivityManager activityManager = context.getSystemService(ActivityManager.class); if (activityManager != null) { for (ActivityManager.AppTask appTask : activityManager.getAppTasks()) { diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java index e79b2e7becce..26044545b8d1 100644 --- a/core/java/android/os/BaseBundle.java +++ b/core/java/android/os/BaseBundle.java @@ -45,7 +45,8 @@ import java.util.function.BiFunction; * {@link PersistableBundle} subclass. */ @android.ravenwood.annotation.RavenwoodKeepWholeClass -public class BaseBundle { +@SuppressWarnings("HiddenSuperclass") +public class BaseBundle implements Parcel.ClassLoaderProvider { /** @hide */ protected static final String TAG = "Bundle"; static final boolean DEBUG = false; @@ -311,8 +312,9 @@ public class BaseBundle { /** * Return the ClassLoader currently associated with this Bundle. + * @hide */ - ClassLoader getClassLoader() { + public ClassLoader getClassLoader() { return mClassLoader; } @@ -426,6 +428,9 @@ public class BaseBundle { if ((mFlags & Bundle.FLAG_VERIFY_TOKENS_PRESENT) != 0) { Intent.maybeMarkAsMissingCreatorToken(object); } + } else if (object instanceof Bundle) { + Bundle bundle = (Bundle) object; + bundle.setClassLoaderSameAsContainerBundleWhenRetrievedFirstTime(this); } return (clazz != null) ? clazz.cast(object) : (T) object; } @@ -499,7 +504,7 @@ public class BaseBundle { int[] numLazyValues = new int[]{0}; try { parcelledData.readArrayMap(map, count, !parcelledByNative, - /* lazy */ ownsParcel, mClassLoader, numLazyValues); + /* lazy */ ownsParcel, this, numLazyValues); } catch (BadParcelableException e) { if (sShouldDefuse) { Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e); diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java index a24dc5739b7e..c0591e6899b6 100644 --- a/core/java/android/os/Bundle.java +++ b/core/java/android/os/Bundle.java @@ -141,6 +141,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { STRIPPED.putInt("STRIPPED", 1); } + private boolean isFirstRetrievedFromABundle = false; + /** * Constructs a new, empty Bundle. */ @@ -382,7 +384,15 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { bundle.unparcel(); mOwnsLazyValues = false; bundle.mOwnsLazyValues = false; - mMap.putAll(bundle.mMap); + int N = bundle.mMap.size(); + for (int i = 0; i < N; i++) { + String key = bundle.mMap.keyAt(i); + Object value = bundle.mMap.valueAt(i); + if (value instanceof Bundle) { + ((Bundle) value).isFirstRetrievedFromABundle = true; + } + mMap.put(key, value); + } // FD and Binders state is now known if and only if both bundles already knew if ((bundle.mFlags & FLAG_HAS_FDS) != 0) { @@ -592,6 +602,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { mFlags &= ~FLAG_HAS_BINDERS_KNOWN; if (intentClass != null && intentClass.isInstance(value)) { setHasIntent(true); + } else if (value instanceof Bundle) { + ((Bundle) value).isFirstRetrievedFromABundle = true; } } @@ -793,6 +805,9 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { */ public void putBundle(@Nullable String key, @Nullable Bundle value) { unparcel(); + if (value != null) { + value.isFirstRetrievedFromABundle = true; + } mMap.put(key, value); } @@ -1020,7 +1035,9 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { return null; } try { - return (Bundle) o; + Bundle bundle = (Bundle) o; + bundle.setClassLoaderSameAsContainerBundleWhenRetrievedFirstTime(this); + return bundle; } catch (ClassCastException e) { typeWarning(key, o, "Bundle", e); return null; @@ -1028,6 +1045,21 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { } /** + * Set the ClassLoader of a bundle to its container bundle. This is necessary so that when a + * bundle's ClassLoader is changed, it can be propagated to its children. Do this only when it + * is retrieved from the container bundle first time though. Once it is accessed outside of its + * container, its ClassLoader should no longer be changed by its container anymore. + * + * @param containerBundle the bundle this bundle is retrieved from. + */ + void setClassLoaderSameAsContainerBundleWhenRetrievedFirstTime(BaseBundle containerBundle) { + if (!isFirstRetrievedFromABundle) { + setClassLoader(containerBundle.getClassLoader()); + isFirstRetrievedFromABundle = true; + } + } + + /** * Returns the value associated with the given key, or {@code null} if * no mapping of the desired type exists for the given key or a {@code null} * value is explicitly associated with the key. diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index e58934746c14..49d3f06eb80f 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -46,6 +46,7 @@ import android.util.SparseBooleanArray; import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import dalvik.annotation.optimization.CriticalNative; @@ -4661,7 +4662,7 @@ public final class Parcel { * @hide */ @Nullable - public Object readLazyValue(@Nullable ClassLoader loader) { + private Object readLazyValue(@Nullable ClassLoaderProvider loaderProvider) { int start = dataPosition(); int type = readInt(); if (isLengthPrefixed(type)) { @@ -4672,12 +4673,17 @@ public final class Parcel { int end = MathUtils.addOrThrow(dataPosition(), objectLength); int valueLength = end - start; setDataPosition(end); - return new LazyValue(this, start, valueLength, type, loader); + return new LazyValue(this, start, valueLength, type, loaderProvider); } else { - return readValue(type, loader, /* clazz */ null); + return readValue(type, getClassLoader(loaderProvider), /* clazz */ null); } } + @Nullable + private static ClassLoader getClassLoader(@Nullable ClassLoaderProvider loaderProvider) { + return loaderProvider == null ? null : loaderProvider.getClassLoader(); + } + private static final class LazyValue implements BiFunction<Class<?>, Class<?>[], Object> { /** @@ -4691,7 +4697,12 @@ public final class Parcel { private final int mPosition; private final int mLength; private final int mType; - @Nullable private final ClassLoader mLoader; + // this member is set when a bundle that includes a LazyValue is unparceled. But it is used + // when apply method is called. Between these 2 events, the bundle's ClassLoader could have + // changed. Let the bundle be a ClassLoaderProvider allows the bundle provides its current + // ClassLoader at the time apply method is called. + @NonNull + private final ClassLoaderProvider mLoaderProvider; @Nullable private Object mObject; /** @@ -4702,12 +4713,13 @@ public final class Parcel { */ @Nullable private volatile Parcel mSource; - LazyValue(Parcel source, int position, int length, int type, @Nullable ClassLoader loader) { + LazyValue(Parcel source, int position, int length, int type, + @NonNull ClassLoaderProvider loaderProvider) { mSource = requireNonNull(source); mPosition = position; mLength = length; mType = type; - mLoader = loader; + mLoaderProvider = loaderProvider; } @Override @@ -4720,7 +4732,8 @@ public final class Parcel { int restore = source.dataPosition(); try { source.setDataPosition(mPosition); - mObject = source.readValue(mLoader, clazz, itemTypes); + mObject = source.readValue(mLoaderProvider.getClassLoader(), clazz, + itemTypes); } finally { source.setDataPosition(restore); } @@ -4758,6 +4771,12 @@ public final class Parcel { return Parcel.hasFileDescriptors(mObject); } + /** @hide */ + @VisibleForTesting + public ClassLoader getClassLoader() { + return mLoaderProvider.getClassLoader(); + } + @Override public String toString() { return (mSource != null) @@ -4793,7 +4812,8 @@ public final class Parcel { return Objects.equals(mObject, value.mObject); } // Better safely fail here since this could mean we get different objects. - if (!Objects.equals(mLoader, value.mLoader)) { + if (!Objects.equals(mLoaderProvider.getClassLoader(), + value.mLoaderProvider.getClassLoader())) { return false; } // Otherwise compare metadata prior to comparing payload. @@ -4807,10 +4827,24 @@ public final class Parcel { @Override public int hashCode() { // Accessing mSource first to provide memory barrier for mObject - return Objects.hash(mSource == null, mObject, mLoader, mType, mLength); + return Objects.hash(mSource == null, mObject, mLoaderProvider.getClassLoader(), mType, + mLength); } } + /** + * Provides a ClassLoader. + * @hide + */ + public interface ClassLoaderProvider { + /** + * Returns a ClassLoader. + * + * @return ClassLoader + */ + ClassLoader getClassLoader(); + } + /** Same as {@link #readValue(ClassLoader, Class, Class[])} without any item types. */ private <T> T readValue(int type, @Nullable ClassLoader loader, @Nullable Class<T> clazz) { // Avoids allocating Class[0] array @@ -5551,8 +5585,8 @@ public final class Parcel { } private void readArrayMapInternal(@NonNull ArrayMap<? super String, Object> outVal, - int size, @Nullable ClassLoader loader) { - readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loader, null); + int size, @Nullable ClassLoaderProvider loaderProvider) { + readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loaderProvider, null); } /** @@ -5566,11 +5600,12 @@ public final class Parcel { * @hide */ void readArrayMap(ArrayMap<? super String, Object> map, int size, boolean sorted, - boolean lazy, @Nullable ClassLoader loader, int[] lazyValueCount) { + boolean lazy, @Nullable ClassLoaderProvider loaderProvider, int[] lazyValueCount) { ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, size); while (size > 0) { String key = readString(); - Object value = (lazy) ? readLazyValue(loader) : readValue(loader); + Object value = (lazy) ? readLazyValue(loaderProvider) : readValue( + getClassLoader(loaderProvider)); if (value instanceof LazyValue) { lazyValueCount[0]++; } @@ -5591,12 +5626,12 @@ public final class Parcel { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void readArrayMap(@NonNull ArrayMap<? super String, Object> outVal, - @Nullable ClassLoader loader) { + @Nullable ClassLoaderProvider loaderProvider) { final int N = readInt(); if (N < 0) { return; } - readArrayMapInternal(outVal, N, loader); + readArrayMapInternal(outVal, N, loaderProvider); } /** diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index 9e97a8eb58aa..2895bf3f846a 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -21,7 +21,9 @@ import android.annotation.NonNull; import android.annotation.TestApi; import android.annotation.UiContext; import android.app.Activity; +import android.app.ActivityThread; import android.app.AppGlobals; +import android.app.Application; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Configuration; @@ -39,14 +41,13 @@ import android.util.SparseArray; import android.util.TypedValue; import android.view.flags.Flags; +import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; /** * Contains methods to standard constants used in the UI for timeouts, sizes, and distances. */ public class ViewConfiguration { - private static final String TAG = "ViewConfiguration"; - /** * Defines the width of the horizontal scrollbar and the height of the vertical scrollbar in * dips @@ -349,6 +350,8 @@ public class ViewConfiguration { */ private static final int SMART_SELECTION_INITIALIZING_TIMEOUT_IN_MILLISECOND = 500; + private static ResourceCache sResourceCache = new ResourceCache(); + private final boolean mConstructedWithContext; private final int mEdgeSlop; private final int mFadingEdgeLength; @@ -374,7 +377,6 @@ public class ViewConfiguration { private final int mOverscrollDistance; private final int mOverflingDistance; private final boolean mViewTouchScreenHapticScrollFeedbackEnabled; - @UnsupportedAppUsage private final boolean mFadingMarqueeEnabled; private final long mGlobalActionsKeyTimeout; private final float mVerticalScrollFactor; @@ -468,14 +470,12 @@ public class ViewConfiguration { mEdgeSlop = (int) (sizeAndDensity * EDGE_SLOP + 0.5f); mFadingEdgeLength = (int) (sizeAndDensity * FADING_EDGE_LENGTH + 0.5f); - mScrollbarSize = res.getDimensionPixelSize( - com.android.internal.R.dimen.config_scrollbarSize); + mScrollbarSize = res.getDimensionPixelSize(R.dimen.config_scrollbarSize); mDoubleTapSlop = (int) (sizeAndDensity * DOUBLE_TAP_SLOP + 0.5f); mWindowTouchSlop = (int) (sizeAndDensity * WINDOW_TOUCH_SLOP + 0.5f); final TypedValue multiplierValue = new TypedValue(); - res.getValue( - com.android.internal.R.dimen.config_ambiguousGestureMultiplier, + res.getValue(R.dimen.config_ambiguousGestureMultiplier, multiplierValue, true /*resolveRefs*/); mAmbiguousGestureMultiplier = Math.max(1.0f, multiplierValue.getFloat()); @@ -488,8 +488,7 @@ public class ViewConfiguration { mOverflingDistance = (int) (sizeAndDensity * OVERFLING_DISTANCE + 0.5f); if (!sHasPermanentMenuKeySet) { - final int configVal = res.getInteger( - com.android.internal.R.integer.config_overrideHasPermanentMenuKey); + final int configVal = res.getInteger(R.integer.config_overrideHasPermanentMenuKey); switch (configVal) { default: @@ -516,32 +515,27 @@ public class ViewConfiguration { } } - mFadingMarqueeEnabled = res.getBoolean( - com.android.internal.R.bool.config_ui_enableFadingMarquee); - mTouchSlop = res.getDimensionPixelSize( - com.android.internal.R.dimen.config_viewConfigurationTouchSlop); + mFadingMarqueeEnabled = res.getBoolean(R.bool.config_ui_enableFadingMarquee); + mTouchSlop = res.getDimensionPixelSize(R.dimen.config_viewConfigurationTouchSlop); mHandwritingSlop = res.getDimensionPixelSize( - com.android.internal.R.dimen.config_viewConfigurationHandwritingSlop); - mHoverSlop = res.getDimensionPixelSize( - com.android.internal.R.dimen.config_viewConfigurationHoverSlop); + R.dimen.config_viewConfigurationHandwritingSlop); + mHoverSlop = res.getDimensionPixelSize(R.dimen.config_viewConfigurationHoverSlop); mMinScrollbarTouchTarget = res.getDimensionPixelSize( - com.android.internal.R.dimen.config_minScrollbarTouchTarget); + R.dimen.config_minScrollbarTouchTarget); mPagingTouchSlop = mTouchSlop * 2; mDoubleTapTouchSlop = mTouchSlop; mHandwritingGestureLineMargin = res.getDimensionPixelSize( - com.android.internal.R.dimen.config_viewConfigurationHandwritingGestureLineMargin); + R.dimen.config_viewConfigurationHandwritingGestureLineMargin); - mMinimumFlingVelocity = res.getDimensionPixelSize( - com.android.internal.R.dimen.config_viewMinFlingVelocity); - mMaximumFlingVelocity = res.getDimensionPixelSize( - com.android.internal.R.dimen.config_viewMaxFlingVelocity); + mMinimumFlingVelocity = res.getDimensionPixelSize(R.dimen.config_viewMinFlingVelocity); + mMaximumFlingVelocity = res.getDimensionPixelSize(R.dimen.config_viewMaxFlingVelocity); int configMinRotaryEncoderFlingVelocity = res.getDimensionPixelSize( - com.android.internal.R.dimen.config_viewMinRotaryEncoderFlingVelocity); + R.dimen.config_viewMinRotaryEncoderFlingVelocity); int configMaxRotaryEncoderFlingVelocity = res.getDimensionPixelSize( - com.android.internal.R.dimen.config_viewMaxRotaryEncoderFlingVelocity); + R.dimen.config_viewMaxRotaryEncoderFlingVelocity); if (configMinRotaryEncoderFlingVelocity < 0 || configMaxRotaryEncoderFlingVelocity < 0) { mMinimumRotaryEncoderFlingVelocity = NO_FLING_MIN_VELOCITY; mMaximumRotaryEncoderFlingVelocity = NO_FLING_MAX_VELOCITY; @@ -551,8 +545,7 @@ public class ViewConfiguration { } int configRotaryEncoderHapticScrollFeedbackTickIntervalPixels = - res.getDimensionPixelSize( - com.android.internal.R.dimen + res.getDimensionPixelSize(R.dimen .config_rotaryEncoderAxisScrollTickInterval); mRotaryEncoderHapticScrollFeedbackTickIntervalPixels = configRotaryEncoderHapticScrollFeedbackTickIntervalPixels > 0 @@ -560,41 +553,31 @@ public class ViewConfiguration { : NO_HAPTIC_SCROLL_TICK_INTERVAL; mRotaryEncoderHapticScrollFeedbackEnabled = - res.getBoolean( - com.android.internal.R.bool + res.getBoolean(R.bool .config_viewRotaryEncoderHapticScrollFedbackEnabled); - mGlobalActionsKeyTimeout = res.getInteger( - com.android.internal.R.integer.config_globalActionsKeyTimeout); + mGlobalActionsKeyTimeout = res.getInteger(R.integer.config_globalActionsKeyTimeout); - mHorizontalScrollFactor = res.getDimensionPixelSize( - com.android.internal.R.dimen.config_horizontalScrollFactor); - mVerticalScrollFactor = res.getDimensionPixelSize( - com.android.internal.R.dimen.config_verticalScrollFactor); + mHorizontalScrollFactor = res.getDimensionPixelSize(R.dimen.config_horizontalScrollFactor); + mVerticalScrollFactor = res.getDimensionPixelSize(R.dimen.config_verticalScrollFactor); mShowMenuShortcutsWhenKeyboardPresent = res.getBoolean( - com.android.internal.R.bool.config_showMenuShortcutsWhenKeyboardPresent); + R.bool.config_showMenuShortcutsWhenKeyboardPresent); - mMinScalingSpan = res.getDimensionPixelSize( - com.android.internal.R.dimen.config_minScalingSpan); + mMinScalingSpan = res.getDimensionPixelSize(R.dimen.config_minScalingSpan); - mScreenshotChordKeyTimeout = res.getInteger( - com.android.internal.R.integer.config_screenshotChordKeyTimeout); + mScreenshotChordKeyTimeout = res.getInteger(R.integer.config_screenshotChordKeyTimeout); mSmartSelectionInitializedTimeout = res.getInteger( - com.android.internal.R.integer.config_smartSelectionInitializedTimeoutMillis); + R.integer.config_smartSelectionInitializedTimeoutMillis); mSmartSelectionInitializingTimeout = res.getInteger( - com.android.internal.R.integer.config_smartSelectionInitializingTimeoutMillis); - mPreferKeepClearForFocusEnabled = res.getBoolean( - com.android.internal.R.bool.config_preferKeepClearForFocus); + R.integer.config_smartSelectionInitializingTimeoutMillis); + mPreferKeepClearForFocusEnabled = res.getBoolean(R.bool.config_preferKeepClearForFocus); mViewBasedRotaryEncoderScrollHapticsEnabledConfig = - res.getBoolean( - com.android.internal.R.bool.config_viewBasedRotaryEncoderHapticsEnabled); + res.getBoolean(R.bool.config_viewBasedRotaryEncoderHapticsEnabled); mViewTouchScreenHapticScrollFeedbackEnabled = Flags.enableScrollFeedbackForTouch() - ? res.getBoolean( - com.android.internal.R.bool - .config_viewTouchScreenHapticScrollFeedbackEnabled) + ? res.getBoolean(R.bool.config_viewTouchScreenHapticScrollFeedbackEnabled) : false; } @@ -632,6 +615,7 @@ public class ViewConfiguration { @VisibleForTesting public static void resetCacheForTesting() { sConfigurations.clear(); + sResourceCache = new ResourceCache(); } /** @@ -707,7 +691,7 @@ public class ViewConfiguration { * components. */ public static int getPressedStateDuration() { - return PRESSED_STATE_DURATION; + return sResourceCache.getPressedStateDuration(); } /** @@ -752,7 +736,7 @@ public class ViewConfiguration { * considered to be a tap. */ public static int getTapTimeout() { - return TAP_TIMEOUT; + return sResourceCache.getTapTimeout(); } /** @@ -761,7 +745,7 @@ public class ViewConfiguration { * considered to be a tap. */ public static int getJumpTapTimeout() { - return JUMP_TAP_TIMEOUT; + return sResourceCache.getJumpTapTimeout(); } /** @@ -770,7 +754,7 @@ public class ViewConfiguration { * double-tap. */ public static int getDoubleTapTimeout() { - return DOUBLE_TAP_TIMEOUT; + return sResourceCache.getDoubleTapTimeout(); } /** @@ -782,7 +766,7 @@ public class ViewConfiguration { */ @UnsupportedAppUsage public static int getDoubleTapMinTime() { - return DOUBLE_TAP_MIN_TIME; + return sResourceCache.getDoubleTapMinTime(); } /** @@ -792,7 +776,7 @@ public class ViewConfiguration { * @hide */ public static int getHoverTapTimeout() { - return HOVER_TAP_TIMEOUT; + return sResourceCache.getHoverTapTimeout(); } /** @@ -803,7 +787,7 @@ public class ViewConfiguration { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public static int getHoverTapSlop() { - return HOVER_TAP_SLOP; + return sResourceCache.getHoverTapSlop(); } /** @@ -1044,7 +1028,7 @@ public class ViewConfiguration { * in milliseconds. */ public static long getZoomControlsTimeout() { - return ZOOM_CONTROLS_TIMEOUT; + return sResourceCache.getZoomControlsTimeout(); } /** @@ -1113,14 +1097,14 @@ public class ViewConfiguration { * friction. */ public static float getScrollFriction() { - return SCROLL_FRICTION; + return sResourceCache.getScrollFriction(); } /** * @return the default duration in milliseconds for {@link ActionMode#hide(long)}. */ public static long getDefaultActionModeHideDuration() { - return ACTION_MODE_HIDE_DURATION_DEFAULT; + return sResourceCache.getDefaultActionModeHideDuration(); } /** @@ -1471,8 +1455,137 @@ public class ViewConfiguration { return HOVER_TOOLTIP_HIDE_SHORT_TIMEOUT; } - private static final int getDisplayDensity(Context context) { + private static int getDisplayDensity(Context context) { final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); return (int) (100.0f * metrics.density); } + + /** + * Fetches resource values statically and caches them locally for fast lookup. Note that these + * values will not be updated during the lifetime of a process, even if resource overlays are + * applied. + */ + private static final class ResourceCache { + + private int mPressedStateDuration = -1; + private int mTapTimeout = -1; + private int mJumpTapTimeout = -1; + private int mDoubleTapTimeout = -1; + private int mDoubleTapMinTime = -1; + private int mHoverTapTimeout = -1; + private int mHoverTapSlop = -1; + private long mZoomControlsTimeout = -1L; + private float mScrollFriction = -1f; + private long mDefaultActionModeHideDuration = -1L; + + public int getPressedStateDuration() { + if (mPressedStateDuration < 0) { + Resources resources = getCurrentResources(); + mPressedStateDuration = resources != null + ? resources.getInteger(R.integer.config_pressedStateDurationMillis) + : PRESSED_STATE_DURATION; + } + return mPressedStateDuration; + } + + public int getTapTimeout() { + if (mTapTimeout < 0) { + Resources resources = getCurrentResources(); + mTapTimeout = resources != null + ? resources.getInteger(R.integer.config_tapTimeoutMillis) + : TAP_TIMEOUT; + } + return mTapTimeout; + } + + public int getJumpTapTimeout() { + if (mJumpTapTimeout < 0) { + Resources resources = getCurrentResources(); + mJumpTapTimeout = resources != null + ? resources.getInteger(R.integer.config_jumpTapTimeoutMillis) + : JUMP_TAP_TIMEOUT; + } + return mJumpTapTimeout; + } + + public int getDoubleTapTimeout() { + if (mDoubleTapTimeout < 0) { + Resources resources = getCurrentResources(); + mDoubleTapTimeout = resources != null + ? resources.getInteger(R.integer.config_doubleTapTimeoutMillis) + : DOUBLE_TAP_TIMEOUT; + } + return mDoubleTapTimeout; + } + + public int getDoubleTapMinTime() { + if (mDoubleTapMinTime < 0) { + Resources resources = getCurrentResources(); + mDoubleTapMinTime = resources != null + ? resources.getInteger(R.integer.config_doubleTapMinTimeMillis) + : DOUBLE_TAP_MIN_TIME; + } + return mDoubleTapMinTime; + } + + public int getHoverTapTimeout() { + if (mHoverTapTimeout < 0) { + Resources resources = getCurrentResources(); + mHoverTapTimeout = resources != null + ? resources.getInteger(R.integer.config_hoverTapTimeoutMillis) + : HOVER_TAP_TIMEOUT; + } + return mHoverTapTimeout; + } + + public int getHoverTapSlop() { + if (mHoverTapSlop < 0) { + Resources resources = getCurrentResources(); + mHoverTapSlop = resources != null + ? resources.getDimensionPixelSize(R.dimen.config_hoverTapSlop) + : HOVER_TAP_SLOP; + } + return mHoverTapSlop; + } + + public long getZoomControlsTimeout() { + if (mZoomControlsTimeout < 0) { + Resources resources = getCurrentResources(); + mZoomControlsTimeout = resources != null + ? resources.getInteger(R.integer.config_zoomControlsTimeoutMillis) + : ZOOM_CONTROLS_TIMEOUT; + } + return mZoomControlsTimeout; + } + + public float getScrollFriction() { + if (mScrollFriction < 0) { + Resources resources = getCurrentResources(); + mScrollFriction = resources != null + ? resources.getFloat(R.dimen.config_scrollFriction) + : SCROLL_FRICTION; + } + return mScrollFriction; + } + + public long getDefaultActionModeHideDuration() { + if (mDefaultActionModeHideDuration < 0) { + Resources resources = getCurrentResources(); + mDefaultActionModeHideDuration = resources != null + ? resources.getInteger(R.integer.config_defaultActionModeHideDurationMillis) + : ACTION_MODE_HIDE_DURATION_DEFAULT; + } + return mDefaultActionModeHideDuration; + } + + private static Resources getCurrentResources() { + if (!android.companion.virtualdevice.flags.Flags + .migrateViewconfigurationConstantsToResources()) { + return null; + } + Application application = ActivityThread.currentApplication(); + Context context = application != null ? application.getApplicationContext() : null; + return context != null ? context.getResources() : null; + } + } } diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java index 7c5335cc753c..9085bbec949f 100644 --- a/core/java/com/android/internal/jank/Cuj.java +++ b/core/java/com/android/internal/jank/Cuj.java @@ -306,8 +306,15 @@ public class Cuj { /** Track work utility view animation shrinking when scrolling down app list. */ public static final int CUJ_LAUNCHER_WORK_UTILITY_VIEW_SHRINK = 127; + /** + * Track task transitions + * + * <p>Tracking starts and ends with the animation.</p> + */ + public static final int CUJ_DEFAULT_TASK_TO_TASK_ANIMATION = 128; + // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE. - @VisibleForTesting static final int LAST_CUJ = CUJ_LAUNCHER_WORK_UTILITY_VIEW_SHRINK; + @VisibleForTesting static final int LAST_CUJ = CUJ_DEFAULT_TASK_TO_TASK_ANIMATION; /** @hide */ @IntDef({ @@ -426,7 +433,8 @@ public class Cuj { CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_ICON, CUJ_DESKTOP_MODE_KEYBOARD_QUICK_SWITCH_APP_LAUNCH, CUJ_LAUNCHER_WORK_UTILITY_VIEW_EXPAND, - CUJ_LAUNCHER_WORK_UTILITY_VIEW_SHRINK + CUJ_LAUNCHER_WORK_UTILITY_VIEW_SHRINK, + CUJ_DEFAULT_TASK_TO_TASK_ANIMATION }) @Retention(RetentionPolicy.SOURCE) public @interface CujType {} @@ -556,6 +564,7 @@ public class Cuj { CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_KEYBOARD_QUICK_SWITCH_APP_LAUNCH] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_KEYBOARD_QUICK_SWITCH_APP_LAUNCH; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_WORK_UTILITY_VIEW_EXPAND] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_WORK_UTILITY_VIEW_EXPAND; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_WORK_UTILITY_VIEW_SHRINK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_WORK_UTILITY_VIEW_SHRINK; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DEFAULT_TASK_TO_TASK_ANIMATION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DEFAULT_TASK_TO_TASK_ANIMATION; } private Cuj() { @@ -806,6 +815,8 @@ public class Cuj { return "LAUNCHER_WORK_UTILITY_VIEW_EXPAND"; case CUJ_LAUNCHER_WORK_UTILITY_VIEW_SHRINK: return "LAUNCHER_WORK_UTILITY_VIEW_SHRINK"; + case CUJ_DEFAULT_TASK_TO_TASK_ANIMATION: + return "CUJ_DEFAULT_TASK_TO_TASK_ANIMATION"; } return "UNKNOWN"; } diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 8db94a420e4c..37e553ea3827 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3061,6 +3061,43 @@ {@link MotionEvent#ACTION_SCROLL} event. --> <dimen name="config_scrollFactor">64dp</dimen> + <!-- Duration in milliseconds of the pressed state in child components. --> + <integer name="config_pressedStateDurationMillis">64</integer> + + <!-- Duration in milliseconds we will wait to see if a touch event is a tap or a scroll. + If the user does not move within this interval, it is considered to be a tap. --> + <integer name="config_tapTimeoutMillis">100</integer> + + <!-- Duration in milliseconds we will wait to see if a touch event is a jump tap. + If the user does not move within this interval, it is considered to be a tap. --> + <integer name="config_jumpTapTimeoutMillis">500</integer> + + <!-- Duration in milliseconds between the first tap's up event and the second tap's down + event for an interaction to be considered a double-tap. --> + <integer name="config_doubleTapTimeoutMillis">300</integer> + + <!-- Minimum duration in milliseconds between the first tap's up event and the second tap's + down event for an interaction to be considered a double-tap. --> + <integer name="config_doubleTapMinTimeMillis">40</integer> + + <!-- Maximum duration in milliseconds between a touch pad touch and release for a given touch + to be considered a tap (click) as opposed to a hover movement gesture. --> + <integer name="config_hoverTapTimeoutMillis">150</integer> + + <!-- The amount of time in milliseconds that the zoom controls should be displayed on the + screen. --> + <integer name="config_zoomControlsTimeoutMillis">3000</integer> + + <!-- Default duration in milliseconds for {@link ActionMode#hide(long)}. --> + <integer name="config_defaultActionModeHideDurationMillis">2000</integer> + + <!-- Maximum distance in pixels that a touch pad touch can move before being released + for it to be considered a tap (click) as opposed to a hover movement gesture. --> + <dimen name="config_hoverTapSlop">20px</dimen> + + <!-- The amount of friction applied to scrolls and flings. --> + <item name="config_scrollFriction" format="float" type="dimen">0.015</item> + <!-- Maximum number of grid columns permitted in the ResolverActivity used for picking activities to handle an intent. --> <integer name="config_maxResolverActivityColumns">3</integer> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 8c2ca97af493..a34fbb89c629 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4153,6 +4153,17 @@ <java-symbol type="string" name="config_headlineFontFamily" /> <java-symbol type="string" name="config_headlineFontFamilyMedium" /> + <java-symbol type="integer" name="config_pressedStateDurationMillis" /> + <java-symbol type="integer" name="config_tapTimeoutMillis" /> + <java-symbol type="integer" name="config_jumpTapTimeoutMillis" /> + <java-symbol type="integer" name="config_doubleTapTimeoutMillis" /> + <java-symbol type="integer" name="config_doubleTapMinTimeMillis" /> + <java-symbol type="integer" name="config_hoverTapTimeoutMillis" /> + <java-symbol type="integer" name="config_zoomControlsTimeoutMillis" /> + <java-symbol type="integer" name="config_defaultActionModeHideDurationMillis" /> + <java-symbol type="dimen" name="config_hoverTapSlop" /> + <java-symbol type="dimen" name="config_scrollFriction" /> + <java-symbol type="drawable" name="stat_sys_vitals" /> <java-symbol type="color" name="text_color_primary" /> diff --git a/core/tests/coretests/src/android/content/IntentTest.java b/core/tests/coretests/src/android/content/IntentTest.java index fdfb0c34cdeb..fa1948d9786c 100644 --- a/core/tests/coretests/src/android/content/IntentTest.java +++ b/core/tests/coretests/src/android/content/IntentTest.java @@ -23,6 +23,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.os.Binder; +import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; import android.platform.test.annotations.Presubmit; @@ -238,4 +239,42 @@ public class IntentTest { // Not all keys from intent are kept - clip data keys are dropped. assertFalse(intent.getExtraIntentKeys().containsAll(originalIntentKeys)); } + + @Test + public void testSetIntentExtrasClassLoaderEffectiveAfterExtraBundleUnparcel() { + Intent intent = new Intent(); + intent.putExtra("bundle", new Bundle()); + + final Parcel parcel = Parcel.obtain(); + intent.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + final Intent target = new Intent(); + target.readFromParcel(parcel); + target.collectExtraIntentKeys(); + ClassLoader cl = new ClassLoader() { + }; + target.setExtrasClassLoader(cl); + assertThat(target.getBundleExtra("bundle").getClassLoader()).isEqualTo(cl); + } + + @Test + public void testBundlePutAllClassLoader() { + Intent intent = new Intent(); + Bundle b1 = new Bundle(); + b1.putBundle("bundle", new Bundle()); + intent.putExtra("b1", b1); + final Parcel parcel = Parcel.obtain(); + intent.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + final Intent target = new Intent(); + target.readFromParcel(parcel); + + ClassLoader cl = new ClassLoader() { + }; + target.setExtrasClassLoader(cl); + Bundle b2 = new Bundle(); + b2.putAll(target.getBundleExtra("b1")); + assertThat(b2.getBundle("bundle").getClassLoader()).isEqualTo(cl); + } + } diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/draganddrop/DragAndDropConstants.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/draganddrop/DragAndDropConstants.java index 4127adc1f901..12938db07ece 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/draganddrop/DragAndDropConstants.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/draganddrop/DragAndDropConstants.java @@ -24,4 +24,9 @@ public class DragAndDropConstants { * ignore drag events. */ public static final String EXTRA_DISALLOW_HIT_REGION = "DISALLOW_HIT_REGION"; + + /** + * An Intent extra that Launcher can use to specify the {@link android.content.pm.ShortcutInfo} + */ + public static final String EXTRA_SHORTCUT_INFO = "EXTRA_SHORTCUT_INFO"; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index ddcdf9f8c617..d9489287ff42 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -54,6 +54,7 @@ import com.android.launcher3.icons.BubbleIconFactory; import com.android.wm.shell.Flags; import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView; import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; +import com.android.wm.shell.common.ComponentUtils; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.shared.bubbles.BubbleInfo; @@ -282,6 +283,29 @@ public class Bubble implements BubbleViewProvider { mPackageName = intent.getPackage(); } + private Bubble( + PendingIntent intent, + UserHandle user, + String key, + @ShellMainThread Executor mainExecutor, + @ShellBackgroundThread Executor bgExecutor) { + mGroupKey = null; + mLocusId = null; + mFlags = 0; + mUser = user; + mIcon = null; + mType = BubbleType.TYPE_APP; + mKey = key; + mShowBubbleUpdateDot = false; + mMainExecutor = mainExecutor; + mBgExecutor = bgExecutor; + mTaskId = INVALID_TASK_ID; + mPendingIntent = intent; + mIntent = null; + mDesiredHeight = Integer.MAX_VALUE; + mPackageName = ComponentUtils.getPackageName(intent); + } + private Bubble(ShortcutInfo info, @ShellMainThread Executor mainExecutor, @ShellBackgroundThread Executor bgExecutor) { mGroupKey = null; @@ -336,6 +360,15 @@ public class Bubble implements BubbleViewProvider { } /** Creates an app bubble. */ + public static Bubble createAppBubble(PendingIntent intent, UserHandle user, + @ShellMainThread Executor mainExecutor, @ShellBackgroundThread Executor bgExecutor) { + return new Bubble(intent, + user, + /* key= */ getAppBubbleKeyForApp(ComponentUtils.getPackageName(intent), user), + mainExecutor, bgExecutor); + } + + /** Creates an app bubble. */ public static Bubble createAppBubble(Intent intent, UserHandle user, @Nullable Icon icon, @ShellMainThread Executor mainExecutor, @ShellBackgroundThread Executor bgExecutor) { return new Bubble(intent, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index b93b7b86e661..8cf2370df48d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -46,6 +46,7 @@ import android.app.NotificationChannel; import android.app.PendingIntent; import android.app.TaskInfo; import android.content.BroadcastReceiver; +import android.content.ClipDescription; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -118,6 +119,7 @@ import com.android.wm.shell.shared.bubbles.BubbleBarLocation; import com.android.wm.shell.shared.bubbles.BubbleBarUpdate; import com.android.wm.shell.shared.bubbles.BubbleDropTargetBoundsProvider; import com.android.wm.shell.shared.bubbles.DeviceConfig; +import com.android.wm.shell.shared.draganddrop.DragAndDropConstants; import com.android.wm.shell.sysui.ConfigurationChangeListener; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; @@ -872,11 +874,19 @@ public class BubbleController implements ConfigurationChangeListener, } @Override - public void onItemDroppedOverBubbleBarDragZone(BubbleBarLocation location, Intent appIntent, - UserHandle userHandle) { - if (isShowingAsBubbleBar() && BubbleAnythingFlagHelper.enableCreateAnyBubble()) { - hideBubbleBarExpandedViewDropTarget(); - expandStackAndSelectBubble(appIntent, userHandle, location); + public void onItemDroppedOverBubbleBarDragZone(BubbleBarLocation location, Intent itemIntent) { + hideBubbleBarExpandedViewDropTarget(); + ShortcutInfo shortcutInfo = (ShortcutInfo) itemIntent + .getExtra(DragAndDropConstants.EXTRA_SHORTCUT_INFO); + if (shortcutInfo != null) { + expandStackAndSelectBubble(shortcutInfo, location); + return; + } + UserHandle user = (UserHandle) itemIntent.getExtra(Intent.EXTRA_USER); + PendingIntent pendingIntent = (PendingIntent) itemIntent + .getExtra(ClipDescription.EXTRA_PENDING_INTENT); + if (pendingIntent != null && user != null) { + expandStackAndSelectBubble(pendingIntent, user, location); } } @@ -1506,9 +1516,16 @@ public class BubbleController implements ConfigurationChangeListener, * Expands and selects a bubble created or found via the provided shortcut info. * * @param info the shortcut info for the bubble. + * @param bubbleBarLocation optional location in case bubble bar should be repositioned. */ - public void expandStackAndSelectBubble(ShortcutInfo info) { + public void expandStackAndSelectBubble(ShortcutInfo info, + @Nullable BubbleBarLocation bubbleBarLocation) { if (!BubbleAnythingFlagHelper.enableCreateAnyBubble()) return; + if (bubbleBarLocation != null) { + //TODO (b/388894910) combine location update with the setSelectedBubbleAndExpandStack & + // fix bubble bar flicking + setBubbleBarLocation(bubbleBarLocation, BubbleBarLocation.UpdateSource.APP_ICON_DRAG); + } Bubble b = mBubbleData.getOrCreateBubble(info); // Removes from overflow ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - shortcut=%s", info); if (b.isInflated()) { @@ -1524,7 +1541,25 @@ public class BubbleController implements ConfigurationChangeListener, * * @param intent the intent for the bubble. */ - public void expandStackAndSelectBubble(Intent intent, UserHandle user, + public void expandStackAndSelectBubble(Intent intent, UserHandle user) { + if (!BubbleAnythingFlagHelper.enableCreateAnyBubble()) return; + Bubble b = mBubbleData.getOrCreateBubble(intent, user); // Removes from overflow + ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - intent=%s", intent); + if (b.isInflated()) { + mBubbleData.setSelectedBubbleAndExpandStack(b); + } else { + b.enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); + inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false); + } + } + + /** + * Expands and selects a bubble created or found for this app. + * + * @param pendingIntent the intent for the bubble. + * @param bubbleBarLocation optional location in case bubble bar should be repositioned. + */ + public void expandStackAndSelectBubble(PendingIntent pendingIntent, UserHandle user, @Nullable BubbleBarLocation bubbleBarLocation) { if (!BubbleAnythingFlagHelper.enableCreateAnyBubble()) return; if (bubbleBarLocation != null) { @@ -1532,8 +1567,9 @@ public class BubbleController implements ConfigurationChangeListener, // fix bubble bar flicking setBubbleBarLocation(bubbleBarLocation, BubbleBarLocation.UpdateSource.APP_ICON_DRAG); } - Bubble b = mBubbleData.getOrCreateBubble(intent, user); // Removes from overflow - ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - intent=%s", intent); + Bubble b = mBubbleData.getOrCreateBubble(pendingIntent, user); + ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - pendingIntent=%s", + pendingIntent); if (b.isInflated()) { mBubbleData.setSelectedBubbleAndExpandStack(b); } else { @@ -2756,13 +2792,13 @@ public class BubbleController implements ConfigurationChangeListener, @Override public void showShortcutBubble(ShortcutInfo info) { - mMainExecutor.execute(() -> mController.expandStackAndSelectBubble(info)); + mMainExecutor.execute(() -> mController + .expandStackAndSelectBubble(info, /* bubbleBarLocation = */ null)); } @Override public void showAppBubble(Intent intent, UserHandle user) { - mMainExecutor.execute(() -> mController.expandStackAndSelectBubble(intent, - user, /* bubbleBarLocation = */ null)); + mMainExecutor.execute(() -> mController.expandStackAndSelectBubble(intent, user)); } @Override @@ -2983,9 +3019,10 @@ public class BubbleController implements ConfigurationChangeListener, @Override public void expandStackAndSelectBubble(ShortcutInfo info) { - mMainExecutor.execute(() -> { - BubbleController.this.expandStackAndSelectBubble(info); - }); + mMainExecutor.execute(() -> + BubbleController.this + .expandStackAndSelectBubble(info, /* bubbleBarLocation = */ null) + ); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index 96d0f6d5654e..f97133a4c3d1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -471,6 +471,16 @@ public class BubbleData { return bubbleToReturn; } + Bubble getOrCreateBubble(PendingIntent pendingIntent, UserHandle user) { + String bubbleKey = Bubble.getAppBubbleKeyForApp(pendingIntent.getCreatorPackage(), user); + Bubble bubbleToReturn = findAndRemoveBubbleFromOverflow(bubbleKey); + if (bubbleToReturn == null) { + bubbleToReturn = Bubble.createAppBubble(pendingIntent, user, mMainExecutor, + mBgExecutor); + } + return bubbleToReturn; + } + Bubble getOrCreateBubble(TaskInfo taskInfo) { UserHandle user = UserHandle.of(mCurrentUserId); String bubbleKey = Bubble.getAppBubbleKeyForTask(taskInfo); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java index 4a0eee861d21..e47ac61a53dd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java @@ -117,15 +117,24 @@ public class BubbleTaskViewHelper { Context context = mContext.createContextAsUser( mBubble.getUser(), Context.CONTEXT_RESTRICTED); - PendingIntent pi = PendingIntent.getActivity( - context, - /* requestCode= */ 0, - mBubble.getIntent() - .addFlags(FLAG_ACTIVITY_MULTIPLE_TASK), - PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT, - /* options= */ null); - mTaskView.startActivity(pi, /* fillInIntent= */ null, options, - launchBounds); + Intent fillInIntent = null; + //first try get pending intent from the bubble + PendingIntent pi = mBubble.getPendingIntent(); + if (pi == null) { + // if null - create new one + pi = PendingIntent.getActivity( + context, + /* requestCode= */ 0, + mBubble.getIntent() + .addFlags(FLAG_ACTIVITY_MULTIPLE_TASK), + PendingIntent.FLAG_IMMUTABLE + | PendingIntent.FLAG_UPDATE_CURRENT, + /* options= */ null); + } else { + fillInIntent = new Intent(pi.getIntent()); + fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); + } + mTaskView.startActivity(pi, fillInIntent, options, launchBounds); } else if (isShortcutBubble) { options.setLaunchedFromBubble(true); options.setApplyActivityFlagsForBubbles(true); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarDragListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarDragListener.kt index afe5c87604d9..3ff80b5ab8ac 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarDragListener.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarDragListener.kt @@ -18,7 +18,6 @@ package com.android.wm.shell.bubbles.bar import android.content.Intent import android.graphics.Rect -import android.os.UserHandle import com.android.wm.shell.shared.bubbles.BubbleBarLocation /** Controller that takes care of the bubble bar drag events. */ @@ -31,11 +30,7 @@ interface BubbleBarDragListener { fun onItemDraggedOutsideBubbleBarDropZone() /** Called when the drop event happens over the bubble bar drop zone. */ - fun onItemDroppedOverBubbleBarDragZone( - location: BubbleBarLocation, - intent: Intent, - userHandle: UserHandle - ) + fun onItemDroppedOverBubbleBarDragZone(location: BubbleBarLocation, itemIntent: Intent) /** * Returns mapping of the bubble bar locations to the corresponding diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 3c7780711a14..f48b34d0d646 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -3074,6 +3074,7 @@ class DesktopTasksController( ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS pendingIntentLaunchFlags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK + splashScreenStyle = SPLASH_SCREEN_STYLE_ICON } if (windowingMode == WINDOWING_MODE_FULLSCREEN) { dragAndDropFullscreenCookie = Binder() @@ -3082,7 +3083,12 @@ class DesktopTasksController( val wct = WindowContainerTransaction() wct.sendPendingIntent(launchIntent, null, opts.toBundle()) if (windowingMode == WINDOWING_MODE_FREEFORM) { - desktopModeDragAndDropTransitionHandler.handleDropEvent(wct) + if (DesktopModeFlags.ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX.isTrue()) { + // TODO b/376389593: Use a custom tab tearing transition/animation + startLaunchTransition(TRANSIT_OPEN, wct, launchingTaskId = null) + } else { + desktopModeDragAndDropTransitionHandler.handleDropEvent(wct) + } } else { transitions.startTransition(TRANSIT_OPEN, wct, null) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java index 2571e0e36cd9..b3c1a92f5e1d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java @@ -47,7 +47,6 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; import android.graphics.drawable.Drawable; -import android.os.UserHandle; import android.view.DragEvent; import android.view.SurfaceControl; import android.view.View; @@ -70,6 +69,7 @@ import com.android.wm.shell.bubbles.bar.BubbleBarDragListener; import com.android.wm.shell.common.split.SplitScreenUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.animation.Interpolators; +import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper; import com.android.wm.shell.shared.bubbles.BubbleBarLocation; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -627,8 +627,7 @@ public class DragLayout extends LinearLayout @Nullable private BubbleBarLocation getBubbleBarLocation(int x, int y) { Intent appData = mSession.appData; - if (appData == null || appData.getExtra(Intent.EXTRA_INTENT) == null - || appData.getExtra(Intent.EXTRA_USER) == null) { + if (appData == null) { // there is no app data, so drop event over the bubble bar can not be handled return null; } @@ -686,11 +685,10 @@ public class DragLayout extends LinearLayout // Process the drop exclusive by DropTarget OR by the BubbleBar if (mCurrentTarget != null) { mPolicy.onDropped(mCurrentTarget, hideTaskToken); - } else if (appData != null && mCurrentBubbleBarTarget != null) { - Intent appIntent = (Intent) appData.getExtra(Intent.EXTRA_INTENT); - UserHandle user = (UserHandle) appData.getExtra(Intent.EXTRA_USER); + } else if (appData != null && mCurrentBubbleBarTarget != null + && BubbleAnythingFlagHelper.enableCreateAnyBubble()) { mBubbleBarDragListener.onItemDroppedOverBubbleBarDragZone(mCurrentBubbleBarTarget, - appIntent, user); + appData); } // Start animating the drop UI out with the drag surface diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 0689205a1110..a5a5fd73d5c0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -39,9 +39,12 @@ import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; +import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_RELAUNCH; import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL; import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL; import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS; @@ -55,6 +58,7 @@ import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; import static android.window.TransitionInfo.FLAG_TRANSLUCENT; +import static com.android.internal.jank.Cuj.CUJ_DEFAULT_TASK_TO_TASK_ANIMATION; import static com.android.internal.policy.TransitionAnimation.DEFAULT_APP_TRANSITION_DURATION; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CHANGE; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE; @@ -101,6 +105,7 @@ import android.window.WindowContainerTransaction; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.policy.ScreenDecorationsUtils; import com.android.internal.policy.TransitionAnimation; import com.android.internal.protolog.ProtoLog; @@ -144,6 +149,9 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { private Drawable mEnterpriseThumbnailDrawable; + static final InteractionJankMonitor sInteractionJankMonitor = + InteractionJankMonitor.getInstance(); + private BroadcastReceiver mEnterpriseResourceUpdatedReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -321,8 +329,17 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { final ArrayList<Animator> animations = new ArrayList<>(); mAnimations.put(transition, animations); + final boolean isTaskTransition = isTaskTransition(info); + if (isTaskTransition) { + sInteractionJankMonitor.begin(info.getRoot(0).getLeash(), mContext, + mMainHandler, CUJ_DEFAULT_TASK_TO_TASK_ANIMATION); + } + final Runnable onAnimFinish = () -> { if (!animations.isEmpty()) return; + if (isTaskTransition) { + sInteractionJankMonitor.end(CUJ_DEFAULT_TASK_TO_TASK_ANIMATION); + } mAnimations.remove(transition); finishCallback.onTransitionFinished(null /* wct */); }; @@ -678,6 +695,30 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } /** + * A task transition is defined as a transition where there is exaclty one open/to_front task + * and one close/to_back task. Nothing else is allowed to be included in the transition + */ + public static boolean isTaskTransition(@NonNull TransitionInfo info) { + if (info.getChanges().size() != 2) { + return false; + } + boolean hasOpeningTask = false; + boolean hasClosingTask = false; + + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (change.getTaskInfo() == null) { + // A non-task is in the transition + return false; + } + int mode = change.getMode(); + hasOpeningTask |= mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT; + hasClosingTask |= mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK; + } + return hasOpeningTask && hasClosingTask; + } + + /** * Does `info` only contain translucent visibility changes (CHANGEs are ignored). We select * different animations and z-orders for these */ @@ -986,4 +1027,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { || animType == ANIM_CLIP_REVEAL || animType == ANIM_OPEN_CROSS_PROFILE_APPS || animType == ANIM_FROM_STYLE; } + + @Override + public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, + @Nullable SurfaceControl.Transaction finishTransaction) { + sInteractionJankMonitor.cancel(CUJ_DEFAULT_TASK_TO_TASK_ANIMATION); + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index edb9b2d2fede..cd1c16a93475 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -5427,38 +5427,90 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun onUnhandledDrag_newFreeformIntent() { + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX) + fun onUnhandledDrag_newFreeformIntent_tabTearingAnimationBugfixFlagEnabled() { testOnUnhandledDrag( DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR, PointF(1200f, 700f), Rect(240, 700, 2160, 1900), + tabTearingAnimationFlagEnabled = true, ) } @Test - fun onUnhandledDrag_newFreeformIntentSplitLeft() { + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX) + fun onUnhandledDrag_newFreeformIntent_tabTearingAnimationBugfixFlagDisabled() { + testOnUnhandledDrag( + DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR, + PointF(1200f, 700f), + Rect(240, 700, 2160, 1900), + tabTearingAnimationFlagEnabled = false, + ) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX) + fun onUnhandledDrag_newFreeformIntentSplitLeft_tabTearingAnimationBugfixFlagEnabled() { + testOnUnhandledDrag( + DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR, + PointF(50f, 700f), + Rect(0, 0, 500, 1000), + tabTearingAnimationFlagEnabled = true, + ) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX) + fun onUnhandledDrag_newFreeformIntentSplitLeft_tabTearingAnimationBugfixFlagDisabled() { testOnUnhandledDrag( DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR, PointF(50f, 700f), Rect(0, 0, 500, 1000), + tabTearingAnimationFlagEnabled = false, + ) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX) + fun onUnhandledDrag_newFreeformIntentSplitRight_tabTearingAnimationBugfixFlagEnabled() { + testOnUnhandledDrag( + DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR, + PointF(2500f, 700f), + Rect(500, 0, 1000, 1000), + tabTearingAnimationFlagEnabled = true, ) } @Test - fun onUnhandledDrag_newFreeformIntentSplitRight() { + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX) + fun onUnhandledDrag_newFreeformIntentSplitRight_tabTearingAnimationBugfixFlagDisabled() { testOnUnhandledDrag( DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR, PointF(2500f, 700f), Rect(500, 0, 1000, 1000), + tabTearingAnimationFlagEnabled = false, ) } @Test - fun onUnhandledDrag_newFullscreenIntent() { + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX) + fun onUnhandledDrag_newFullscreenIntent_tabTearingAnimationBugfixFlagEnabled() { testOnUnhandledDrag( DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR, PointF(1200f, 50f), Rect(), + tabTearingAnimationFlagEnabled = true, + ) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX) + fun onUnhandledDrag_newFullscreenIntent_tabTearingAnimationBugfixFlagDisabled() { + testOnUnhandledDrag( + DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR, + PointF(1200f, 50f), + Rect(), + tabTearingAnimationFlagEnabled = false, ) } @@ -5812,6 +5864,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() indicatorType: DesktopModeVisualIndicator.IndicatorType, inputCoordinate: PointF, expectedBounds: Rect, + tabTearingAnimationFlagEnabled: Boolean, ) { setUpLandscapeDisplay() val task = setUpFreeformTask() @@ -5842,6 +5895,16 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() anyOrNull(), eq(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT), ) + whenever( + desktopMixedTransitionHandler.startLaunchTransition( + eq(TRANSIT_OPEN), + any(), + anyOrNull(), + anyOrNull(), + anyOrNull(), + ) + ) + .thenReturn(Binder()) spyController.onUnhandledDrag( mockPendingIntent, @@ -5858,8 +5921,19 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() verify(transitions).startTransition(any(), capture(arg), anyOrNull()) } else { expectedWindowingMode = WINDOWING_MODE_FREEFORM - // All other launches use a special handler. - verify(dragAndDropTransitionHandler).handleDropEvent(capture(arg)) + if (tabTearingAnimationFlagEnabled) { + verify(desktopMixedTransitionHandler) + .startLaunchTransition( + eq(TRANSIT_OPEN), + capture(arg), + anyOrNull(), + anyOrNull(), + anyOrNull(), + ) + } else { + // All other launches use a special handler. + verify(dragAndDropTransitionHandler).handleDropEvent(capture(arg)) + } } assertThat( ActivityOptions.fromBundle(arg.value.hierarchyOps[0].launchOptions) diff --git a/libs/hwui/OWNERS b/libs/hwui/OWNERS index bc174599a4d3..70d13ab8b3e5 100644 --- a/libs/hwui/OWNERS +++ b/libs/hwui/OWNERS @@ -4,7 +4,6 @@ alecmouri@google.com djsollen@google.com jreck@google.com njawad@google.com -scroggo@google.com sumir@google.com # For text, e.g. Typeface, Font, Minikin, etc. diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 744388f47d0e..1a6365433be5 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -538,6 +538,7 @@ android_library { kotlincflags: [ "-Xjvm-default=all", "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", + "-P plugin:androidx.compose.compiler.plugins.kotlin:sourceInformation=true", ], plugins: [ diff --git a/packages/SystemUI/compose/core/Android.bp b/packages/SystemUI/compose/core/Android.bp index c63c2b48638c..9c6bb2c8f778 100644 --- a/packages/SystemUI/compose/core/Android.bp +++ b/packages/SystemUI/compose/core/Android.bp @@ -42,6 +42,9 @@ android_library { "//frameworks/libs/systemui:tracinglib-platform", ], - kotlincflags: ["-Xjvm-default=all"], + kotlincflags: [ + "-Xjvm-default=all", + "-P plugin:androidx.compose.compiler.plugins.kotlin:sourceInformation=true", + ], use_resource_processor: true, } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt index 4a4607b6e8fc..2ca70558f18b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -207,12 +207,7 @@ fun CommunalContainer( Box(modifier = Modifier.fillMaxSize()) } - scene( - CommunalScenes.Communal, - userActions = - if (viewModel.v2FlagEnabled()) emptyMap() - else mapOf(Swipe.End to CommunalScenes.Blank), - ) { + scene(CommunalScenes.Communal, userActions = mapOf(Swipe.End to CommunalScenes.Blank)) { CommunalScene( backgroundType = backgroundType, colors = colors, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt index ba25719f1d60..0abed39dce6b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt @@ -26,18 +26,16 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalDensity import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters +import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import com.android.systemui.plugins.clocks.ClockController import kotlin.math.min import kotlin.math.roundToInt /** Produces a [BurnInState] that can be used to query the `LockscreenBurnInViewModel` flows. */ @Composable -fun rememberBurnIn( - clockInteractor: KeyguardClockInteractor, -): BurnInState { - val clock by clockInteractor.currentClock.collectAsStateWithLifecycle() +fun rememberBurnIn(clockViewModel: KeyguardClockViewModel): BurnInState { + val clock by clockViewModel.currentClock.collectAsStateWithLifecycle() val (smartspaceTop, onSmartspaceTopChanged) = remember { mutableStateOf<Float?>(null) } val (smallClockTop, onSmallClockTopChanged) = remember { mutableStateOf<Float?>(null) } @@ -62,18 +60,12 @@ fun rememberBurnIn( } @Composable -private fun rememberBurnInParameters( - clock: ClockController?, - topmostTop: Int, -): BurnInParameters { +private fun rememberBurnInParameters(clock: ClockController?, topmostTop: Int): BurnInParameters { val density = LocalDensity.current val topInset = WindowInsets.systemBars.union(WindowInsets.displayCutout).getTop(density) return remember(clock, topInset, topmostTop) { - BurnInParameters( - topInset = topInset, - minViewY = topmostTop, - ) + BurnInParameters(topInset = topInset, minViewY = topmostTop) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt index abf7fdc05f2e..f51049a10569 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt @@ -38,11 +38,11 @@ import com.android.compose.animation.scene.ContentScope import com.android.compose.modifiers.thenIf import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn import com.android.systemui.keyguard.ui.composable.modifier.burnInAware import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters +import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.notifications.ui.composable.ConstrainedNotificationStack @@ -89,7 +89,7 @@ constructor( private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore, private val aodPromotedNotificationViewModelFactory: AODPromotedNotificationViewModel.Factory, private val systemBarUtilsState: SystemBarUtilsState, - private val clockInteractor: KeyguardClockInteractor, + private val keyguardClockViewModel: KeyguardClockViewModel, ) { init { @@ -118,7 +118,7 @@ constructor( val isVisible by keyguardRootViewModel.isAodPromotedNotifVisible.collectAsStateWithLifecycle() - val burnIn = rememberBurnIn(clockInteractor) + val burnIn = rememberBurnIn(keyguardClockViewModel) AnimatedVisibility( visible = isVisible, @@ -141,7 +141,7 @@ constructor( isVisible.stopAnimating() } } - val burnIn = rememberBurnIn(clockInteractor) + val burnIn = rememberBurnIn(keyguardClockViewModel) AnimatedVisibility( visibleState = transitionState, enter = fadeIn(), diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt index 410499a3c23f..6293fc26f96a 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt @@ -37,7 +37,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState import com.android.compose.modifiers.thenIf -import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.smallClockScene import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.splitShadeLargeClockScene @@ -56,7 +55,7 @@ constructor( private val mediaCarouselSection: MediaCarouselSection, private val clockSection: DefaultClockSection, private val weatherClockSection: WeatherClockSection, - private val clockInteractor: KeyguardClockInteractor, + private val keyguardClockViewModel: KeyguardClockViewModel, ) { @Composable fun ContentScope.DefaultClockLayout( @@ -138,7 +137,7 @@ constructor( smartSpacePaddingTop: (Resources) -> Int, modifier: Modifier = Modifier, ) { - val burnIn = rememberBurnIn(clockInteractor) + val burnIn = rememberBurnIn(keyguardClockViewModel) Column(modifier = modifier) { with(clockSection) { @@ -163,7 +162,7 @@ constructor( smartSpacePaddingTop: (Resources) -> Int, shouldOffSetClockToOneHalf: Boolean = false, ) { - val burnIn = rememberBurnIn(clockInteractor) + val burnIn = rememberBurnIn(keyguardClockViewModel) val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsStateWithLifecycle() LaunchedEffect(isLargeClockVisible) { @@ -204,7 +203,7 @@ constructor( smartSpacePaddingTop: (Resources) -> Int, modifier: Modifier = Modifier, ) { - val burnIn = rememberBurnIn(clockInteractor) + val burnIn = rememberBurnIn(keyguardClockViewModel) val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsStateWithLifecycle() val currentClockState = clockViewModel.currentClock.collectAsStateWithLifecycle() diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt index 7c50d6f8af12..db1358a5a28a 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt @@ -30,9 +30,9 @@ import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn import com.android.systemui.keyguard.ui.composable.section.DefaultClockSection +import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeOverlayActionsViewModel import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeOverlayContentViewModel @@ -57,7 +57,7 @@ constructor( private val shadeSession: SaveableSession, private val stackScrollView: Lazy<NotificationScrollView>, private val clockSection: DefaultClockSection, - private val clockInteractor: KeyguardClockInteractor, + private val keyguardClockViewModel: KeyguardClockViewModel, ) : Overlay { override val key = Overlays.NotificationsShade @@ -105,7 +105,7 @@ constructor( Box { Column { if (viewModel.showClock) { - val burnIn = rememberBurnIn(clockInteractor) + val burnIn = rememberBurnIn(keyguardClockViewModel) with(clockSection) { SmallClock( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt index da4e5824eb3e..619b4280d954 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt @@ -46,6 +46,8 @@ import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.observableTransitionState import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState import com.android.compose.gesture.effect.rememberOffsetOverscrollEffectFactory +import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn +import com.android.systemui.keyguard.ui.composable.modifier.burnInAware import com.android.systemui.lifecycle.rememberActivated import com.android.systemui.qs.ui.adapter.QSSceneAdapter import com.android.systemui.qs.ui.composable.QuickSettingsTheme @@ -239,7 +241,12 @@ fun SceneContainer( BottomRightCornerRibbon( content = { Text(text = "flexi\uD83E\uDD43", color = Color.White) }, colorSaturation = { viewModel.ribbonColorSaturation }, - modifier = Modifier.align(Alignment.BottomEnd), + modifier = + Modifier.align(Alignment.BottomEnd) + .burnInAware( + viewModel = viewModel.burnIn, + params = rememberBurnIn(viewModel.clock).parameters, + ), ) } } diff --git a/packages/SystemUI/compose/scene/Android.bp b/packages/SystemUI/compose/scene/Android.bp index 090e9ccedda0..42dd85a3d0a7 100644 --- a/packages/SystemUI/compose/scene/Android.bp +++ b/packages/SystemUI/compose/scene/Android.bp @@ -45,6 +45,9 @@ android_library { "mechanics", ], - kotlincflags: ["-Xjvm-default=all"], + kotlincflags: [ + "-Xjvm-default=all", + "-P plugin:androidx.compose.compiler.plugins.kotlin:sourceInformation=true", + ], use_resource_processor: true, } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java index bd33e52689c2..f53f964cd3d9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java @@ -64,12 +64,12 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import java.util.List; -import java.util.Optional; - import platform.test.runner.parameterized.ParameterizedAndroidJunit4; import platform.test.runner.parameterized.Parameters; +import java.util.List; +import java.util.Optional; + @SmallTest @RunWith(ParameterizedAndroidJunit4.class) @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX) @@ -171,6 +171,7 @@ public class BouncerFullscreenSwipeTouchHandlerTest extends SysuiTestCase { mActivityStarter, mKeyguardInteractor, mSceneInteractor, + mKosmos.getShadeRepository(), Optional.of(() -> mWindowRootView)); when(mScrimManager.getCurrentController()).thenReturn(mScrimController); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java index 494e0b4deef4..dd43d817cccc 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java @@ -74,12 +74,12 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import java.util.List; -import java.util.Optional; - import platform.test.runner.parameterized.ParameterizedAndroidJunit4; import platform.test.runner.parameterized.Parameters; +import java.util.List; +import java.util.Optional; + @SmallTest @RunWith(ParameterizedAndroidJunit4.class) @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX) @@ -187,6 +187,7 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { mActivityStarter, mKeyguardInteractor, mSceneInteractor, + mKosmos.getShadeRepository(), Optional.of(() -> mWindowRootView) ); @@ -627,6 +628,22 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { onRemovedCallbackCaptor.getValue().onRemoved(); } + @Test + public void testTouchSessionStart_notifiesShadeOfUserInteraction() { + mTouchHandler.onSessionStart(mTouchSession); + + mKosmos.getTestScope().getTestScheduler().runCurrent(); + assertThat(mKosmos.getShadeRepository().getLegacyShadeTracking().getValue()).isTrue(); + + ArgumentCaptor<TouchHandler.TouchSession.Callback> onRemovedCallbackCaptor = + ArgumentCaptor.forClass(TouchHandler.TouchSession.Callback.class); + verify(mTouchSession).registerCallback(onRemovedCallbackCaptor.capture()); + onRemovedCallbackCaptor.getValue().onRemoved(); + + mKosmos.getTestScope().getTestScheduler().runCurrent(); + assertThat(mKosmos.getShadeRepository().getLegacyShadeTracking().getValue()).isFalse(); + } + private void swipeToPosition(float percent, float velocityY) { Mockito.clearInvocations(mTouchSession); mTouchHandler.onSessionStart(mTouchSession); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java index 3d3178793a09..c6801f1ad9d5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java @@ -460,14 +460,14 @@ public class CommandQueueTest extends SysuiTestCase { } @Test - public void testonDisplayAddSystemDecorations() { + public void testOnDisplayAddSystemDecorations() { mCommandQueue.onDisplayAddSystemDecorations(DEFAULT_DISPLAY); waitForIdleSync(); verify(mCallbacks).onDisplayAddSystemDecorations(eq(DEFAULT_DISPLAY)); } @Test - public void testonDisplayAddSystemDecorationsForSecondaryDisplay() { + public void testOnDisplayAddSystemDecorationsForSecondaryDisplay() { mCommandQueue.onDisplayAddSystemDecorations(SECONDARY_DISPLAY); waitForIdleSync(); verify(mCallbacks).onDisplayAddSystemDecorations(eq(SECONDARY_DISPLAY)); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt index d14ff35f824a..e5cb0fbc9e4b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt @@ -49,6 +49,7 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { private val sectionsManager = mock<NotificationSectionsManager>() private val msdlPlayer = kosmos.fakeMSDLPlayer private var canRowBeDismissed = true + private var magneticAnimationsCancelled = false private val underTest = kosmos.magneticNotificationRowManagerImpl @@ -64,6 +65,7 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { children = notificationTestHelper.createGroup(childrenNumber).childrenContainer swipedRow = children.attachedChildren[childrenNumber / 2] configureMagneticRowListener(swipedRow) + magneticAnimationsCancelled = false } @Test @@ -247,6 +249,35 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { assertThat(underTest.currentState).isEqualTo(State.IDLE) } + @Test + fun onMagneticInteractionEnd_whenDetached_cancelsMagneticAnimations() = + kosmos.testScope.runTest { + // GIVEN the swiped row is detached + setDetachedState() + + // WHEN the interaction ends on the row + underTest.onMagneticInteractionEnd(swipedRow, velocity = null) + + // THEN magnetic animations are cancelled + assertThat(magneticAnimationsCancelled).isTrue() + } + + @Test + fun onMagneticInteractionEnd_forMagneticNeighbor_cancelsMagneticAnimations() = + kosmos.testScope.runTest { + val neighborRow = children.attachedChildren[childrenNumber / 2 - 1] + configureMagneticRowListener(neighborRow) + + // GIVEN that targets are set + setTargets() + + // WHEN the interactionEnd is called on a target different from the swiped row + underTest.onMagneticInteractionEnd(neighborRow, null) + + // THEN magnetic animations are cancelled + assertThat(magneticAnimationsCancelled).isTrue() + } + private fun setDetachedState() { val threshold = 100f underTest.setSwipeThresholdPx(threshold) @@ -284,7 +315,11 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { startVelocity: Float, ) {} - override fun cancelMagneticAnimations() {} + override fun cancelMagneticAnimations() { + magneticAnimationsCancelled = true + } + + override fun cancelTranslationAnimations() {} override fun canRowBeDismissed(): Boolean = canRowBeDismissed } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java index 766ae73cb49d..789701f5e4b0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java @@ -405,7 +405,7 @@ public class NotificationSwipeHelperTest extends SysuiTestCase { doNothing().when(mSwipeHelper).superSnapChild(mNotificationRow, 0, 0); mSwipeHelper.snapChild(mNotificationRow, 0, 0); - verify(mCallback, times(1)).onDragCancelledWithVelocity(mNotificationRow, 0); + verify(mCallback, times(1)).onDragCancelled(mNotificationRow); verify(mSwipeHelper, times(1)).superSnapChild(mNotificationRow, 0, 0); verify(mSwipeHelper, times(1)).handleMenuCoveredOrDismissed(); } @@ -416,7 +416,7 @@ public class NotificationSwipeHelperTest extends SysuiTestCase { doNothing().when(mSwipeHelper).superSnapChild(mNotificationRow, 10, 0); mSwipeHelper.snapChild(mNotificationRow, 10, 0); - verify(mCallback, times(1)).onDragCancelledWithVelocity(mNotificationRow, 0); + verify(mCallback, times(1)).onDragCancelled(mNotificationRow); verify(mSwipeHelper, times(1)).superSnapChild(mNotificationRow, 10, 0); verify(mSwipeHelper, times(0)).handleMenuCoveredOrDismissed(); } @@ -426,7 +426,7 @@ public class NotificationSwipeHelperTest extends SysuiTestCase { doNothing().when(mSwipeHelper).superSnapChild(mView, 10, 0); mSwipeHelper.snapChild(mView, 10, 0); - verify(mCallback).onDragCancelledWithVelocity(mView, 0); + verify(mCallback).onDragCancelled(mView); verify(mSwipeHelper, never()).superSnapChild(mView, 10, 0); } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 2e12336f6e93..6f785a3731e1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.phone; import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN; import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE; +import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow; import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher; import static org.junit.Assert.assertFalse; @@ -76,6 +77,7 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInte import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.bouncer.ui.BouncerView; import com.android.systemui.bouncer.ui.BouncerViewDelegate; +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor; import com.android.systemui.dock.DockManager; import com.android.systemui.dreams.DreamOverlayStateController; @@ -171,6 +173,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Mock private SceneInteractor mSceneInteractor; @Mock private DismissCallbackRegistry mDismissCallbackRegistry; @Mock private BouncerInteractor mBouncerInteractor; + @Mock private CommunalSceneInteractor mCommunalSceneInteractor; private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback @@ -209,6 +212,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { .thenReturn(mNotificationShadeWindowView); when(mNotificationShadeWindowView.getWindowInsetsController()) .thenReturn(mWindowInsetsController); + when(mCommunalSceneInteractor.isIdleOnCommunal()).thenReturn(MutableStateFlow(false)); mStatusBarKeyguardViewManager = new StatusBarKeyguardViewManager( @@ -245,7 +249,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mExecutor, () -> mDeviceEntryInteractor, mDismissCallbackRegistry, - () -> mBouncerInteractor) { + () -> mBouncerInteractor, + mCommunalSceneInteractor) { @Override public ViewRootImpl getViewRootImpl() { return mViewRootImpl; @@ -749,7 +754,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mExecutor, () -> mDeviceEntryInteractor, mDismissCallbackRegistry, - () -> mBouncerInteractor) { + () -> mBouncerInteractor, + mCommunalSceneInteractor) { @Override public ViewRootImpl getViewRootImpl() { return mViewRootImpl; diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt index 73c191b32393..f0823e2f645e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone.ongoingcall.domain.interactor import android.app.PendingIntent +import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -30,12 +31,12 @@ import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository import com.android.systemui.statusbar.gesture.swipeStatusBarAwayGestureHandler -import com.android.systemui.statusbar.notification.data.model.activeNotificationModel -import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel -import com.android.systemui.statusbar.notification.shared.CallType +import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel +import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.setNoCallState +import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.setOngoingCallState import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest @@ -50,6 +51,7 @@ import org.mockito.kotlin.verify @SmallTest @RunWith(AndroidJUnit4::class) +@EnableFlags(StatusBarChipsModernization.FLAG_NAME) class OngoingCallInteractorTest : SysuiTestCase() { private val kosmos = Kosmos().useUnconfinedTestDispatcher() private val repository = kosmos.activeNotificationListRepository @@ -76,21 +78,14 @@ class OngoingCallInteractorTest : SysuiTestCase() { val testIntent: PendingIntent = mock() val testPromotedContent = PromotedNotificationContentModel.Builder("promotedCall").build() - repository.activeNotifications.value = - ActiveNotificationsStore.Builder() - .apply { - addIndividualNotif( - activeNotificationModel( - key = "promotedCall", - whenTime = 1000L, - callType = CallType.Ongoing, - statusBarChipIcon = testIconView, - contentIntent = testIntent, - promotedContent = testPromotedContent, - ) - ) - } - .build() + setOngoingCallState( + kosmos = this, + key = "promotedCall", + startTimeMs = 1000L, + statusBarChipIconView = testIconView, + contentIntent = testIntent, + promotedContent = testPromotedContent, + ) // Verify model is InCall and has the correct icon, intent, and promoted content. assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java) @@ -101,45 +96,13 @@ class OngoingCallInteractorTest : SysuiTestCase() { } @Test - fun ongoingCallNotification_emitsInCall() = - kosmos.runTest { - val latest by collectLastValue(underTest.ongoingCallState) - - repository.activeNotifications.value = - ActiveNotificationsStore.Builder() - .apply { - addIndividualNotif( - activeNotificationModel( - key = "notif1", - whenTime = 1000L, - callType = CallType.Ongoing, - ) - ) - } - .build() - - assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java) - } - - @Test fun notificationRemoved_emitsNoCall() = kosmos.runTest { val latest by collectLastValue(underTest.ongoingCallState) - repository.activeNotifications.value = - ActiveNotificationsStore.Builder() - .apply { - addIndividualNotif( - activeNotificationModel( - key = "notif1", - whenTime = 1000L, - callType = CallType.Ongoing, - ) - ) - } - .build() - - repository.activeNotifications.value = ActiveNotificationsStore() + setOngoingCallState(kosmos = this) + setNoCallState(kosmos = this) + assertThat(latest).isInstanceOf(OngoingCallModel.NoCall::class.java) } @@ -149,19 +112,7 @@ class OngoingCallInteractorTest : SysuiTestCase() { kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = true val latest by collectLastValue(underTest.ongoingCallState) - repository.activeNotifications.value = - ActiveNotificationsStore.Builder() - .apply { - addIndividualNotif( - activeNotificationModel( - key = "notif1", - whenTime = 1000L, - callType = CallType.Ongoing, - uid = UID, - ) - ) - } - .build() + setOngoingCallState(kosmos = this, uid = UID) assertThat(latest).isInstanceOf(OngoingCallModel.InCallWithVisibleApp::class.java) } @@ -172,19 +123,7 @@ class OngoingCallInteractorTest : SysuiTestCase() { kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = false val latest by collectLastValue(underTest.ongoingCallState) - repository.activeNotifications.value = - ActiveNotificationsStore.Builder() - .apply { - addIndividualNotif( - activeNotificationModel( - key = "notif1", - whenTime = 1000L, - callType = CallType.Ongoing, - uid = UID, - ) - ) - } - .build() + setOngoingCallState(kosmos = this, uid = UID) assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java) } @@ -196,19 +135,7 @@ class OngoingCallInteractorTest : SysuiTestCase() { // Start with notification and app not visible kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = false - repository.activeNotifications.value = - ActiveNotificationsStore.Builder() - .apply { - addIndividualNotif( - activeNotificationModel( - key = "notif1", - whenTime = 1000L, - callType = CallType.Ongoing, - uid = UID, - ) - ) - } - .build() + setOngoingCallState(kosmos = this, uid = UID) assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java) // App becomes visible @@ -234,7 +161,7 @@ class OngoingCallInteractorTest : SysuiTestCase() { kosmos.fakeStatusBarWindowControllerStore.defaultDisplay .ongoingProcessRequiresStatusBarVisible ) - postOngoingCallNotification() + setOngoingCallState(kosmos = this) assertThat(isStatusBarRequired).isTrue() assertThat(requiresStatusBarVisibleInRepository).isTrue() @@ -256,9 +183,9 @@ class OngoingCallInteractorTest : SysuiTestCase() { .ongoingProcessRequiresStatusBarVisible ) - postOngoingCallNotification() + setOngoingCallState(kosmos = this) - repository.activeNotifications.value = ActiveNotificationsStore() + setNoCallState(kosmos = this) assertThat(isStatusBarRequired).isFalse() assertThat(requiresStatusBarVisibleInRepository).isFalse() @@ -283,7 +210,7 @@ class OngoingCallInteractorTest : SysuiTestCase() { kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = false - postOngoingCallNotification() + setOngoingCallState(kosmos = this, uid = UID) assertThat(ongoingCallState).isInstanceOf(OngoingCallModel.InCall::class.java) assertThat(requiresStatusBarVisibleInRepository).isTrue() @@ -305,7 +232,7 @@ class OngoingCallInteractorTest : SysuiTestCase() { clearInvocations(kosmos.swipeStatusBarAwayGestureHandler) // Set up notification but not in fullscreen kosmos.fakeStatusBarModeRepository.defaultDisplay.isInFullscreenMode.value = false - postOngoingCallNotification() + setOngoingCallState(kosmos = this) assertThat(ongoingCallState).isInstanceOf(OngoingCallModel.InCall::class.java) verify(kosmos.swipeStatusBarAwayGestureHandler, never()) @@ -319,7 +246,7 @@ class OngoingCallInteractorTest : SysuiTestCase() { // Set up notification and fullscreen mode kosmos.fakeStatusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true - postOngoingCallNotification() + setOngoingCallState(kosmos = this) assertThat(isGestureListeningEnabled).isTrue() verify(kosmos.swipeStatusBarAwayGestureHandler) @@ -333,7 +260,7 @@ class OngoingCallInteractorTest : SysuiTestCase() { // Set up notification and fullscreen mode kosmos.fakeStatusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true - postOngoingCallNotification() + setOngoingCallState(kosmos = this) clearInvocations(kosmos.swipeStatusBarAwayGestureHandler) @@ -360,7 +287,7 @@ class OngoingCallInteractorTest : SysuiTestCase() { ) // Start with an ongoing call (which should set status bar required) - postOngoingCallNotification() + setOngoingCallState(kosmos = this) assertThat(isStatusBarRequiredForOngoingCall).isTrue() assertThat(requiresStatusBarVisibleInRepository).isTrue() @@ -374,22 +301,6 @@ class OngoingCallInteractorTest : SysuiTestCase() { assertThat(requiresStatusBarVisibleInWindowController).isFalse() } - private fun postOngoingCallNotification() { - repository.activeNotifications.value = - ActiveNotificationsStore.Builder() - .apply { - addIndividualNotif( - activeNotificationModel( - key = "notif1", - whenTime = 1000L, - callType = CallType.Ongoing, - uid = UID, - ) - ) - } - .build() - } - companion object { private const val UID = 885 } diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index f835ad689132..e2065f175c79 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -352,6 +352,7 @@ public class SwipeHelper implements Gefingerpoken, Dumpable { && Math.abs(delta) > Math.abs(deltaPerpendicular)) { if (mCallback.canChildBeDragged(mTouchedView)) { mIsSwiping = true; + mCallback.setMagneticAndRoundableTargets(mTouchedView); mCallback.onBeginDrag(mTouchedView); mInitialTouchPos = getPos(ev); mTranslation = getTranslation(mTouchedView); @@ -444,6 +445,7 @@ public class SwipeHelper implements Gefingerpoken, Dumpable { }; Animator anim = getViewTranslationAnimator(animView, newPos, updateListener); + mCallback.onMagneticInteractionEnd(animView, velocity); if (anim == null) { onDismissChildWithAnimationFinished(); return; @@ -733,7 +735,8 @@ public class SwipeHelper implements Gefingerpoken, Dumpable { dismissChild(mTouchedView, velocity, !swipedFastEnough() /* useAccelerateInterpolator */); } else { - mCallback.onDragCancelledWithVelocity(mTouchedView, velocity); + mCallback.onMagneticInteractionEnd(mTouchedView, velocity); + mCallback.onDragCancelled(mTouchedView); snapChild(mTouchedView, 0 /* leftTarget */, velocity); } mTouchedView = null; @@ -935,18 +938,24 @@ public class SwipeHelper implements Gefingerpoken, Dumpable { void onBeginDrag(View v); + /** + * Set magnetic and roundable targets for a view. + */ + void setMagneticAndRoundableTargets(View v); + void onChildDismissed(View v); void onDragCancelled(View v); /** - * A drag operation has been cancelled on a view with a final velocity. - * @param v View that was dragged. - * @param finalVelocity Final velocity of the drag. + * Notify that a magnetic interaction ended on a view with a velocity. + * <p> + * This method should be called when a view will snap back or be dismissed. + * + * @param view The {@link View} whose magnetic interaction ended. + * @param velocity The velocity when the interaction ended. */ - default void onDragCancelledWithVelocity(View v, float finalVelocity) { - onDragCancelled(v); - } + void onMagneticInteractionEnd(View view, float velocity); /** * Called when the child is long pressed and available to start drag and drop. diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt index e365b770c203..d8e7a168ef3c 100644 --- a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt @@ -43,6 +43,7 @@ import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.ui.view.WindowRootView import com.android.systemui.shade.ShadeExpansionChangeEvent +import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.wm.shell.animation.FlingAnimationUtils @@ -79,6 +80,7 @@ constructor( private val activityStarter: ActivityStarter, private val keyguardInteractor: KeyguardInteractor, private val sceneInteractor: SceneInteractor, + private val shadeRepository: ShadeRepository, private val windowRootViewProvider: Optional<Provider<WindowRootView>>, ) : TouchHandler { /** An interface for creating ValueAnimators. */ @@ -260,6 +262,8 @@ constructor( } scrimManager.addCallback(scrimManagerCallback) currentScrimController = scrimManager.currentController + + shadeRepository.setLegacyShadeTracking(true) session.registerCallback { velocityTracker?.apply { recycle() } velocityTracker = null @@ -270,6 +274,7 @@ constructor( if (!Flags.communalBouncerDoNotModifyPluginOpen()) { notificationShadeWindowController.setForcePluginOpen(false, this) } + shadeRepository.setLegacyShadeTracking(false) } session.registerGestureListener(onGestureListener) session.registerInputListener { ev: InputEvent -> onMotionEvent(ev) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index 23be5c52ab5c..c61530c3dbcc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -163,10 +163,6 @@ constructor( } private fun bindJankViewModel() { - if (SceneContainerFlag.isEnabled) { - return - } - jankHandle?.dispose() jankHandle = KeyguardJankBinder.bind( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index efa9c21f96b4..caf0fd4450fc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -16,7 +16,6 @@ package com.android.systemui.keyguard; -import static android.app.KeyguardManager.LOCK_ON_USER_SWITCH_CALLBACK; import static android.app.StatusBarManager.SESSION_KEYGUARD; import static android.provider.Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT; import static android.provider.Settings.System.LOCKSCREEN_SOUNDS_ENABLED; @@ -76,7 +75,6 @@ import android.os.Bundle; import android.os.DeadObjectException; import android.os.Handler; import android.os.IBinder; -import android.os.IRemoteCallback; import android.os.Looper; import android.os.Message; import android.os.PowerManager; @@ -194,8 +192,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; -import java.util.Iterator; -import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -286,9 +282,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private static final int SYSTEM_READY = 18; private static final int CANCEL_KEYGUARD_EXIT_ANIM = 19; private static final int BOOT_INTERACTOR = 20; - private static final int BEFORE_USER_SWITCHING = 21; - private static final int USER_SWITCHING = 22; - private static final int USER_SWITCH_COMPLETE = 23; /** Enum for reasons behind updating wakeAndUnlock state. */ @Retention(RetentionPolicy.SOURCE) @@ -306,8 +299,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, int WAKE_AND_UNLOCK = 3; } - private final List<LockNowCallback> mLockNowCallbacks = new ArrayList<>(); - /** * The default amount of time we stay awake (used for all key input) */ @@ -366,18 +357,13 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private final Lazy<NotificationShadeDepthController> mNotificationShadeDepthController; private final Lazy<ShadeController> mShadeController; private final Lazy<CommunalSceneInteractor> mCommunalSceneInteractor; - /* - * Records the user id on request to go away, for validation when WM calls back to start the - * exit animation. - */ - private int mGoingAwayRequestedForUserId = -1; - private boolean mSystemReady; private boolean mBootCompleted; private boolean mBootSendUserPresent; private boolean mShuttingDown; private boolean mDozing; private boolean mAnimatingScreenOff; + private boolean mIgnoreDismiss; private final Context mContext; private final FalsingCollector mFalsingCollector; @@ -640,78 +626,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } }; - @VisibleForTesting - protected UserTracker.Callback mUserChangedCallback = new UserTracker.Callback() { - - @Override - public void onBeforeUserSwitching(int newUser, @NonNull Runnable resultCallback) { - mHandler.sendMessage(mHandler.obtainMessage(BEFORE_USER_SWITCHING, - newUser, 0, resultCallback)); - } - - @Override - public void onUserChanging(int newUser, @NonNull Context userContext, - @NonNull Runnable resultCallback) { - mHandler.sendMessage(mHandler.obtainMessage(USER_SWITCHING, - newUser, 0, resultCallback)); - } - - @Override - public void onUserChanged(int newUser, Context userContext) { - mHandler.sendMessage(mHandler.obtainMessage(USER_SWITCH_COMPLETE, - newUser, 0)); - } - }; - - /** - * Handle {@link #BEFORE_USER_SWITCHING} - */ - @VisibleForTesting - void handleBeforeUserSwitching(int userId, Runnable resultCallback) { - Log.d(TAG, String.format("onBeforeUserSwitching %d", userId)); - synchronized (KeyguardViewMediator.this) { - mHandler.removeMessages(DISMISS); - notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(userId)); - resetKeyguardDonePendingLocked(); - adjustStatusBarLocked(); - mKeyguardStateController.notifyKeyguardGoingAway(false); - if (mLockPatternUtils.isSecure(userId) && !mShowing) { - doKeyguardLocked(null); - } else { - resetStateLocked(); - } - resultCallback.run(); - } - } - - /** - * Handle {@link #USER_SWITCHING} - */ - @VisibleForTesting - void handleUserSwitching(int userId, Runnable resultCallback) { - Log.d(TAG, String.format("onUserSwitching %d", userId)); - synchronized (KeyguardViewMediator.this) { - if (!mLockPatternUtils.isSecure(userId)) { - dismiss(null, null); - } - resultCallback.run(); - } - } - - /** - * Handle {@link #USER_SWITCH_COMPLETE} - */ - @VisibleForTesting - void handleUserSwitchComplete(int userId) { - Log.d(TAG, String.format("onUserSwitchComplete %d", userId)); - // Calling dismiss on a secure user will show the bouncer - if (mLockPatternUtils.isSecure(userId)) { - // We are calling dismiss with a delay as there are race conditions in some scenarios - // caused by async layout listeners - mHandler.postDelayed(() -> dismiss(null /* callback */, null /* message */), 500); - } - } - KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() { @Override @@ -728,6 +642,27 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } @Override + public void onUserSwitching(int userId) { + Log.d(TAG, String.format("onUserSwitching %d", userId)); + synchronized (KeyguardViewMediator.this) { + mIgnoreDismiss = true; + notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(userId)); + resetKeyguardDonePendingLocked(); + resetStateLocked(); + adjustStatusBarLocked(); + } + } + + @Override + public void onUserSwitchComplete(int userId) { + mIgnoreDismiss = false; + Log.d(TAG, String.format("onUserSwitchComplete %d", userId)); + // We are calling dismiss with a delay as there are race conditions in some scenarios + // caused by async layout listeners + mHandler.postDelayed(() -> dismiss(null /* callback */, null /* message */), 500); + } + + @Override public void onDeviceProvisioned() { sendUserPresentBroadcast(); } @@ -1736,13 +1671,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, com.android.internal.R.anim.lock_screen_behind_enter); mWorkLockController = new WorkLockActivityController(mContext, mUserTracker); - mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor()); - // start() can be invoked in the middle of user switching, so check for this state and issue - // the call manually as that important event was missed. - if (mUserTracker.isUserSwitching()) { - handleBeforeUserSwitching(mUserTracker.getUserId(), () -> {}); - handleUserSwitching(mUserTracker.getUserId(), () -> {}); - } + mJavaAdapter.alwaysCollectFlow( mWallpaperRepository.getWallpaperSupportsAmbientMode(), this::setWallpaperSupportsAmbientMode); @@ -1791,7 +1720,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, // System ready can be invoked in the middle of user switching, so check for this state // and issue the call manually as that important event was missed. if (mUserTracker.isUserSwitching()) { - mUserChangedCallback.onUserChanging(mUserTracker.getUserId(), mContext, () -> {}); + mUpdateCallback.onUserSwitching(mUserTracker.getUserId()); } } // Most services aren't available until the system reaches the ready state, so we @@ -2432,23 +2361,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mCommunalSceneInteractor.get().showHubFromPowerButton(); } - int currentUserId = mSelectedUserInteractor.getSelectedUserId(); - if (options != null && options.getBinder(LOCK_ON_USER_SWITCH_CALLBACK) != null) { - LockNowCallback callback = new LockNowCallback(currentUserId, - IRemoteCallback.Stub.asInterface( - options.getBinder(LOCK_ON_USER_SWITCH_CALLBACK))); - synchronized (mLockNowCallbacks) { - mLockNowCallbacks.add(callback); - } - Log.d(TAG, "LockNowCallback required for user: " + callback.mUserId); - } - // if another app is disabling us, don't show if (!mExternallyEnabled && !mLockPatternUtils.isUserInLockdown( mSelectedUserInteractor.getSelectedUserId())) { if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled"); - notifyLockNowCallback(); + mNeedToReshowWhenReenabled = true; return; } @@ -2466,7 +2384,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, // We're removing "reset" in the refactor - "resetting" the views will happen // as a reaction to the root cause of the "reset" signal. if (KeyguardWmStateRefactor.isEnabled()) { - notifyLockNowCallback(); return; } @@ -2479,7 +2396,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, + "previously hiding. It should be safe to short-circuit " + "here."); resetStateLocked(/* hideBouncer= */ false); - notifyLockNowCallback(); return; } } else { @@ -2506,7 +2422,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, Log.d(TAG, "doKeyguard: not showing because device isn't provisioned and the sim is" + " not locked or missing"); } - notifyLockNowCallback(); return; } @@ -2514,7 +2429,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, if (mLockPatternUtils.isLockScreenDisabled(mSelectedUserInteractor.getSelectedUserId()) && !lockedOrMissing && !forceShow) { if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off"); - notifyLockNowCallback(); return; } @@ -2562,6 +2476,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } public void dismiss(IKeyguardDismissCallback callback, CharSequence message) { + if (mIgnoreDismiss) { + android.util.Log.i(TAG, "Ignoring request to dismiss (user switch in progress?)"); + return; + } + if (mKeyguardStateController.isKeyguardGoingAway()) { Log.i(TAG, "Ignoring dismiss because we're already going away."); return; @@ -2579,7 +2498,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } private void resetStateLocked(boolean hideBouncer) { - if (DEBUG) Log.d(TAG, "resetStateLocked"); + if (DEBUG) Log.e(TAG, "resetStateLocked"); Message msg = mHandler.obtainMessage(RESET, hideBouncer ? 1 : 0, 0); mHandler.sendMessage(msg); } @@ -2827,18 +2746,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, message = "BOOT_INTERACTOR"; handleBootInteractor(); break; - case BEFORE_USER_SWITCHING: - message = "BEFORE_USER_SWITCHING"; - handleBeforeUserSwitching(msg.arg1, (Runnable) msg.obj); - break; - case USER_SWITCHING: - message = "USER_SWITCHING"; - handleUserSwitching(msg.arg1, (Runnable) msg.obj); - break; - case USER_SWITCH_COMPLETE: - message = "USER_SWITCH_COMPLETE"; - handleUserSwitchComplete(msg.arg1); - break; } Log.d(TAG, "KeyguardViewMediator queue processing message: " + message); } @@ -2980,9 +2887,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mUiBgExecutor.execute(() -> { Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ", " + reason + ")"); - if (showing) { - notifyLockNowCallback(); - } if (KeyguardWmStateRefactor.isEnabled()) { // Handled in WmLockscreenVisibilityManager if flag is enabled. @@ -3027,7 +2931,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, synchronized (KeyguardViewMediator.this) { if (!mSystemReady) { if (DEBUG) Log.d(TAG, "ignoring handleShow because system is not ready."); - notifyLockNowCallback(); return; } if (DEBUG) Log.d(TAG, "handleShow"); @@ -3086,11 +2989,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } } - final Runnable mKeyguardGoingAwayRunnable = new Runnable() { + private final Runnable mKeyguardGoingAwayRunnable = new Runnable() { @SuppressLint("MissingPermission") @Override public void run() { Trace.beginSection("KeyguardViewMediator.mKeyGuardGoingAwayRunnable"); + Log.d(TAG, "keyguardGoingAwayRunnable"); mKeyguardViewControllerLazy.get().keyguardGoingAway(); int flags = 0; @@ -3127,10 +3031,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, // Handled in WmLockscreenVisibilityManager if flag is enabled. if (!KeyguardWmStateRefactor.isEnabled()) { - mGoingAwayRequestedForUserId = mSelectedUserInteractor.getSelectedUserId(); - Log.d(TAG, "keyguardGoingAway requested for userId: " - + mGoingAwayRequestedForUserId); - // Don't actually hide the Keyguard at the moment, wait for window manager // until it tells us it's safe to do so with startKeyguardExitAnimation. // Posting to mUiOffloadThread to ensure that calls to ActivityTaskManager @@ -3269,30 +3169,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) { Log.d(TAG, "handleStartKeyguardExitAnimation startTime=" + startTime + " fadeoutDuration=" + fadeoutDuration); - int currentUserId = mSelectedUserInteractor.getSelectedUserId(); - if (!KeyguardWmStateRefactor.isEnabled() && mGoingAwayRequestedForUserId != currentUserId) { - Log.e(TAG, "Not executing handleStartKeyguardExitAnimationInner() due to userId " - + "mismatch. Requested: " + mGoingAwayRequestedForUserId + ", current: " - + currentUserId); - if (finishedCallback != null) { - // There will not execute animation, send a finish callback to ensure the remote - // animation won't hang there. - try { - finishedCallback.onAnimationFinished(); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to call onAnimationFinished", e); - } - } - mHiding = false; - if (mLockPatternUtils.isSecure(currentUserId)) { - doKeyguardLocked(null); - } else { - resetStateLocked(); - dismiss(null, null); - } - return; - } - synchronized (KeyguardViewMediator.this) { mIsKeyguardExitAnimationCanceled = false; // Tell ActivityManager that we canceled the keyguard animation if @@ -3537,13 +3413,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, * app transition before finishing the current RemoteAnimation, or the keyguard being re-shown). */ private void handleCancelKeyguardExitAnimation() { - if (!KeyguardWmStateRefactor.isEnabled() - && mGoingAwayRequestedForUserId != mSelectedUserInteractor.getSelectedUserId()) { - Log.e(TAG, "Setting pendingLock = true due to userId mismatch. Requested: " - + mGoingAwayRequestedForUserId + ", current: " - + mSelectedUserInteractor.getSelectedUserId()); - setPendingLock(true); - } if (mPendingLock) { Log.d(TAG, "#handleCancelKeyguardExitAnimation: keyguard exit animation cancelled. " + "There's a pending lock, so we were cancelled because the device was locked " @@ -3644,7 +3513,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mSurfaceBehindRemoteAnimationRequested = true; if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS && !KeyguardWmStateRefactor.isEnabled()) { - mGoingAwayRequestedForUserId = mSelectedUserInteractor.getSelectedUserId(); startKeyguardTransition(false /* keyguardShowing */, false /* aodShowing */); return; } @@ -3665,9 +3533,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, if (!KeyguardWmStateRefactor.isEnabled()) { // Handled in WmLockscreenVisibilityManager. - mGoingAwayRequestedForUserId = mSelectedUserInteractor.getSelectedUserId(); - Log.d(TAG, "keyguardGoingAway requested for userId: " - + mGoingAwayRequestedForUserId); mActivityTaskManagerService.keyguardGoingAway(flags); } } catch (RemoteException e) { @@ -4123,29 +3988,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mUiBgExecutor.execute(mTrustManager::reportKeyguardShowingChanged); } - private void notifyLockNowCallback() { - List<LockNowCallback> callbacks; - synchronized (mLockNowCallbacks) { - callbacks = new ArrayList<LockNowCallback>(mLockNowCallbacks); - mLockNowCallbacks.clear(); - } - Iterator<LockNowCallback> iter = callbacks.listIterator(); - while (iter.hasNext()) { - LockNowCallback callback = iter.next(); - iter.remove(); - if (callback.mUserId != mSelectedUserInteractor.getSelectedUserId()) { - Log.i(TAG, "Not notifying lockNowCallback due to user mismatch"); - continue; - } - Log.i(TAG, "Notifying lockNowCallback"); - try { - callback.mRemoteCallback.sendResult(null); - } catch (RemoteException e) { - Log.e(TAG, "Could not issue LockNowCallback sendResult", e); - } - } - } - private void notifyTrustedChangedLocked(boolean trusted) { int size = mKeyguardStateCallbacks.size(); for (int i = size - 1; i >= 0; i--) { @@ -4310,14 +4152,4 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } }; } - - private class LockNowCallback { - final int mUserId; - final IRemoteCallback mRemoteCallback; - - LockNowCallback(int userId, IRemoteCallback remoteCallback) { - mUserId = userId; - mRemoteCallback = remoteCallback; - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt index 5c03d65e570f..8f6815829ba2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt @@ -69,7 +69,7 @@ constructor( * Note that [onCancel] isn't used when the scene framework is enabled. */ fun sharedFlow( - duration: Duration, + duration: Duration = transitionDuration, onStep: (Float) -> Float, startTime: Duration = 0.milliseconds, onStart: (() -> Unit)? = null, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardJankBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardJankBinder.kt index 0cb684a1aabe..38263be33c82 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardJankBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardJankBinder.kt @@ -30,6 +30,7 @@ import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.ui.viewmodel.KeyguardJankViewModel import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.scene.shared.flag.SceneContainerFlag import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -79,15 +80,18 @@ object KeyguardJankBinder { } } - launch { - viewModel.lockscreenToAodTransition.collect { - processStep(it, CUJ_LOCKSCREEN_TRANSITION_TO_AOD) + // The following is already done in KeyguardTransitionAnimationCallbackImpl. + if (!SceneContainerFlag.isEnabled) { + launch { + viewModel.lockscreenToAodTransition.collect { + processStep(it, CUJ_LOCKSCREEN_TRANSITION_TO_AOD) + } } - } - launch { - viewModel.aodToLockscreenTransition.collect { - processStep(it, CUJ_LOCKSCREEN_TRANSITION_FROM_AOD) + launch { + viewModel.aodToLockscreenTransition.collect { + processStep(it, CUJ_LOCKSCREEN_TRANSITION_FROM_AOD) + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/GlanceableHubBlurProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/GlanceableHubBlurProvider.kt index 19cd501fa787..50f8e086ac6e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/GlanceableHubBlurProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/GlanceableHubBlurProvider.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.ui.transitions +import android.util.MathUtils import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import javax.inject.Inject import kotlinx.coroutines.flow.Flow @@ -33,8 +34,18 @@ constructor( blurConfig: BlurConfig, ) { val exitBlurRadius: Flow<Float> = - transitionAnimation.immediatelyTransitionTo(blurConfig.minBlurRadiusPx) + transitionAnimation.sharedFlow( + onStep = { MathUtils.lerp(blurConfig.maxBlurRadiusPx, blurConfig.minBlurRadiusPx, it) }, + onStart = { blurConfig.maxBlurRadiusPx }, + onFinish = { blurConfig.minBlurRadiusPx }, + onCancel = { blurConfig.maxBlurRadiusPx }, + ) val enterBlurRadius: Flow<Float> = - transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx) + transitionAnimation.sharedFlow( + onStep = { MathUtils.lerp(blurConfig.minBlurRadiusPx, blurConfig.maxBlurRadiusPx, it) }, + onStart = { blurConfig.minBlurRadiusPx }, + onFinish = { blurConfig.maxBlurRadiusPx }, + onCancel = { blurConfig.minBlurRadiusPx }, + ) } diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt index f0f476e65e2f..364da5f8e80d 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt @@ -30,19 +30,14 @@ import com.android.systemui.compose.ComposeInitializer import com.android.systemui.res.R /** A view that can serve as the root of the main SysUI window. */ -open class WindowRootView( - context: Context, - attrs: AttributeSet?, -) : - FrameLayout( - context, - attrs, - ) { +open class WindowRootView(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) { private lateinit var layoutInsetsController: LayoutInsetsController private var leftInset = 0 private var rightInset = 0 + private var previousInsets: WindowInsets? = null + override fun onAttachedToWindow() { super.onAttachedToWindow() @@ -66,11 +61,14 @@ open class WindowRootView( override fun generateDefaultLayoutParams(): FrameLayout.LayoutParams? { return LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.MATCH_PARENT + FrameLayout.LayoutParams.MATCH_PARENT, ) } override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets? { + if (windowInsets == previousInsets) { + return windowInsets + } val insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()) if (fitsSystemWindows) { val paddingChanged = insets.top != paddingTop || insets.bottom != paddingBottom @@ -95,7 +93,7 @@ open class WindowRootView( leftInset = pairInsets.first rightInset = pairInsets.second applyMargins() - return windowInsets + return windowInsets.also { previousInsets = WindowInsets(it) } } fun setLayoutInsetsController(layoutInsetsController: LayoutInsetsController) { @@ -143,37 +141,22 @@ open class WindowRootView( interface LayoutInsetsController { /** Update the insets and calculate them accordingly. */ - fun getinsets( - windowInsets: WindowInsets?, - displayCutout: DisplayCutout?, - ): Pair<Int, Int> + fun getinsets(windowInsets: WindowInsets?, displayCutout: DisplayCutout?): Pair<Int, Int> } private class LayoutParams : FrameLayout.LayoutParams { var ignoreRightInset = false - constructor( - width: Int, - height: Int, - ) : super( - width, - height, - ) + constructor(width: Int, height: Int) : super(width, height) @SuppressLint("CustomViewStyleable") - constructor( - context: Context, - attrs: AttributeSet?, - ) : super( - context, - attrs, - ) { + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { val obtainedAttributes = context.obtainStyledAttributes(attrs, R.styleable.StatusBarWindowView_Layout) ignoreRightInset = obtainedAttributes.getBoolean( R.styleable.StatusBarWindowView_Layout_ignoreRightInset, - false + false, ) obtainedAttributes.recycle() } diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt index fbcd8ea9b9e4..233e15846450 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt @@ -31,6 +31,8 @@ import com.android.compose.animation.scene.UserActionResult import com.android.systemui.classifier.Classifier import com.android.systemui.classifier.domain.interactor.FalsingInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.lifecycle.Hydrator @@ -68,6 +70,8 @@ constructor( val lightRevealScrim: LightRevealScrimViewModel, val wallpaperViewModel: WallpaperViewModel, keyguardInteractor: KeyguardInteractor, + val burnIn: AodBurnInViewModel, + val clock: KeyguardClockViewModel, @Assisted view: View, @Assisted private val motionEventHandlerReceiver: (MotionEventHandler?) -> Unit, ) : ExclusiveActivatable() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index 76ba7f9ea901..2bc48746f847 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -106,7 +106,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro @Override public void triggerMagneticForce(float endTranslation, @NonNull SpringForce springForce, float startVelocity) { - cancelMagneticAnimations(); + cancelTranslationAnimations(); mMagneticAnimator.setSpring(springForce); mMagneticAnimator.setStartVelocity(startVelocity); mMagneticAnimator.animateToFinalPosition(endTranslation); @@ -114,11 +114,15 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro @Override public void cancelMagneticAnimations() { - cancelTranslationAnimations(); mMagneticAnimator.cancel(); } @Override + public void cancelTranslationAnimations() { + ExpandableView.this.cancelTranslationAnimations(); + } + + @Override public boolean canRowBeDismissed() { return canExpandableViewBeDismissed(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt index 3941700496f4..5a29a699a7e6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt @@ -97,6 +97,7 @@ constructor( stackScrollLayout, MAGNETIC_TRANSLATION_MULTIPLIERS.size, ) + currentMagneticListeners.swipedListener()?.cancelTranslationAnimations() newListeners.forEach { if (currentMagneticListeners.contains(it)) { it?.cancelMagneticAnimations() @@ -214,22 +215,32 @@ constructor( } override fun onMagneticInteractionEnd(row: ExpandableNotificationRow, velocity: Float?) { - if (!row.isSwipedTarget()) return - - when (currentState) { - State.PULLING -> { - snapNeighborsBack(velocity) - currentState = State.IDLE - } - State.DETACHED -> { - currentState = State.IDLE + if (row.isSwipedTarget()) { + when (currentState) { + State.PULLING -> { + snapNeighborsBack(velocity) + currentState = State.IDLE + } + State.DETACHED -> { + // Cancel any detaching animation that may be occurring + currentMagneticListeners.swipedListener()?.cancelMagneticAnimations() + currentState = State.IDLE + } + else -> {} } - else -> {} + } else { + // A magnetic neighbor may be dismissing. In this case, we need to cancel any snap back + // magnetic animation to let the external dismiss animation proceed. + val listener = currentMagneticListeners.find { it == row.magneticRowListener } + listener?.cancelMagneticAnimations() } } override fun reset() { - currentMagneticListeners.forEach { it?.cancelMagneticAnimations() } + currentMagneticListeners.forEach { + it?.cancelMagneticAnimations() + it?.cancelTranslationAnimations() + } currentState = State.IDLE currentMagneticListeners = listOf() currentRoundableTargets = null diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt index 46036d4c1fad..5959ef1e093b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt @@ -42,6 +42,9 @@ interface MagneticRowListener { /** Cancel any animations related to the magnetic interactions of the row */ fun cancelMagneticAnimations() + /** Cancel any other animations related to the row's translation */ + fun cancelTranslationAnimations() + /** Can the row be dismissed. */ fun canRowBeDismissed(): Boolean } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 48a6a4c057df..810d0b43b0dd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -463,6 +463,13 @@ public class NotificationStackScrollLayoutController implements Dumpable { } @Override + public void onMagneticInteractionEnd(View view, float velocity) { + if (view instanceof ExpandableNotificationRow row) { + mMagneticNotificationRowManager.onMagneticInteractionEnd(row, velocity); + } + } + + @Override public float getTotalTranslationLength(View animView) { return mView.getTotalTranslationLength(animView); } @@ -504,14 +511,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { public void onDragCancelled(View v) { } - @Override - public void onDragCancelledWithVelocity(View v, float finalVelocity) { - if (v instanceof ExpandableNotificationRow row) { - mMagneticNotificationRowManager.onMagneticInteractionEnd( - row, finalVelocity); - } - } - /** * Handles cleanup after the given {@code view} has been fully swiped out (including * re-invoking dismiss logic in case the notification has not made its way out yet). @@ -539,10 +538,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { */ public void handleChildViewDismissed(View view) { - if (view instanceof ExpandableNotificationRow row) { - mMagneticNotificationRowManager.onMagneticInteractionEnd( - row, null /* velocity */); - } // The View needs to clean up the Swipe states, e.g. roundness. mView.onSwipeEnd(); if (mView.getClearAllInProgress()) { @@ -614,11 +609,15 @@ public class NotificationStackScrollLayoutController implements Dumpable { @Override public void onBeginDrag(View v) { + mView.onSwipeBegin(v); + } + + @Override + public void setMagneticAndRoundableTargets(View v) { if (v instanceof ExpandableNotificationRow row) { mMagneticNotificationRowManager.setMagneticAndRoundableTargets( row, mView, mSectionsManager); } - mView.onSwipeBegin(v); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java index d476d482226d..6f4047f48205 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java @@ -362,7 +362,8 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc superSnapChild(animView, targetLeft, velocity); } - mCallback.onDragCancelledWithVelocity(animView, velocity); + mCallback.onMagneticInteractionEnd(animView, velocity); + mCallback.onDragCancelled(animView); if (targetLeft == 0) { handleMenuCoveredOrDismissed(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index a339bc98457e..58326dbb3a34 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -61,6 +61,7 @@ import kotlinx.coroutines.flow.StateFlowKt; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Objects; /** * The header group on Keyguard. @@ -103,6 +104,9 @@ public class KeyguardStatusBarView extends RelativeLayout { */ private int mCutoutSideNudge = 0; + @Nullable + private WindowInsets mPreviousInsets = null; + private DisplayCutout mDisplayCutout; private int mRoundedCornerPadding = 0; // right and left padding applied to this view to account for cutouts and rounded corners @@ -284,9 +288,12 @@ public class KeyguardStatusBarView extends RelativeLayout { WindowInsets updateWindowInsets( WindowInsets insets, StatusBarContentInsetsProvider insetsProvider) { - mLayoutState = LAYOUT_NONE; - if (updateLayoutConsideringCutout(insetsProvider)) { - requestLayout(); + if (!Objects.equals(mPreviousInsets, insets)) { + mLayoutState = LAYOUT_NONE; + if (updateLayoutConsideringCutout(insetsProvider)) { + requestLayout(); + } + mPreviousInsets = new WindowInsets(insets); } return super.onApplyWindowInsets(insets); } 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 b2c4ef95242b..01de925f3d78 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -65,6 +65,7 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags; import com.android.systemui.bouncer.ui.BouncerView; import com.android.systemui.bouncer.util.BouncerTestUtilsKt; +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor; @@ -170,6 +171,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private final Lazy<SceneInteractor> mSceneInteractorLazy; private final Lazy<DeviceEntryInteractor> mDeviceEntryInteractorLazy; private final DismissCallbackRegistry mDismissCallbackRegistry; + private final CommunalSceneInteractor mCommunalSceneInteractor; private Job mListenForAlternateBouncerTransitionSteps = null; private Job mListenForKeyguardAuthenticatedBiometricsHandled = null; @@ -406,7 +408,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Main DelayableExecutor executor, Lazy<DeviceEntryInteractor> deviceEntryInteractorLazy, DismissCallbackRegistry dismissCallbackRegistry, - Lazy<BouncerInteractor> bouncerInteractor + Lazy<BouncerInteractor> bouncerInteractor, + CommunalSceneInteractor communalSceneInteractor ) { mContext = context; mExecutor = executor; @@ -443,6 +446,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mStatusBarKeyguardViewManagerInteractor = statusBarKeyguardViewManagerInteractor; mDeviceEntryInteractorLazy = deviceEntryInteractorLazy; mDismissCallbackRegistry = dismissCallbackRegistry; + mCommunalSceneInteractor = communalSceneInteractor; } KeyguardTransitionInteractor mKeyguardTransitionInteractor; @@ -1364,11 +1368,13 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } mStatusBarStateController.setLeaveOpenOnKeyguardHide(false); - boolean hideBouncerOverDream = isBouncerShowing() - && mDreamOverlayStateController.isOverlayActive(); + boolean hideBouncerOverDreamOrHub = isBouncerShowing() + && (mDreamOverlayStateController.isOverlayActive() + || mCommunalSceneInteractor.isIdleOnCommunal().getValue()); mCentralSurfaces.endAffordanceLaunch(); // The second condition is for SIM card locked bouncer - if (hideBouncerOverDream || (primaryBouncerIsScrimmed() && !needsFullscreenBouncer())) { + if (hideBouncerOverDreamOrHub + || (primaryBouncerIsScrimmed() && !needsFullscreenBouncer())) { hideBouncer(false); updateStates(); } else { diff --git a/packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt b/packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt index 6b7de982e00a..22a74c86e0f1 100644 --- a/packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt @@ -16,10 +16,8 @@ package com.android.systemui.window.data.repository -import android.annotation.SuppressLint import com.android.systemui.dagger.SysUISingleton import javax.inject.Inject -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow /** Repository that maintains state for the window blur effect. */ @@ -28,6 +26,4 @@ class WindowRootViewBlurRepository @Inject constructor() { val blurRadius = MutableStateFlow(0) val isBlurOpaque = MutableStateFlow(false) - - @SuppressLint("SharedFlowCreation") val onBlurApplied = MutableSharedFlow<Int>() } diff --git a/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt b/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt index e21e0a1cadc7..9e369347dea5 100644 --- a/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt @@ -16,6 +16,7 @@ package com.android.systemui.window.domain.interactor +import android.annotation.SuppressLint import android.util.Log import com.android.systemui.Flags import com.android.systemui.communal.domain.interactor.CommunalInteractor @@ -28,6 +29,7 @@ import com.android.systemui.window.data.repository.WindowRootViewBlurRepository import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -52,6 +54,8 @@ constructor( private val communalInteractor: CommunalInteractor, private val repository: WindowRootViewBlurRepository, ) { + @SuppressLint("SharedFlowCreation") private val _onBlurAppliedEvent = MutableSharedFlow<Int>() + private var isBouncerTransitionInProgress: StateFlow<Boolean> = if (Flags.bouncerUiRevamp()) { keyguardTransitionInteractor @@ -68,7 +72,7 @@ constructor( * root view. */ suspend fun onBlurApplied(appliedBlurRadius: Int) { - repository.onBlurApplied.emit(appliedBlurRadius) + _onBlurAppliedEvent.emit(appliedBlurRadius) } /** Radius of blur to be applied on the window root view. */ @@ -77,7 +81,7 @@ constructor( /** * Emits the applied blur radius whenever blur is successfully applied to the window root view. */ - val onBlurAppliedEvent: Flow<Int> = repository.onBlurApplied + val onBlurAppliedEvent: Flow<Int> = _onBlurAppliedEvent /** Whether the blur applied is opaque or transparent. */ val isBlurOpaque: Flow<Boolean> = diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index e8054c07eac8..4ccfa29d4ba0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -206,7 +206,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock ShadeInteractor mShadeInteractor; private @Mock ShadeWindowLogger mShadeWindowLogger; private @Mock SelectedUserInteractor mSelectedUserInteractor; - private @Mock UserTracker.Callback mUserTrackerCallback; private @Mock KeyguardInteractor mKeyguardInteractor; private @Mock KeyguardTransitionBootInteractor mKeyguardTransitionBootInteractor; private @Captor ArgumentCaptor<KeyguardStateController.Callback> @@ -281,7 +280,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { () -> mShadeInteractor, mShadeWindowLogger, () -> mSelectedUserInteractor, - mock(UserTracker.class), + mUserTracker, mKosmos.getNotificationShadeWindowModel(), mSecureSettings, mKosmos::getCommunalInteractor, @@ -319,7 +318,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { } catch (Exception e) { // Just so we don't have to add the exception signature to every test. - fail(e.getMessage()); + fail(); } } @@ -331,156 +330,18 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { /* First test the default behavior: handleUserSwitching() is not invoked */ when(mUserTracker.isUserSwitching()).thenReturn(false); + mViewMediator.mUpdateCallback = mock(KeyguardUpdateMonitorCallback.class); mViewMediator.onSystemReady(); TestableLooper.get(this).processAllMessages(); - verify(mUserTrackerCallback, never()).onUserChanging(eq(userId), eq(mContext), - any(Runnable.class)); + verify(mViewMediator.mUpdateCallback, never()).onUserSwitching(userId); /* Next test user switching is already in progress when started */ when(mUserTracker.isUserSwitching()).thenReturn(true); mViewMediator.onSystemReady(); TestableLooper.get(this).processAllMessages(); - verify(mUserTrackerCallback).onUserChanging(eq(userId), eq(mContext), - any(Runnable.class)); - } - - @Test - @TestableLooper.RunWithLooper(setAsMainLooper = true) - public void testGoingAwayFollowedByBeforeUserSwitchDoesNotHideKeyguard() { - setCurrentUser(/* userId= */1099, /* isSecure= */false); - - // Setup keyguard - mViewMediator.onSystemReady(); - processAllMessagesAndBgExecutorMessages(); - mViewMediator.setShowingLocked(true, ""); - - // Request keyguard going away - when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true); - mViewMediator.mKeyguardGoingAwayRunnable.run(); - - // After the request, begin a switch to a new secure user - int nextUserId = 500; - setCurrentUser(nextUserId, /* isSecure= */true); - Runnable result = mock(Runnable.class); - mViewMediator.handleBeforeUserSwitching(nextUserId, result); - processAllMessagesAndBgExecutorMessages(); - verify(result).run(); - - // After that request has begun, have WM tell us to exit keyguard - RemoteAnimationTarget[] apps = new RemoteAnimationTarget[]{ - mock(RemoteAnimationTarget.class) - }; - RemoteAnimationTarget[] wallpapers = new RemoteAnimationTarget[]{ - mock(RemoteAnimationTarget.class) - }; - IRemoteAnimationFinishedCallback callback = mock(IRemoteAnimationFinishedCallback.class); - mViewMediator.startKeyguardExitAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY, apps, wallpapers, - null, callback); - processAllMessagesAndBgExecutorMessages(); - - // The call to exit should be rejected, and keyguard should still be visible - verify(mKeyguardUnlockAnimationController, never()).notifyStartSurfaceBehindRemoteAnimation( - any(), any(), any(), anyLong(), anyBoolean()); - try { - assertATMSLockScreenShowing(true); - } catch (Exception e) { - fail(e.getMessage()); - } - assertTrue(mViewMediator.isShowingAndNotOccluded()); - } - - @Test - @TestableLooper.RunWithLooper(setAsMainLooper = true) - public void testUserSwitchToSecureUserShowsBouncer() { - setCurrentUser(/* userId= */1099, /* isSecure= */true); - - // Setup keyguard - mViewMediator.onSystemReady(); - processAllMessagesAndBgExecutorMessages(); - mViewMediator.setShowingLocked(true, ""); - - // After the request, begin a switch to a new secure user - int nextUserId = 500; - setCurrentUser(nextUserId, /* isSecure= */true); - - Runnable beforeResult = mock(Runnable.class); - mViewMediator.handleBeforeUserSwitching(nextUserId, beforeResult); - processAllMessagesAndBgExecutorMessages(); - verify(beforeResult).run(); - - // Dismiss should not be called while user switch is in progress - Runnable onSwitchResult = mock(Runnable.class); - mViewMediator.handleUserSwitching(nextUserId, onSwitchResult); - processAllMessagesAndBgExecutorMessages(); - verify(onSwitchResult).run(); - verify(mStatusBarKeyguardViewManager, never()).dismissAndCollapse(); - - // The attempt to dismiss only comes on user switch complete, which will trigger a call to - // show the bouncer in StatusBarKeyguardViewManager - mViewMediator.handleUserSwitchComplete(nextUserId); - TestableLooper.get(this).moveTimeForward(600); - processAllMessagesAndBgExecutorMessages(); - - verify(mStatusBarKeyguardViewManager).dismissAndCollapse(); - } - - @Test - @TestableLooper.RunWithLooper(setAsMainLooper = true) - public void testUserSwitchToInsecureUserDismissesKeyguard() { - int userId = 1099; - when(mUserTracker.getUserId()).thenReturn(userId); - - // Setup keyguard - mViewMediator.onSystemReady(); - processAllMessagesAndBgExecutorMessages(); - mViewMediator.setShowingLocked(true, ""); - - // After the request, begin a switch to an insecure user - int nextUserId = 500; - when(mLockPatternUtils.isSecure(nextUserId)).thenReturn(false); - - Runnable beforeResult = mock(Runnable.class); - mViewMediator.handleBeforeUserSwitching(nextUserId, beforeResult); - processAllMessagesAndBgExecutorMessages(); - verify(beforeResult).run(); - - // The call to dismiss comes during the user switch - Runnable onSwitchResult = mock(Runnable.class); - mViewMediator.handleUserSwitching(nextUserId, onSwitchResult); - processAllMessagesAndBgExecutorMessages(); - verify(onSwitchResult).run(); - - verify(mStatusBarKeyguardViewManager).dismissAndCollapse(); - } - - @Test - @TestableLooper.RunWithLooper(setAsMainLooper = true) - public void testUserSwitchToSecureUserWhileKeyguardNotVisibleShowsKeyguard() { - setCurrentUser(/* userId= */1099, /* isSecure= */true); - - // Setup keyguard as not visible - mViewMediator.onSystemReady(); - processAllMessagesAndBgExecutorMessages(); - mViewMediator.setShowingLocked(false, ""); - processAllMessagesAndBgExecutorMessages(); - - // Begin a switch to a new secure user - int nextUserId = 500; - setCurrentUser(nextUserId, /* isSecure= */true); - - Runnable beforeResult = mock(Runnable.class); - mViewMediator.handleBeforeUserSwitching(nextUserId, beforeResult); - processAllMessagesAndBgExecutorMessages(); - verify(beforeResult).run(); - - try { - assertATMSLockScreenShowing(true); - } catch (Exception e) { - fail(); - } - assertTrue(mViewMediator.isShowingAndNotOccluded()); + verify(mViewMediator.mUpdateCallback).onUserSwitching(userId); } @Test @@ -1244,7 +1105,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { processAllMessagesAndBgExecutorMessages(); verify(mStatusBarKeyguardViewManager, never()).reset(anyBoolean()); - + assertATMSAndKeyguardViewMediatorStatesMatch(); } @Test @@ -1288,7 +1149,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { IRemoteAnimationFinishedCallback callback = mock(IRemoteAnimationFinishedCallback.class); when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true); - mViewMediator.mKeyguardGoingAwayRunnable.run(); mViewMediator.startKeyguardExitAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY, apps, wallpapers, null, callback); processAllMessagesAndBgExecutorMessages(); @@ -1343,6 +1203,13 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { // The captor will have the most recent setLockScreenShown call's value. assertEquals(showing, showingCaptor.getValue()); + + // We're now just after the last setLockScreenShown call. If we expect the lockscreen to be + // showing, ensure that we didn't subsequently ask for it to go away. + if (showing) { + orderedSetLockScreenShownCalls.verify(mActivityTaskManagerService, never()) + .keyguardGoingAway(anyInt()); + } } /** @@ -1504,7 +1371,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mKeyguardTransitionBootInteractor, mKosmos::getCommunalSceneInteractor, mock(WindowManagerOcclusionManager.class)); - mViewMediator.mUserChangedCallback = mUserTrackerCallback; mViewMediator.start(); mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null); @@ -1518,10 +1384,4 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private void captureKeyguardUpdateMonitorCallback() { verify(mUpdateMonitor).registerCallback(mKeyguardUpdateMonitorCallbackCaptor.capture()); } - - private void setCurrentUser(int userId, boolean isSecure) { - when(mUserTracker.getUserId()).thenReturn(userId); - when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(userId); - when(mLockPatternUtils.isSecure(userId)).thenReturn(isSecure); - } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt index ce298bb90ba2..825e0143800b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt @@ -5,6 +5,8 @@ import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.classifier.domain.interactor.falsingInteractor import com.android.systemui.haptics.msdl.msdlPlayer import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel +import com.android.systemui.keyguard.ui.viewmodel.keyguardClockViewModel import com.android.systemui.keyguard.ui.viewmodel.lightRevealScrimViewModel import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture @@ -105,6 +107,8 @@ val Kosmos.sceneContainerViewModelFactory by Fixture { lightRevealScrim = lightRevealScrimViewModel, wallpaperViewModel = wallpaperViewModel, keyguardInteractor = keyguardInteractor, + burnIn = aodBurnInViewModel, + clock = keyguardClockViewModel, ) } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt deleted file mode 100644 index 923b36d4f2cf..000000000000 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.phone.ongoingcall.shared.model - -import android.app.PendingIntent -import com.android.systemui.statusbar.StatusBarIconView -import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel - -/** Helper for building [OngoingCallModel.InCall] instances in tests. */ -fun inCallModel( - startTimeMs: Long, - notificationIcon: StatusBarIconView? = null, - intent: PendingIntent? = null, - notificationKey: String = "test", - appName: String = "", - promotedContent: PromotedNotificationContentModel? = null, -) = - OngoingCallModel.InCall( - startTimeMs, - notificationIcon, - intent, - notificationKey, - appName, - promotedContent, - ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt new file mode 100644 index 000000000000..7bcedcaa99d1 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone.ongoingcall.shared.model + +import android.app.PendingIntent +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.core.StatusBarConnectedDisplays +import com.android.systemui.statusbar.notification.data.model.activeNotificationModel +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore +import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel +import com.android.systemui.statusbar.notification.shared.CallType +import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization +import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository +import org.mockito.kotlin.mock + +/** Helper for building [OngoingCallModel.InCall] instances in tests. */ +fun inCallModel( + startTimeMs: Long, + notificationIcon: StatusBarIconView? = null, + intent: PendingIntent? = null, + notificationKey: String = "test", + appName: String = "", + promotedContent: PromotedNotificationContentModel? = null, +) = + OngoingCallModel.InCall( + startTimeMs, + notificationIcon, + intent, + notificationKey, + appName, + promotedContent, + ) + +object OngoingCallTestHelper { + /** + * Sets the call state to be no call, and does it correctly based on whether + * [StatusBarChipsModernization] is enabled or not. + */ + fun setNoCallState(kosmos: Kosmos) { + if (StatusBarChipsModernization.isEnabled) { + // TODO(b/372657935): Maybe don't clear *all* notifications + kosmos.activeNotificationListRepository.activeNotifications.value = + ActiveNotificationsStore() + } else { + kosmos.ongoingCallRepository.setOngoingCallState(OngoingCallModel.NoCall) + } + } + + /** + * Sets the ongoing call state correctly based on whether [StatusBarChipsModernization] is + * enabled or not. + */ + fun setOngoingCallState( + kosmos: Kosmos, + startTimeMs: Long = 1000L, + key: String = "notif", + statusBarChipIconView: StatusBarIconView? = createStatusBarIconViewOrNull(), + promotedContent: PromotedNotificationContentModel? = null, + contentIntent: PendingIntent? = null, + uid: Int = DEFAULT_UID, + ) { + if (StatusBarChipsModernization.isEnabled) { + kosmos.activeNotificationListRepository.activeNotifications.value = + ActiveNotificationsStore.Builder() + .apply { + addIndividualNotif( + activeNotificationModel( + key = key, + whenTime = startTimeMs, + callType = CallType.Ongoing, + statusBarChipIcon = statusBarChipIconView, + contentIntent = contentIntent, + promotedContent = promotedContent, + uid = uid, + ) + ) + } + .build() + } else { + kosmos.ongoingCallRepository.setOngoingCallState( + inCallModel( + startTimeMs = startTimeMs, + notificationIcon = statusBarChipIconView, + intent = contentIntent, + notificationKey = key, + promotedContent = promotedContent, + ) + ) + } + } + + private fun createStatusBarIconViewOrNull(): StatusBarIconView? = + if (StatusBarConnectedDisplays.isEnabled) { + null + } else { + mock<StatusBarIconView>() + } + + private const val DEFAULT_UID = 886 +} diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java index 0354d2be60c9..2ef11f4b78e1 100644 --- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java +++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java @@ -20,11 +20,14 @@ import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_M import android.content.Context; import android.graphics.PixelFormat; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.WindowInsets; import android.view.WindowManager; +import android.widget.ImageButton; import android.widget.LinearLayout; import androidx.annotation.NonNull; @@ -51,6 +54,8 @@ public class AutoclickTypePanel { private final LinearLayout mDragButton; private final LinearLayout mScrollButton; + private LinearLayout mSelectedButton; + public AutoclickTypePanel(Context context, WindowManager windowManager) { mContext = context; mWindowManager = windowManager; @@ -80,6 +85,40 @@ public class AutoclickTypePanel { // Initializes panel as collapsed state and only displays the left click button. hideAllClickTypeButtons(); mLeftClickButton.setVisibility(View.VISIBLE); + setSelectedButton(/* selectedButton= */ mLeftClickButton); + } + + /** Sets the selected button and updates the newly and previously selected button styling. */ + private void setSelectedButton(@NonNull LinearLayout selectedButton) { + // Updates the previously selected button styling. + if (mSelectedButton != null) { + toggleSelectedButtonStyle(mSelectedButton, /* isSelected= */ false); + } + + mSelectedButton = selectedButton; + + // Updates the newly selected button styling. + toggleSelectedButtonStyle(selectedButton, /* isSelected= */ true); + } + + private void toggleSelectedButtonStyle(@NonNull LinearLayout button, boolean isSelected) { + // Sets icon background color. + GradientDrawable gradientDrawable = (GradientDrawable) button.getBackground(); + gradientDrawable.setColor( + mContext.getColor( + isSelected + ? R.color.materialColorPrimary + : R.color.materialColorSurfaceContainer)); + + // Sets icon color. + ImageButton imageButton = (ImageButton) button.getChildAt(/* index= */ 0); + Drawable drawable = imageButton.getDrawable(); + drawable.mutate() + .setTint( + mContext.getColor( + isSelected + ? R.color.materialColorSurfaceContainer + : R.color.materialColorPrimary)); } public void show() { @@ -97,6 +136,9 @@ public class AutoclickTypePanel { // buttons except the one user selected. hideAllClickTypeButtons(); button.setVisibility(View.VISIBLE); + + // Sets the newly selected button. + setSelectedButton(/* selectedButton= */ button); } else { // If the panel is already collapsed, we just need to expand it. showAllClickTypeButtons(); diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java index 43764442e2cf..d0ee7af1bbfb 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java @@ -27,6 +27,7 @@ import android.annotation.WorkerThread; import android.app.appfunctions.AppFunctionException; import android.app.appfunctions.AppFunctionManager; import android.app.appfunctions.AppFunctionManagerHelper; +import android.app.appfunctions.AppFunctionManagerHelper.AppFunctionNotFoundException; import android.app.appfunctions.AppFunctionRuntimeMetadata; import android.app.appfunctions.AppFunctionStaticMetadataHelper; import android.app.appfunctions.ExecuteAppFunctionAidlRequest; @@ -513,7 +514,9 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { e = e.getCause(); } int resultCode = AppFunctionException.ERROR_SYSTEM_ERROR; - if (e instanceof AppSearchException appSearchException) { + if (e instanceof AppFunctionNotFoundException) { + resultCode = AppFunctionException.ERROR_FUNCTION_NOT_FOUND; + } else if (e instanceof AppSearchException appSearchException) { resultCode = mapAppSearchResultFailureCodeToExecuteAppFunctionResponse( appSearchException.getResultCode()); diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index e0fbaf43ea43..27e9e44f1090 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -31,7 +31,6 @@ import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE; import static android.app.ActivityManagerInternal.ALLOW_PROFILES_OR_NON_FULL; -import static android.app.KeyguardManager.LOCK_ON_USER_SWITCH_CALLBACK; import static android.os.PowerWhitelistManager.REASON_BOOT_COMPLETED; import static android.os.PowerWhitelistManager.REASON_LOCKED_BOOT_COMPLETED; import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; @@ -3905,6 +3904,10 @@ class UserController implements Handler.Callback { return mService.mWindowManager; } + ActivityTaskManagerInternal getActivityTaskManagerInternal() { + return mService.mAtmInternal; + } + void activityManagerOnUserStopped(@UserIdInt int userId) { LocalServices.getService(ActivityTaskManagerInternal.class).onUserStopped(userId); } @@ -4119,25 +4122,40 @@ class UserController implements Handler.Callback { } void lockDeviceNowAndWaitForKeyguardShown() { + if (getWindowManager().isKeyguardLocked()) { + Slogf.w(TAG, "Not locking the device since the keyguard is already locked"); + return; + } + final TimingsTraceAndSlog t = new TimingsTraceAndSlog(); t.traceBegin("lockDeviceNowAndWaitForKeyguardShown"); final CountDownLatch latch = new CountDownLatch(1); - Bundle bundle = new Bundle(); - bundle.putBinder(LOCK_ON_USER_SWITCH_CALLBACK, new IRemoteCallback.Stub() { - public void sendResult(Bundle data) { - latch.countDown(); - } - }); - getWindowManager().lockNow(bundle); + ActivityTaskManagerInternal.ScreenObserver screenObserver = + new ActivityTaskManagerInternal.ScreenObserver() { + @Override + public void onAwakeStateChanged(boolean isAwake) { + + } + + @Override + public void onKeyguardStateChanged(boolean isShowing) { + if (isShowing) { + latch.countDown(); + } + } + }; + + getActivityTaskManagerInternal().registerScreenObserver(screenObserver); + getWindowManager().lockDeviceNow(); try { if (!latch.await(20, TimeUnit.SECONDS)) { - throw new RuntimeException("User controller expected a callback while waiting " - + "to show the keyguard. Timed out after 20 seconds."); + throw new RuntimeException("Keyguard is not shown in 20 seconds"); } } catch (InterruptedException e) { throw new RuntimeException(e); } finally { + getActivityTaskManagerInternal().unregisterScreenObserver(screenObserver); t.traceEnd(); } } diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java index cc4c2b5bf893..068d68d25017 100644 --- a/services/core/java/com/android/server/pm/AppsFilterImpl.java +++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java @@ -40,6 +40,7 @@ import static com.android.server.pm.AppsFilterUtils.canQueryViaUsesLibrary; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.app.ApplicationPackageManager; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.SigningDetails; @@ -173,6 +174,10 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, * Report a change to observers. */ private void onChanged() { + // App visibility may have changed, which means that earlier fetches from these caches may + // be invalid. + PackageManager.invalidatePackageInfoCache(); + ApplicationPackageManager.invalidateGetPackagesForUidCache(); dispatchChange(this); } diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java index 66aaa562b873..a01df8bf108d 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java @@ -37,6 +37,7 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.Signature; import android.content.pm.SigningDetails; import android.content.pm.UserInfo; +import android.app.PropertyInvalidatedCache; import android.os.Build; import android.os.Handler; import android.os.Message; @@ -50,6 +51,8 @@ import android.util.SparseArray; import androidx.annotation.NonNull; +import android.app.ApplicationPackageManager; +import android.content.pm.PackageManager; import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.pm.pkg.component.ParsedActivity; @@ -64,8 +67,10 @@ import com.android.internal.pm.pkg.component.ParsedUsesPermissionImpl; import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.server.om.OverlayReferenceMapper; import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.utils.Watchable; import com.android.server.utils.WatchableTester; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -244,6 +249,55 @@ public class AppsFilterImplTest { (Answer<Boolean>) invocation -> ((AndroidPackage) invocation.getArgument(SYSTEM_USER)).getTargetSdkVersion() >= Build.VERSION_CODES.R); + PropertyInvalidatedCache.setTestMode(true); + PackageManager.sApplicationInfoCache.testPropertyName(); + ApplicationPackageManager.sGetPackagesForUidCache.testPropertyName(); + } + + @After + public void tearDown() { + PropertyInvalidatedCache.setTestMode(false); + } + + /** + * A class to make it easier to verify that PM caches are properly invalidated by + * AppsFilterImpl operations. This extends WatchableTester to test the cache nonces along + * with change reporting. + */ + private static class NonceTester extends WatchableTester { + // The nonces from caches under consideration. The no-parameter constructor fetches the + // values from the cacches. + private static record Nonces(long applicationInfo, long packageInfo) { + Nonces() { + this(ApplicationPackageManager.sGetPackagesForUidCache.getNonce(), + PackageManager.sApplicationInfoCache.getNonce()); + } + } + + // Track the latest cache nonces. + private Nonces mNonces; + + NonceTester(Watchable w, String k) { + super(w, k); + mNonces = new Nonces(); + } + + @Override + public void verifyChangeReported(String msg) { + super.verifyChangeReported(msg); + Nonces update = new Nonces(); + assertTrue(msg, update.applicationInfo != mNonces.applicationInfo); + assertTrue(msg, update.packageInfo != mNonces.packageInfo); + mNonces = update; + } + + @Override + public void verifyNoChangeReported(String msg) { + super.verifyNoChangeReported(msg); + Nonces update = new Nonces(); + assertTrue(msg, update.applicationInfo == mNonces.applicationInfo); + assertTrue(msg, update.packageInfo == mNonces.packageInfo); + } } @Test @@ -1167,7 +1221,7 @@ public class AppsFilterImplTest { final AppsFilterImpl appsFilter = new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null, mMockHandler); - final WatchableTester watcher = new WatchableTester(appsFilter, "onChange"); + final WatchableTester watcher = new NonceTester(appsFilter, "onChange"); watcher.register(); simulateAddBasicAndroid(appsFilter); watcher.verifyChangeReported("addBasicAndroid"); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java index f0334598bd30..7b8824cb0e3d 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java @@ -21,6 +21,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static com.google.common.truth.Truth.assertThat; import android.content.Context; +import android.graphics.drawable.GradientDrawable; import android.testing.AndroidTestingRunner; import android.testing.TestableContext; import android.testing.TestableLooper; @@ -28,6 +29,8 @@ import android.view.View; import android.view.WindowManager; import android.widget.LinearLayout; +import androidx.annotation.NonNull; + import com.android.internal.R; import org.junit.Before; @@ -87,6 +90,11 @@ public class AutoclickTypePanelTest { } @Test + public void AutoclickTypePanel_initialState_correctButtonStyle() { + verifyButtonHasSelectedStyle(mLeftClickButton); + } + + @Test public void togglePanelExpansion_onClick_expandedTrue() { // On clicking left click button, the panel is expanded and all buttons are visible. mLeftClickButton.callOnClick(); @@ -116,4 +124,21 @@ public class AutoclickTypePanelTest { assertThat(mDoubleClickButton.getVisibility()).isEqualTo(View.GONE); assertThat(mDragButton.getVisibility()).isEqualTo(View.GONE); } + + @Test + public void togglePanelExpansion_selectButton_correctStyle() { + // By first click, the panel is expanded. + mLeftClickButton.callOnClick(); + + // Clicks any button in the expanded state to select a type button. + mScrollButton.callOnClick(); + + verifyButtonHasSelectedStyle(mScrollButton); + } + + private void verifyButtonHasSelectedStyle(@NonNull LinearLayout button) { + GradientDrawable gradientDrawable = (GradientDrawable) button.getBackground(); + assertThat(gradientDrawable.getColor().getDefaultColor()) + .isEqualTo(mTestableContext.getColor(R.color.materialColorPrimary)); + } } diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index 1627f683cd3e..06958b81d846 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -25,7 +25,6 @@ import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE; import static android.app.ActivityManagerInternal.ALLOW_PROFILES_OR_NON_FULL; -import static android.app.KeyguardManager.LOCK_ON_USER_SWITCH_CALLBACK; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader; @@ -116,6 +115,7 @@ import com.android.server.pm.UserManagerInternal; import com.android.server.pm.UserManagerService; import com.android.server.pm.UserTypeDetails; import com.android.server.pm.UserTypeFactory; +import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerService; import com.google.common.collect.Range; @@ -1563,11 +1563,11 @@ public class UserControllerTest { // and the thread is still alive assertTrue(threadStartUser.isAlive()); - // mock the binder response for the user switch completion - ArgumentCaptor<Bundle> captor = ArgumentCaptor.forClass(Bundle.class); - verify(mInjector.mWindowManagerMock).lockNow(captor.capture()); - IRemoteCallback.Stub.asInterface(captor.getValue().getBinder( - LOCK_ON_USER_SWITCH_CALLBACK)).sendResult(null); + // mock send the keyguard shown event + ArgumentCaptor<ActivityTaskManagerInternal.ScreenObserver> captor = ArgumentCaptor.forClass( + ActivityTaskManagerInternal.ScreenObserver.class); + verify(mInjector.mActivityTaskManagerInternal).registerScreenObserver(captor.capture()); + captor.getValue().onKeyguardStateChanged(true); // verify the switch now moves on... Thread.sleep(1000); @@ -1757,6 +1757,7 @@ public class UserControllerTest { private final IStorageManager mStorageManagerMock; private final UserManagerInternal mUserManagerInternalMock; private final WindowManagerService mWindowManagerMock; + private final ActivityTaskManagerInternal mActivityTaskManagerInternal; private final PowerManagerInternal mPowerManagerInternal; private final AlarmManagerInternal mAlarmManagerInternal; private final KeyguardManager mKeyguardManagerMock; @@ -1778,6 +1779,7 @@ public class UserControllerTest { mUserManagerMock = mock(UserManagerService.class); mUserManagerInternalMock = mock(UserManagerInternal.class); mWindowManagerMock = mock(WindowManagerService.class); + mActivityTaskManagerInternal = mock(ActivityTaskManagerInternal.class); mStorageManagerMock = mock(IStorageManager.class); mPowerManagerInternal = mock(PowerManagerInternal.class); mAlarmManagerInternal = mock(AlarmManagerInternal.class); @@ -1841,6 +1843,11 @@ public class UserControllerTest { } @Override + ActivityTaskManagerInternal getActivityTaskManagerInternal() { + return mActivityTaskManagerInternal; + } + + @Override PowerManagerInternal getPowerManagerInternal() { return mPowerManagerInternal; } |