diff options
105 files changed, 2715 insertions, 1432 deletions
diff --git a/Android.bp b/Android.bp index 9d3b64d7335b..303fa2cd18da 100644 --- a/Android.bp +++ b/Android.bp @@ -583,6 +583,7 @@ java_library {          "documents-ui-compat-config",          "calendar-provider-compat-config",          "contacts-provider-platform-compat-config", +        "SystemUI-core-compat-config",      ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {          "true": [],          default: [ diff --git a/apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java b/apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java index 8e3ed6d9931c..7a7250b9e910 100644 --- a/apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java +++ b/apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java @@ -19,27 +19,24 @@ 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.test.filters.LargeTest; -import androidx.test.runner.AndroidJUnit4; +import androidx.benchmark.BenchmarkState; +import androidx.benchmark.junit4.BenchmarkRule; +import androidx.test.filters.SmallTest;  import org.junit.Rule;  import org.junit.Test; -import org.junit.runner.RunWith; -@LargeTest -@RunWith(AndroidJUnit4.class) +@SmallTest  public class ViewConfigurationPerfTest {      @Rule -    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); +    public final BenchmarkRule mBenchmarkRule = new BenchmarkRule();      private final Context mContext = getInstrumentation().getTargetContext();      @Test      public void testGet_newViewConfiguration() { -        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); +        final BenchmarkState state = mBenchmarkRule.getState();          while (state.keepRunning()) {              state.pauseTiming(); @@ -53,7 +50,7 @@ public class ViewConfigurationPerfTest {      @Test      public void testGet_cachedViewConfiguration() { -        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); +        final BenchmarkState state = mBenchmarkRule.getState();          // Do `get` once to make sure there's something cached.          ViewConfiguration.get(mContext); @@ -61,265 +58,4 @@ 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/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig index c3dc257e6535..fcdb02ab5da2 100644 --- a/core/java/android/companion/virtual/flags/flags.aconfig +++ b/core/java/android/companion/virtual/flags/flags.aconfig @@ -125,11 +125,3 @@ 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/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/os/health/SystemHealthManager.java b/core/java/android/os/health/SystemHealthManager.java index 9d0e221bd9e7..a8a22f675e08 100644 --- a/core/java/android/os/health/SystemHealthManager.java +++ b/core/java/android/os/health/SystemHealthManager.java @@ -473,17 +473,31 @@ public class SystemHealthManager {              }          } -        final HealthStats[] results = new HealthStats[uids.length]; -        if (result.bundle != null) { -            HealthStatsParceler[] parcelers = result.bundle.getParcelableArray( -                    IBatteryStats.KEY_UID_SNAPSHOTS, HealthStatsParceler.class); -            if (parcelers != null && parcelers.length == uids.length) { -                for (int i = 0; i < parcelers.length; i++) { -                    results[i] = parcelers[i].getHealthStats(); +        switch (result.resultCode) { +            case IBatteryStats.RESULT_OK: { +                final HealthStats[] results = new HealthStats[uids.length]; +                if (result.bundle != null) { +                    HealthStatsParceler[] parcelers = result.bundle.getParcelableArray( +                            IBatteryStats.KEY_UID_SNAPSHOTS, HealthStatsParceler.class); +                    if (parcelers != null && parcelers.length == uids.length) { +                        for (int i = 0; i < parcelers.length; i++) { +                            results[i] = parcelers[i].getHealthStats(); +                        } +                    }                  } +                return results; +            } +            case IBatteryStats.RESULT_SECURITY_EXCEPTION: { +                throw new SecurityException(result.bundle != null +                        ? result.bundle.getString(IBatteryStats.KEY_EXCEPTION_MESSAGE) : null); +            } +            case IBatteryStats.RESULT_RUNTIME_EXCEPTION: { +                throw new RuntimeException(result.bundle != null +                        ? result.bundle.getString(IBatteryStats.KEY_EXCEPTION_MESSAGE) : null);              } +            default: +                throw new RuntimeException("Error code: " + result.resultCode);          } -        return results;      }      /** diff --git a/core/java/android/view/RoundScrollbarRenderer.java b/core/java/android/view/RoundScrollbarRenderer.java index 5e1eadae0953..331e34526ae8 100644 --- a/core/java/android/view/RoundScrollbarRenderer.java +++ b/core/java/android/view/RoundScrollbarRenderer.java @@ -20,6 +20,7 @@ import static android.util.MathUtils.acos;  import static java.lang.Math.sin; +import android.content.res.Resources;  import android.graphics.Canvas;  import android.graphics.Color;  import android.graphics.Paint; @@ -40,9 +41,9 @@ public class RoundScrollbarRenderer {      // The range of the scrollbar position represented as an angle in degrees.      private static final float SCROLLBAR_ANGLE_RANGE = 28.8f; -    private static final float MAX_SCROLLBAR_ANGLE_SWIPE = 26.3f; // 90% -    private static final float MIN_SCROLLBAR_ANGLE_SWIPE = 3.1f; // 10% -    private static final float THUMB_WIDTH_DP = 4f; +    private static final float MAX_SCROLLBAR_ANGLE_SWIPE = 0.7f * SCROLLBAR_ANGLE_RANGE; +    private static final float MIN_SCROLLBAR_ANGLE_SWIPE = 0.3f * SCROLLBAR_ANGLE_RANGE; +    private static final float GAP_BETWEEN_TRACK_AND_THUMB_DP = 3f;      private static final float OUTER_PADDING_DP = 2f;      private static final int DEFAULT_THUMB_COLOR = 0xFFFFFFFF;      private static final int DEFAULT_TRACK_COLOR = 0x4CFFFFFF; @@ -57,14 +58,16 @@ public class RoundScrollbarRenderer {      private final RectF mRect = new RectF();      private final View mParent;      private final float mInset; +    private final float mGapBetweenThumbAndTrackPx; +    private final boolean mUseRefactoredRoundScrollbar;      private float mPreviousMaxScroll = 0;      private float mMaxScrollDiff = 0;      private float mPreviousCurrentScroll = 0;      private float mCurrentScrollDiff = 0;      private float mThumbStrokeWidthAsDegrees = 0; +    private float mGapBetweenTrackAndThumbAsDegrees = 0;      private boolean mDrawToLeft; -    private boolean mUseRefactoredRoundScrollbar;      public RoundScrollbarRenderer(View parent) {          // Paints for the round scrollbar. @@ -80,16 +83,17 @@ public class RoundScrollbarRenderer {          mParent = parent; +        Resources resources = parent.getContext().getResources();          // Fetch the resource indicating the thickness of CircularDisplayMask, rounding in the same          // way WindowManagerService.showCircularMask does. The scroll bar is inset by this amount so          // that it doesn't get clipped.          int maskThickness = -                parent.getContext() -                        .getResources() -                        .getDimensionPixelSize( -                                com.android.internal.R.dimen.circular_display_mask_thickness); +                resources.getDimensionPixelSize( +                        com.android.internal.R.dimen.circular_display_mask_thickness); -        float thumbWidth = dpToPx(THUMB_WIDTH_DP); +        float thumbWidth = +                resources.getDimensionPixelSize(com.android.internal.R.dimen.round_scrollbar_width); +        mGapBetweenThumbAndTrackPx = dpToPx(GAP_BETWEEN_TRACK_AND_THUMB_DP);          mThumbPaint.setStrokeWidth(thumbWidth);          mTrackPaint.setStrokeWidth(thumbWidth);          mInset = thumbWidth / 2 + maskThickness; @@ -175,7 +179,6 @@ public class RoundScrollbarRenderer {          }      } -    /** Returns true if horizontal bounds are updated */      private void updateBounds(Rect bounds) {          mRect.set(                  bounds.left + mInset, @@ -184,6 +187,8 @@ public class RoundScrollbarRenderer {                  bounds.bottom - mInset);          mThumbStrokeWidthAsDegrees =                  getVertexAngle((mRect.right - mRect.left) / 2f, mThumbPaint.getStrokeWidth() / 2f); +        mGapBetweenTrackAndThumbAsDegrees = +                getVertexAngle((mRect.right - mRect.left) / 2f, mGapBetweenThumbAndTrackPx);      }      private float computeSweepAngle(float scrollExtent, float maxScroll) { @@ -262,20 +267,22 @@ public class RoundScrollbarRenderer {                  // The highest point of the top track on a vertical scale. Here the thumb width is                  // reduced to account for the arc formed by ROUND stroke style                  -SCROLLBAR_ANGLE_RANGE / 2f - mThumbStrokeWidthAsDegrees, -                // The lowest point of the top track on a vertical scale. Here the thumb width is -                // reduced twice to (a) account for the arc formed by ROUND stroke style (b) gap -                // between thumb and top track -                thumbStartAngle - mThumbStrokeWidthAsDegrees * 2, +                // The lowest point of the top track on a vertical scale. It's reduced by +                // (a) angular distance for the arc formed by ROUND stroke style +                // (b) gap between thumb and top track +                thumbStartAngle - mThumbStrokeWidthAsDegrees - mGapBetweenTrackAndThumbAsDegrees,                  alpha);          // Draws the thumb          drawArc(canvas, thumbStartAngle, thumbSweepAngle, mThumbPaint);          // Draws the bottom arc          drawTrack(                  canvas, -                // The highest point of the bottom track on a vertical scale. Here the thumb width -                // is added twice to (a) account for the arc formed by ROUND stroke style (b) gap -                // between thumb and bottom track -                (thumbStartAngle + thumbSweepAngle) + mThumbStrokeWidthAsDegrees * 2, +                // The highest point of the bottom track on a vertical scale. Following added to it +                // (a) angular distance for the arc formed by ROUND stroke style +                // (b) gap between thumb and top track +                (thumbStartAngle + thumbSweepAngle) +                        + mThumbStrokeWidthAsDegrees +                        + mGapBetweenTrackAndThumbAsDegrees,                  // The lowest point of the top track on a vertical scale. Here the thumb width is                  // added to account for the arc formed by ROUND stroke style                  SCROLLBAR_ANGLE_RANGE / 2f + mThumbStrokeWidthAsDegrees, diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index 2895bf3f846a..9e97a8eb58aa 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -21,9 +21,7 @@ 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; @@ -41,13 +39,14 @@ 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 @@ -350,8 +349,6 @@ 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; @@ -377,6 +374,7 @@ 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; @@ -470,12 +468,14 @@ public class ViewConfiguration {          mEdgeSlop = (int) (sizeAndDensity * EDGE_SLOP + 0.5f);          mFadingEdgeLength = (int) (sizeAndDensity * FADING_EDGE_LENGTH + 0.5f); -        mScrollbarSize = res.getDimensionPixelSize(R.dimen.config_scrollbarSize); +        mScrollbarSize = res.getDimensionPixelSize( +                com.android.internal.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(R.dimen.config_ambiguousGestureMultiplier, +        res.getValue( +                com.android.internal.R.dimen.config_ambiguousGestureMultiplier,                  multiplierValue,                  true /*resolveRefs*/);          mAmbiguousGestureMultiplier = Math.max(1.0f, multiplierValue.getFloat()); @@ -488,7 +488,8 @@ public class ViewConfiguration {          mOverflingDistance = (int) (sizeAndDensity * OVERFLING_DISTANCE + 0.5f);          if (!sHasPermanentMenuKeySet) { -            final int configVal = res.getInteger(R.integer.config_overrideHasPermanentMenuKey); +            final int configVal = res.getInteger( +                    com.android.internal.R.integer.config_overrideHasPermanentMenuKey);              switch (configVal) {                  default: @@ -515,27 +516,32 @@ public class ViewConfiguration {              }          } -        mFadingMarqueeEnabled = res.getBoolean(R.bool.config_ui_enableFadingMarquee); -        mTouchSlop = res.getDimensionPixelSize(R.dimen.config_viewConfigurationTouchSlop); +        mFadingMarqueeEnabled = res.getBoolean( +                com.android.internal.R.bool.config_ui_enableFadingMarquee); +        mTouchSlop = res.getDimensionPixelSize( +                com.android.internal.R.dimen.config_viewConfigurationTouchSlop);          mHandwritingSlop = res.getDimensionPixelSize( -                R.dimen.config_viewConfigurationHandwritingSlop); -        mHoverSlop = res.getDimensionPixelSize(R.dimen.config_viewConfigurationHoverSlop); +                com.android.internal.R.dimen.config_viewConfigurationHandwritingSlop); +        mHoverSlop = res.getDimensionPixelSize( +                com.android.internal.R.dimen.config_viewConfigurationHoverSlop);          mMinScrollbarTouchTarget = res.getDimensionPixelSize( -                R.dimen.config_minScrollbarTouchTarget); +                com.android.internal.R.dimen.config_minScrollbarTouchTarget);          mPagingTouchSlop = mTouchSlop * 2;          mDoubleTapTouchSlop = mTouchSlop;          mHandwritingGestureLineMargin = res.getDimensionPixelSize( -                R.dimen.config_viewConfigurationHandwritingGestureLineMargin); +                com.android.internal.R.dimen.config_viewConfigurationHandwritingGestureLineMargin); -        mMinimumFlingVelocity = res.getDimensionPixelSize(R.dimen.config_viewMinFlingVelocity); -        mMaximumFlingVelocity = res.getDimensionPixelSize(R.dimen.config_viewMaxFlingVelocity); +        mMinimumFlingVelocity = res.getDimensionPixelSize( +                com.android.internal.R.dimen.config_viewMinFlingVelocity); +        mMaximumFlingVelocity = res.getDimensionPixelSize( +                com.android.internal.R.dimen.config_viewMaxFlingVelocity);          int configMinRotaryEncoderFlingVelocity = res.getDimensionPixelSize( -                R.dimen.config_viewMinRotaryEncoderFlingVelocity); +                com.android.internal.R.dimen.config_viewMinRotaryEncoderFlingVelocity);          int configMaxRotaryEncoderFlingVelocity = res.getDimensionPixelSize( -                R.dimen.config_viewMaxRotaryEncoderFlingVelocity); +                com.android.internal.R.dimen.config_viewMaxRotaryEncoderFlingVelocity);          if (configMinRotaryEncoderFlingVelocity < 0 || configMaxRotaryEncoderFlingVelocity < 0) {              mMinimumRotaryEncoderFlingVelocity = NO_FLING_MIN_VELOCITY;              mMaximumRotaryEncoderFlingVelocity = NO_FLING_MAX_VELOCITY; @@ -545,7 +551,8 @@ public class ViewConfiguration {          }          int configRotaryEncoderHapticScrollFeedbackTickIntervalPixels = -                res.getDimensionPixelSize(R.dimen +                res.getDimensionPixelSize( +                        com.android.internal.R.dimen                                  .config_rotaryEncoderAxisScrollTickInterval);          mRotaryEncoderHapticScrollFeedbackTickIntervalPixels =                  configRotaryEncoderHapticScrollFeedbackTickIntervalPixels > 0 @@ -553,31 +560,41 @@ public class ViewConfiguration {                          : NO_HAPTIC_SCROLL_TICK_INTERVAL;          mRotaryEncoderHapticScrollFeedbackEnabled = -                res.getBoolean(R.bool +                res.getBoolean( +                        com.android.internal.R.bool                                  .config_viewRotaryEncoderHapticScrollFedbackEnabled); -        mGlobalActionsKeyTimeout = res.getInteger(R.integer.config_globalActionsKeyTimeout); +        mGlobalActionsKeyTimeout = res.getInteger( +                com.android.internal.R.integer.config_globalActionsKeyTimeout); -        mHorizontalScrollFactor = res.getDimensionPixelSize(R.dimen.config_horizontalScrollFactor); -        mVerticalScrollFactor = res.getDimensionPixelSize(R.dimen.config_verticalScrollFactor); +        mHorizontalScrollFactor = res.getDimensionPixelSize( +                com.android.internal.R.dimen.config_horizontalScrollFactor); +        mVerticalScrollFactor = res.getDimensionPixelSize( +                com.android.internal.R.dimen.config_verticalScrollFactor);          mShowMenuShortcutsWhenKeyboardPresent = res.getBoolean( -                R.bool.config_showMenuShortcutsWhenKeyboardPresent); +            com.android.internal.R.bool.config_showMenuShortcutsWhenKeyboardPresent); -        mMinScalingSpan = res.getDimensionPixelSize(R.dimen.config_minScalingSpan); +        mMinScalingSpan = res.getDimensionPixelSize( +                com.android.internal.R.dimen.config_minScalingSpan); -        mScreenshotChordKeyTimeout = res.getInteger(R.integer.config_screenshotChordKeyTimeout); +        mScreenshotChordKeyTimeout = res.getInteger( +                com.android.internal.R.integer.config_screenshotChordKeyTimeout);          mSmartSelectionInitializedTimeout = res.getInteger( -                R.integer.config_smartSelectionInitializedTimeoutMillis); +                com.android.internal.R.integer.config_smartSelectionInitializedTimeoutMillis);          mSmartSelectionInitializingTimeout = res.getInteger( -                R.integer.config_smartSelectionInitializingTimeoutMillis); -        mPreferKeepClearForFocusEnabled = res.getBoolean(R.bool.config_preferKeepClearForFocus); +                com.android.internal.R.integer.config_smartSelectionInitializingTimeoutMillis); +        mPreferKeepClearForFocusEnabled = res.getBoolean( +                com.android.internal.R.bool.config_preferKeepClearForFocus);          mViewBasedRotaryEncoderScrollHapticsEnabledConfig = -                res.getBoolean(R.bool.config_viewBasedRotaryEncoderHapticsEnabled); +                res.getBoolean( +                        com.android.internal.R.bool.config_viewBasedRotaryEncoderHapticsEnabled);          mViewTouchScreenHapticScrollFeedbackEnabled =                  Flags.enableScrollFeedbackForTouch() -                        ? res.getBoolean(R.bool.config_viewTouchScreenHapticScrollFeedbackEnabled) +                        ? res.getBoolean( +                        com.android.internal.R.bool +                                .config_viewTouchScreenHapticScrollFeedbackEnabled)                          : false;      } @@ -615,7 +632,6 @@ public class ViewConfiguration {      @VisibleForTesting      public static void resetCacheForTesting() {          sConfigurations.clear(); -        sResourceCache = new ResourceCache();      }      /** @@ -691,7 +707,7 @@ public class ViewConfiguration {       * components.       */      public static int getPressedStateDuration() { -        return sResourceCache.getPressedStateDuration(); +        return PRESSED_STATE_DURATION;      }      /** @@ -736,7 +752,7 @@ public class ViewConfiguration {       * considered to be a tap.       */      public static int getTapTimeout() { -        return sResourceCache.getTapTimeout(); +        return TAP_TIMEOUT;      }      /** @@ -745,7 +761,7 @@ public class ViewConfiguration {       * considered to be a tap.       */      public static int getJumpTapTimeout() { -        return sResourceCache.getJumpTapTimeout(); +        return JUMP_TAP_TIMEOUT;      }      /** @@ -754,7 +770,7 @@ public class ViewConfiguration {       * double-tap.       */      public static int getDoubleTapTimeout() { -        return sResourceCache.getDoubleTapTimeout(); +        return DOUBLE_TAP_TIMEOUT;      }      /** @@ -766,7 +782,7 @@ public class ViewConfiguration {       */      @UnsupportedAppUsage      public static int getDoubleTapMinTime() { -        return sResourceCache.getDoubleTapMinTime(); +        return DOUBLE_TAP_MIN_TIME;      }      /** @@ -776,7 +792,7 @@ public class ViewConfiguration {       * @hide       */      public static int getHoverTapTimeout() { -        return sResourceCache.getHoverTapTimeout(); +        return HOVER_TAP_TIMEOUT;      }      /** @@ -787,7 +803,7 @@ public class ViewConfiguration {       */      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)      public static int getHoverTapSlop() { -        return sResourceCache.getHoverTapSlop(); +        return HOVER_TAP_SLOP;      }      /** @@ -1028,7 +1044,7 @@ public class ViewConfiguration {       * in milliseconds.       */      public static long getZoomControlsTimeout() { -        return sResourceCache.getZoomControlsTimeout(); +        return ZOOM_CONTROLS_TIMEOUT;      }      /** @@ -1097,14 +1113,14 @@ public class ViewConfiguration {       *         friction.       */      public static float getScrollFriction() { -        return sResourceCache.getScrollFriction(); +        return SCROLL_FRICTION;      }      /**       * @return the default duration in milliseconds for {@link ActionMode#hide(long)}.       */      public static long getDefaultActionModeHideDuration() { -        return sResourceCache.getDefaultActionModeHideDuration(); +        return ACTION_MODE_HIDE_DURATION_DEFAULT;      }      /** @@ -1455,137 +1471,8 @@ public class ViewConfiguration {          return HOVER_TOOLTIP_HIDE_SHORT_TIMEOUT;      } -    private static int getDisplayDensity(Context context) { +    private static final 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/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index d413ba0b042c..09c6dc0e2b20 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -572,6 +572,13 @@ flag {  }  flag { +    name: "enable_display_reconnect_interaction" +    namespace: "lse_desktop_experience" +    description: "Enables new interaction that occurs when a display is reconnected." +    bug: "365873835" +} + +flag {      name: "show_desktop_experience_dev_option"      namespace: "lse_desktop_experience"      description: "Replace the freeform windowing dev options with a desktop experience one." diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index a5e166b95177..5f1a641945e8 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -35,8 +35,20 @@ import android.telephony.SignalStrength;  interface IBatteryStats {      /** @hide */ +    const int RESULT_OK = 0; + +    /** @hide */ +    const int RESULT_RUNTIME_EXCEPTION = 1; + +    /** @hide */ +    const int RESULT_SECURITY_EXCEPTION = 2; + +    /** @hide */      const String KEY_UID_SNAPSHOTS = "uid_snapshots"; +    /** @hide */ +    const String KEY_EXCEPTION_MESSAGE = "exception"; +      // These first methods are also called by native code, so must      // be kept in sync with frameworks/native/libs/binder/include_batterystats/batterystats/IBatteryStats.h      @EnforcePermission("UPDATE_DEVICE_STATS") diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 270cf085b06f..e20a52b24485 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -231,6 +231,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind      private int mLastRightInset = 0;      @UnsupportedAppUsage      private int mLastLeftInset = 0; +    private WindowInsets mLastInsets = null;      private boolean mLastHasTopStableInset = false;      private boolean mLastHasBottomStableInset = false;      private boolean mLastHasRightStableInset = false; @@ -1100,6 +1101,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind              mLastWindowFlags = attrs.flags;              if (insets != null) { +                mLastInsets = insets;                  mLastForceConsumingTypes = insets.getForceConsumingTypes();                  mLastForceConsumingOpaqueCaptionBar = insets.isForceConsumingOpaqueCaptionBar(); @@ -1176,6 +1178,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind                      mForceWindowDrawsBarBackgrounds, requestedVisibleTypes);          } +        int consumingTypes = 0;          // When we expand the window with FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS or          // mForceWindowDrawsBarBackgrounds, we still need to ensure that the rest of the view          // hierarchy doesn't notice it, unless they've explicitly asked for it. @@ -1186,43 +1189,47 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind          //          // Note: Once the app uses the R+ Window.setDecorFitsSystemWindows(false) API we no longer          // consume insets because they might no longer set SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION. -        boolean hideNavigation = (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0 +        final boolean hideNavigation = (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0                  || (requestedVisibleTypes & WindowInsets.Type.navigationBars()) == 0; -        boolean decorFitsSystemWindows = mWindow.mDecorFitsSystemWindows; -        boolean forceConsumingNavBar = +        final boolean decorFitsSystemWindows = mWindow.mDecorFitsSystemWindows; + +        final boolean fitsNavBar = +                (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0 +                        && decorFitsSystemWindows +                        && !hideNavigation; +        final boolean forceConsumingNavBar =                  ((mForceWindowDrawsBarBackgrounds || mDrawLegacyNavigationBarBackgroundHandled)                          && (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0 -                        && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0 -                        && decorFitsSystemWindows -                        && !hideNavigation) +                        && fitsNavBar)                  || ((mLastForceConsumingTypes & WindowInsets.Type.navigationBars()) != 0                          && hideNavigation); - -        boolean consumingNavBar = -                ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 -                        && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0 -                        && decorFitsSystemWindows -                        && !hideNavigation) +        final boolean consumingNavBar = +                ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 && fitsNavBar)                  || forceConsumingNavBar; +        if (consumingNavBar) { +            consumingTypes |= WindowInsets.Type.navigationBars(); +        } -        // If we didn't request fullscreen layout, but we still got it because of the -        // mForceWindowDrawsBarBackgrounds flag, also consume top inset. +        // If the fullscreen layout was not requested, but still received because of the +        // mForceWindowDrawsBarBackgrounds flag, also consume status bar.          // If we should always consume system bars, only consume that if the app wanted to go to          // fullscreen, as otherwise we can expect the app to handle it. -        boolean fullscreen = (sysUiVisibility & SYSTEM_UI_FLAG_FULLSCREEN) != 0 +        final boolean fullscreen = (sysUiVisibility & SYSTEM_UI_FLAG_FULLSCREEN) != 0                  || (attrs.flags & FLAG_FULLSCREEN) != 0;          final boolean hideStatusBar = fullscreen                  || (requestedVisibleTypes & WindowInsets.Type.statusBars()) == 0; -        boolean consumingStatusBar = -                ((sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0 -                        && decorFitsSystemWindows -                        && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0 -                        && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0 -                        && mForceWindowDrawsBarBackgrounds -                        && mLastTopInset != 0) +        if (((sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0 +                && decorFitsSystemWindows +                && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0 +                && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0 +                && mForceWindowDrawsBarBackgrounds +                && mLastTopInset != 0)                  || ((mLastForceConsumingTypes & WindowInsets.Type.statusBars()) != 0 -                        && hideStatusBar); +                        && hideStatusBar)) { +            consumingTypes |= WindowInsets.Type.statusBars(); +        } +        // Decide if caption bar need to be consumed          final boolean hideCaptionBar = fullscreen                  || (requestedVisibleTypes & WindowInsets.Type.captionBar()) == 0;          final boolean consumingCaptionBar = @@ -1237,22 +1244,23 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind                          && mLastForceConsumingOpaqueCaptionBar                          && isOpaqueCaptionBar; -        final int consumedTop = -                (consumingStatusBar || consumingCaptionBar || consumingOpaqueCaptionBar) -                        ? mLastTopInset : 0; -        int consumedRight = consumingNavBar ? mLastRightInset : 0; -        int consumedBottom = consumingNavBar ? mLastBottomInset : 0; -        int consumedLeft = consumingNavBar ? mLastLeftInset : 0; +        if (consumingCaptionBar || consumingOpaqueCaptionBar) { +            consumingTypes |= WindowInsets.Type.captionBar(); +        } + +        final Insets consumedInsets = mLastInsets != null +                ? mLastInsets.getInsets(consumingTypes) : Insets.NONE;          if (mContentRoot != null                  && mContentRoot.getLayoutParams() instanceof MarginLayoutParams) {              MarginLayoutParams lp = (MarginLayoutParams) mContentRoot.getLayoutParams(); -            if (lp.topMargin != consumedTop || lp.rightMargin != consumedRight -                    || lp.bottomMargin != consumedBottom || lp.leftMargin != consumedLeft) { -                lp.topMargin = consumedTop; -                lp.rightMargin = consumedRight; -                lp.bottomMargin = consumedBottom; -                lp.leftMargin = consumedLeft; +            if (lp.topMargin != consumedInsets.top || lp.rightMargin != consumedInsets.right +                    || lp.bottomMargin != consumedInsets.bottom || lp.leftMargin != +                    consumedInsets.left) { +                lp.topMargin = consumedInsets.top; +                lp.rightMargin = consumedInsets.right; +                lp.bottomMargin = consumedInsets.bottom; +                lp.leftMargin = consumedInsets.left;                  mContentRoot.setLayoutParams(lp);                  if (insets == null) { @@ -1261,11 +1269,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind                      requestApplyInsets();                  }              } -            if (insets != null && (consumedLeft > 0 -                    || consumedTop > 0 -                    || consumedRight > 0 -                    || consumedBottom > 0)) { -                insets = insets.inset(consumedLeft, consumedTop, consumedRight, consumedBottom); +            if (insets != null && !Insets.NONE.equals(consumedInsets)) { +                insets = insets.inset(consumedInsets);              }          } diff --git a/core/res/res/values-w225dp/dimens.xml b/core/res/res/values-w225dp/dimens.xml new file mode 100644 index 000000000000..0cd3293f0894 --- /dev/null +++ b/core/res/res/values-w225dp/dimens.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +  ~ Copyright 2025 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 +  ~ +  ~      https://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. +  --> +<resources> +    <!-- The width of the round scrollbar --> +    <dimen name="round_scrollbar_width">6dp</dimen> +</resources> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 37e553ea3827..b3581d98face 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3061,43 +3061,6 @@           {@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> @@ -3299,6 +3262,14 @@           as private. {@see android.view.Display#FLAG_PRIVATE} -->      <integer-array translatable="false" name="config_localPrivateDisplayPorts"></integer-array> +    <!-- Controls if local secondary displays should be able to steal focus and become top display. +         Value specified in the array represents physical port address of each display and displays +         in this list due to flag dependencies will be marked with the following flags: +         {@see android.view.Display#FLAG_STEAL_TOP_FOCUS_DISABLED} +         {@see android.view.Display#FLAG_OWN_FOCUS} --> +    <integer-array translatable="false" name="config_localNotStealTopFocusDisplayPorts"> +    </integer-array> +      <!-- The default mode for the default display. One of the following values (See Display.java):               0 - COLOR_MODE_DEFAULT               7 - COLOR_MODE_SRGB diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 484e8ef1e049..595160ec9f66 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -782,6 +782,9 @@           aliasing effects). This is only used on circular displays. -->      <dimen name="circular_display_mask_thickness">1px</dimen> +    <!-- The width of the round scrollbar --> +    <dimen name="round_scrollbar_width">5dp</dimen> +      <dimen name="lock_pattern_dot_line_width">22dp</dimen>      <dimen name="lock_pattern_dot_size">14dp</dimen>      <dimen name="lock_pattern_dot_size_activated">30dp</dimen> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index a34fbb89c629..9393aa4b6086 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -430,6 +430,7 @@    <java-symbol type="bool" name="config_enableProximityService" />    <java-symbol type="bool" name="config_enableVirtualDeviceManager" />    <java-symbol type="array" name="config_localPrivateDisplayPorts" /> +  <java-symbol type="array" name="config_localNotStealTopFocusDisplayPorts" />    <java-symbol type="integer" name="config_defaultDisplayDefaultColorMode" />    <java-symbol type="bool" name="config_enableAppWidgetService" />    <java-symbol type="dimen" name="config_pictureInPictureMinAspectRatio" /> @@ -585,6 +586,7 @@    <java-symbol type="dimen" name="accessibility_magnification_indicator_width" />    <java-symbol type="dimen" name="circular_display_mask_thickness" />    <java-symbol type="dimen" name="user_icon_size" /> +  <java-symbol type="dimen" name="round_scrollbar_width" />    <java-symbol type="string" name="add_account_button_label" />    <java-symbol type="string" name="addToDictionary" /> @@ -4153,17 +4155,6 @@    <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/bubbles/DropTargetManager.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt new file mode 100644 index 000000000000..29ce8d90e66f --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.shared.bubbles + +/** + * Manages animating drop targets in response to dragging bubble icons or bubble expanded views + * across different drag zones. + */ +class DropTargetManager( +    private val isLayoutRtl: Boolean, +    private val dragZoneChangedListener: DragZoneChangedListener +) { + +    private var state: DragState? = null + +    /** Must be called when a drag gesture is starting. */ +    fun onDragStarted(draggedObject: DraggedObject, dragZones: List<DragZone>) { +        val state = DragState(dragZones, draggedObject) +        dragZoneChangedListener.onInitialDragZoneSet(state.initialDragZone) +        this.state = state +    } + +    /** Called when the user drags to a new location. */ +    fun onDragUpdated(x: Int, y: Int) { +        val state = state ?: return +        val oldDragZone = state.currentDragZone +        val newDragZone = state.getMatchingDragZone(x = x, y = y) +        state.currentDragZone = newDragZone +        if (oldDragZone != newDragZone) { +            dragZoneChangedListener.onDragZoneChanged(from = oldDragZone, to = newDragZone) +        } +    } + +    /** Called when the drag ended. */ +    fun onDragEnded() { +        state = null +    } + +    /** Stores the current drag state. */ +    private inner class DragState( +        private val dragZones: List<DragZone>, +        draggedObject: DraggedObject +    ) { +        val initialDragZone = +            if (draggedObject.initialLocation.isOnLeft(isLayoutRtl)) { +                dragZones.filterIsInstance<DragZone.Bubble.Left>().first() +            } else { +                dragZones.filterIsInstance<DragZone.Bubble.Right>().first() +            } +        var currentDragZone: DragZone = initialDragZone + +        fun getMatchingDragZone(x: Int, y: Int): DragZone { +            return dragZones.firstOrNull { it.contains(x, y) } ?: currentDragZone +        } +    } + +    /** An interface to be notified when drag zones change. */ +    interface DragZoneChangedListener { +        /** An initial drag zone was set. Called when a drag starts. */ +        fun onInitialDragZoneSet(dragZone: DragZone) +        /** Called when the object was dragged to a different drag zone. */ +        fun onDragZoneChanged(from: DragZone, to: DragZone) +    } +} 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..7491abd4248b 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 @@ -343,10 +343,22 @@ class DesktopTasksController(              DesktopModeFlags.INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC                  .isTrue() && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue()          ) { +            logV( +                "isDesktopModeShowing: hasVisibleTasks=%s hasTopTransparentFullscreenTask=%s hasMinimizedPip=%s", +                hasVisibleTasks, +                hasTopTransparentFullscreenTask, +                hasMinimizedPip, +            )              return hasVisibleTasks || hasTopTransparentFullscreenTask || hasMinimizedPip          } else if (Flags.enableDesktopWindowingPip()) { +            logV( +                "isDesktopModeShowing: hasVisibleTasks=%s hasMinimizedPip=%s", +                hasVisibleTasks, +                hasMinimizedPip, +            )              return hasVisibleTasks || hasMinimizedPip          } +        logV("isDesktopModeShowing: hasVisibleTasks=%s", hasVisibleTasks)          return hasVisibleTasks      } @@ -3074,6 +3086,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 +3095,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/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index 8ad2e1d3c7c9..ae8f8c4eff79 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -317,7 +317,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,                      "RecentsTransitionHandler.mergeAnimation: no controller found");              return;          } -        controller.merge(info, startT, mergeTarget, finishCallback); +        controller.merge(info, startT, finishT, mergeTarget, finishCallback);      }      @Override @@ -912,7 +912,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,           * before any unhandled transitions.           */          @SuppressLint("NewApi") -        void merge(TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget, +        void merge(TransitionInfo info, SurfaceControl.Transaction startT, +                SurfaceControl.Transaction finishT, IBinder mergeTarget,                  Transitions.TransitionFinishCallback finishCallback) {              if (mFinishCB == null) {                  ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, @@ -1072,8 +1073,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,                      Slog.e(TAG, "Returning to recents without closing any opening tasks.");                  }                  // Setup may hide it initially since it doesn't know that overview was still active. -                t.show(recentsOpening.getLeash()); -                t.setAlpha(recentsOpening.getLeash(), 1.f); +                startT.show(recentsOpening.getLeash()); +                startT.setAlpha(recentsOpening.getLeash(), 1.f);                  mState = STATE_NORMAL;              }              boolean didMergeThings = false; @@ -1142,31 +1143,31 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,                          mOpeningTasks.add(pausingTask);                          // Setup hides opening tasks initially, so make it visible again (since we                          // are already showing it). -                        t.show(change.getLeash()); -                        t.setAlpha(change.getLeash(), 1.f); +                        startT.show(change.getLeash()); +                        startT.setAlpha(change.getLeash(), 1.f);                      } else if (isLeaf) {                          // We are receiving new opening leaf tasks, so convert to onTasksAppeared.                          final RemoteAnimationTarget target = TransitionUtil.newTarget( -                                change, layer, info, t, mLeashMap); +                                change, layer, info, startT, mLeashMap);                          appearedTargets[nextTargetIdx++] = target;                          // reparent into the original `mInfo` since that's where we are animating.                          final TransitionInfo.Root root = TransitionUtil.getRootFor(change, mInfo);                          final boolean wasClosing = closingIdx >= 0; -                        t.reparent(target.leash, root.getLeash()); -                        t.setPosition(target.leash, +                        startT.reparent(target.leash, root.getLeash()); +                        startT.setPosition(target.leash,                                  change.getStartAbsBounds().left - root.getOffset().x,                                  change.getStartAbsBounds().top - root.getOffset().y); -                        t.setLayer(target.leash, layer); +                        startT.setLayer(target.leash, layer);                          if (wasClosing) {                              // App was previously visible and is closing -                            t.show(target.leash); -                            t.setAlpha(target.leash, 1f); +                            startT.show(target.leash); +                            startT.setAlpha(target.leash, 1f);                              // Also override the task alpha as it was set earlier when dispatching                              // the transition and setting up the leash to hide the -                            t.setAlpha(change.getLeash(), 1f); +                            startT.setAlpha(change.getLeash(), 1f);                          } else {                              // Hide the animation leash, let the listener show it -                            t.hide(target.leash); +                            startT.hide(target.leash);                          }                          ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,                                  "  opening new leaf taskId=%d wasClosing=%b", @@ -1175,10 +1176,10 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,                      } else {                          ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,                                  "  opening new taskId=%d", change.getTaskInfo().taskId); -                        t.setLayer(change.getLeash(), layer); +                        startT.setLayer(change.getLeash(), layer);                          // Setup hides opening tasks initially, so make it visible since recents                          // is only animating the leafs. -                        t.show(change.getLeash()); +                        startT.show(change.getLeash());                          mOpeningTasks.add(new TaskState(change, null));                      }                  } @@ -1194,7 +1195,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,                  // Activity only transition, so consume the merge as it doesn't affect the rest of                  // recents.                  Slog.d(TAG, "Got an activity only transition during recents, so apply directly"); -                mergeActivityOnly(info, t); +                mergeActivityOnly(info, startT);              } else if (!didMergeThings) {                  // Didn't recognize anything in incoming transition so don't merge it.                  Slog.w(TAG, "Don't know how to merge this transition, foundRecentsClosing=" @@ -1206,7 +1207,10 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,                  return;              }              // At this point, we are accepting the merge. -            t.apply(); +            startT.apply(); +            // Since we're accepting the merge, update the finish transaction so that changes via +            // that transaction will be applied on top of those of the merged transitions +            mFinishTransaction = finishT;              // not using the incoming anim-only surfaces              info.releaseAnimSurfaces();              if (appearedTargets != null) { 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/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt new file mode 100644 index 000000000000..efb91c5fbfda --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.shared.bubbles + +import android.graphics.Rect +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import kotlin.test.assertFails + +/** Unit tests for [DropTargetManager]. */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class DropTargetManagerTest { + +    private lateinit var dropTargetManager: DropTargetManager +    private lateinit var dragZoneChangedListener: FakeDragZoneChangedListener +    private val dropTarget = Rect(0, 0, 0, 0) + +    // create 3 drop zones that are horizontally next to each other +    // ------------------------------------------------- +    // |               |               |               | +    // |    bubble     |               |    bubble     | +    // |               |    dismiss    |               | +    // |     left      |               |     right     | +    // |               |               |               | +    // ------------------------------------------------- +    private val bubbleLeftDragZone = +        DragZone.Bubble.Left(bounds = Rect(0, 0, 100, 100), dropTarget = dropTarget) +    private val dismissDragZone = DragZone.Dismiss(bounds = Rect(100, 0, 200, 100)) +    private val bubbleRightDragZone = +        DragZone.Bubble.Right(bounds = Rect(200, 0, 300, 100), dropTarget = dropTarget) + +    @Before +    fun setUp() { +        dragZoneChangedListener = FakeDragZoneChangedListener() +        dropTargetManager = DropTargetManager(isLayoutRtl = false, dragZoneChangedListener) +    } + +    @Test +    fun onDragStarted_notifiesInitialDragZone() { +        dropTargetManager.onDragStarted( +            DraggedObject.Bubble(BubbleBarLocation.LEFT), +            listOf(bubbleLeftDragZone, bubbleRightDragZone) +        ) +        assertThat(dragZoneChangedListener.initialDragZone).isEqualTo(bubbleLeftDragZone) +    } + +    @Test +    fun onDragStarted_missingExpectedDragZone_fails() { +        assertFails { +            dropTargetManager.onDragStarted( +                DraggedObject.Bubble(BubbleBarLocation.RIGHT), +                listOf(bubbleLeftDragZone) +            ) +        } +    } + +    @Test +    fun onDragUpdated_notifiesDragZoneChanged() { +        dropTargetManager.onDragStarted( +            DraggedObject.Bubble(BubbleBarLocation.LEFT), +            listOf(bubbleLeftDragZone, bubbleRightDragZone, dismissDragZone) +        ) +        dropTargetManager.onDragUpdated( +            bubbleRightDragZone.bounds.centerX(), +            bubbleRightDragZone.bounds.centerY() +        ) +        assertThat(dragZoneChangedListener.fromDragZone).isEqualTo(bubbleLeftDragZone) +        assertThat(dragZoneChangedListener.toDragZone).isEqualTo(bubbleRightDragZone) + +        dropTargetManager.onDragUpdated( +            dismissDragZone.bounds.centerX(), +            dismissDragZone.bounds.centerY() +        ) +        assertThat(dragZoneChangedListener.fromDragZone).isEqualTo(bubbleRightDragZone) +        assertThat(dragZoneChangedListener.toDragZone).isEqualTo(dismissDragZone) +    } + +    @Test +    fun onDragUpdated_withinSameZone_doesNotNotify() { +        dropTargetManager.onDragStarted( +            DraggedObject.Bubble(BubbleBarLocation.LEFT), +            listOf(bubbleLeftDragZone, bubbleRightDragZone, dismissDragZone) +        ) +        dropTargetManager.onDragUpdated( +            bubbleLeftDragZone.bounds.centerX(), +            bubbleLeftDragZone.bounds.centerY() +        ) +        assertThat(dragZoneChangedListener.fromDragZone).isNull() +        assertThat(dragZoneChangedListener.toDragZone).isNull() +    } + +    @Test +    fun onDragUpdated_outsideAllZones_doesNotNotify() { +        dropTargetManager.onDragStarted( +            DraggedObject.Bubble(BubbleBarLocation.LEFT), +            listOf(bubbleLeftDragZone, bubbleRightDragZone) +        ) +        val pointX = 200 +        val pointY = 200 +        assertThat(bubbleLeftDragZone.contains(pointX, pointY)).isFalse() +        assertThat(bubbleRightDragZone.contains(pointX, pointY)).isFalse() +        dropTargetManager.onDragUpdated(pointX, pointY) +        assertThat(dragZoneChangedListener.fromDragZone).isNull() +        assertThat(dragZoneChangedListener.toDragZone).isNull() +    } + +    @Test +    fun onDragUpdated_hasOverlappingZones_notifiesFirstDragZoneChanged() { +        // create a drag zone that spans across the width of all 3 drag zones, but extends below +        // them +        val splitDragZone = DragZone.Split.Left(bounds = Rect(0, 0, 300, 200)) +        dropTargetManager.onDragStarted( +            DraggedObject.Bubble(BubbleBarLocation.LEFT), +            listOf(bubbleLeftDragZone, bubbleRightDragZone, dismissDragZone, splitDragZone) +        ) + +        // drag to a point that is within both the bubble right zone and split zone +        val (pointX, pointY) = +            Pair( +                bubbleRightDragZone.bounds.centerX(), +                bubbleRightDragZone.bounds.centerY() +            ) +        assertThat(splitDragZone.contains(pointX, pointY)).isTrue() +        dropTargetManager.onDragUpdated(pointX, pointY) +        // verify we dragged to the bubble right zone because that has higher priority than split +        assertThat(dragZoneChangedListener.fromDragZone).isEqualTo(bubbleLeftDragZone) +        assertThat(dragZoneChangedListener.toDragZone).isEqualTo(bubbleRightDragZone) + +        dropTargetManager.onDragUpdated( +            bubbleRightDragZone.bounds.centerX(), +            150 // below the bubble and dismiss drag zones but within split +        ) +        assertThat(dragZoneChangedListener.fromDragZone).isEqualTo(bubbleRightDragZone) +        assertThat(dragZoneChangedListener.toDragZone).isEqualTo(splitDragZone) + +        val (dismissPointX, dismissPointY) = +            Pair(dismissDragZone.bounds.centerX(), dismissDragZone.bounds.centerY()) +        assertThat(splitDragZone.contains(dismissPointX, dismissPointY)).isTrue() +        dropTargetManager.onDragUpdated(dismissPointX, dismissPointY) +        assertThat(dragZoneChangedListener.fromDragZone).isEqualTo(splitDragZone) +        assertThat(dragZoneChangedListener.toDragZone).isEqualTo(dismissDragZone) +    } + +    @Test +    fun onDragUpdated_afterDragEnded_doesNotNotify() { +        dropTargetManager.onDragStarted( +            DraggedObject.Bubble(BubbleBarLocation.LEFT), +            listOf(bubbleLeftDragZone, bubbleRightDragZone, dismissDragZone) +        ) +        dropTargetManager.onDragEnded() +        dropTargetManager.onDragUpdated( +            bubbleRightDragZone.bounds.centerX(), +            bubbleRightDragZone.bounds.centerY() +        ) +        assertThat(dragZoneChangedListener.fromDragZone).isNull() +        assertThat(dragZoneChangedListener.toDragZone).isNull() +    } + +    private class FakeDragZoneChangedListener : DropTargetManager.DragZoneChangedListener { +        var initialDragZone: DragZone? = null +        var fromDragZone: DragZone? = null +        var toDragZone: DragZone? = null + +        override fun onInitialDragZoneSet(dragZone: DragZone) { +            initialDragZone = dragZone +        } +        override fun onDragZoneChanged(from: DragZone, to: DragZone) { +            fromDragZone = from +            toDragZone = to +        } +    } +} 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/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java b/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java index e173c5e996df..0f6a2a082e0c 100644 --- a/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java +++ b/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java @@ -118,6 +118,7 @@ public class SettingsSpinnerPreference extends Preference          spinner.setAdapter(mAdapter);          spinner.setSelection(mPosition);          spinner.setOnItemSelectedListener(mOnSelectedListener); +        spinner.setLongClickable(false);          if (mShouldPerformClick) {              mShouldPerformClick = false;              // To show dropdown view. diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt index 0c7d6f093289..b173db0a0505 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt @@ -37,7 +37,7 @@ object BluetoothLeBroadcastMetadataExt {      private const val KEY_BT_ADVERTISER_ADDRESS = "AD"      private const val KEY_BT_BROADCAST_ID = "BI"      private const val KEY_BT_BROADCAST_CODE = "BC" -    private const val KEY_BT_STREAM_METADATA = "MD" +    private const val KEY_BT_PUBLIC_METADATA = "PM"      private const val KEY_BT_STANDARD_QUALITY = "SQ"      private const val KEY_BT_HIGH_QUALITY = "HQ" @@ -84,7 +84,7 @@ object BluetoothLeBroadcastMetadataExt {          }          if (this.publicBroadcastMetadata != null &&                  this.publicBroadcastMetadata?.rawMetadata?.size != 0) { -            entries.add(Pair(KEY_BT_STREAM_METADATA, Base64.encodeToString( +            entries.add(Pair(KEY_BT_PUBLIC_METADATA, Base64.encodeToString(                  this.publicBroadcastMetadata?.rawMetadata, Base64.NO_WRAP)))          }          if ((this.audioConfigQuality and @@ -160,7 +160,7 @@ object BluetoothLeBroadcastMetadataExt {          var sourceAdvertiserSid = -1          var broadcastId = -1          var broadcastName: String? = null -        var streamMetadata: BluetoothLeAudioContentMetadata? = null +        var publicMetadata: BluetoothLeAudioContentMetadata? = null          var paSyncInterval = -1          var broadcastCode: ByteArray? = null          var audioConfigQualityStandard = -1 @@ -207,11 +207,11 @@ object BluetoothLeBroadcastMetadataExt {                      broadcastCode = Base64.decode(value.dropLastWhile { it.equals(0.toByte()) }                              .toByteArray(), Base64.NO_WRAP)                  } -                KEY_BT_STREAM_METADATA -> { -                    require(streamMetadata == null) { -                        "Duplicate streamMetadata $input" +                KEY_BT_PUBLIC_METADATA -> { +                    require(publicMetadata == null) { +                        "Duplicate publicMetadata $input"                      } -                    streamMetadata = BluetoothLeAudioContentMetadata +                    publicMetadata = BluetoothLeAudioContentMetadata                          .fromRawBytes(Base64.decode(value, Base64.NO_WRAP))                  }                  KEY_BT_STANDARD_QUALITY -> { @@ -256,7 +256,7 @@ object BluetoothLeBroadcastMetadataExt {          Log.d(TAG, "parseQrCodeToMetadata: main data elements sourceAddrType=$sourceAddrType, " +                  "sourceAddr=$sourceAddrString, sourceAdvertiserSid=$sourceAdvertiserSid, " +                  "broadcastId=$broadcastId, broadcastName=$broadcastName, " + -                "streamMetadata=${streamMetadata != null}, " + +                "publicMetadata=${publicMetadata != null}, " +                  "paSyncInterval=$paSyncInterval, " +                  "broadcastCode=${broadcastCode?.toString(Charsets.UTF_8)}, " +                  "audioConfigQualityStandard=$audioConfigQualityStandard, " + @@ -317,7 +317,7 @@ object BluetoothLeBroadcastMetadataExt {              setBroadcastName(broadcastName)              // QR code should set PBP(public broadcast profile) for auracast              setPublicBroadcast(true) -            setPublicBroadcastMetadata(streamMetadata) +            setPublicBroadcastMetadata(publicMetadata)              setPaSyncInterval(paSyncInterval)              setEncrypted(broadcastCode != null)              setBroadcastCode(broadcastCode) diff --git a/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt b/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt index 1ad20dc02042..5f6eb5e49573 100644 --- a/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt +++ b/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt @@ -233,7 +233,7 @@ class BluetoothLeBroadcastMetadataExtTest {          const val QR_CODE_STRING =              "BLUETOOTH:UUID:184F;BN:VGVzdA==;AT:1;AD:00A1A1A1A1A1;BI:1E240;BC:VGVzdENvZGU=;" + -            "MD:BgNwVGVzdA==;AS:1;PI:A0;NS:1;BS:3;NB:2;SM:BQNUZXN0BARlbmc=;;" +            "PM:BgNwVGVzdA==;AS:1;PI:A0;NS:1;BS:3;NB:2;SM:BQNUZXN0BARlbmc=;;"          const val QR_CODE_STRING_NON_ENCRYPTED =              "BLUETOOTH:UUID:184F;BN:SG9ja2V5;AT:0;AD:AABBCC001122;BI:DE51E9;SQ:1;AS:1;PI:FFFF;" +              "NS:1;BS:1;NB:1;;" diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 744388f47d0e..19806e7cdf64 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -207,6 +207,8 @@ filegroup {          "tests/src/**/systemui/statusbar/notification/row/NotificationConversationInfoTest.java",          "tests/src/**/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt",          "tests/src/**/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt", +        "tests/src/**/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifierTest.java", +        "tests/src/**/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifierDisabledTest.java",          "tests/src/**/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java",          "tests/src/**/systemui/statusbar/phone/CentralSurfacesImplTest.java",          "tests/src/**/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java", @@ -538,6 +540,7 @@ android_library {      kotlincflags: [          "-Xjvm-default=all",          "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", +        "-P plugin:androidx.compose.compiler.plugins.kotlin:sourceInformation=true",      ],      plugins: [ @@ -552,6 +555,11 @@ android_library {      },  } +platform_compat_config { +    name: "SystemUI-core-compat-config", +    src: ":SystemUI-core", +} +  filegroup {      name: "AAA-src",      srcs: ["tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java"], @@ -754,6 +762,7 @@ android_library {          "kosmos",          "testables",          "androidx.test.rules", +        "platform-compat-test-rules",      ],      libs: [          "android.test.runner.stubs.system", @@ -888,6 +897,7 @@ android_robolectric_test {      static_libs: [          "RoboTestLibraries",          "androidx.compose.runtime_runtime", +        "platform-compat-test-rules",      ],      libs: [          "android.test.runner.impl", 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/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 3c0480d150e0..418a7a52a97e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -1705,15 +1705,38 @@ private fun Umo(      contentScope: ContentScope?,      modifier: Modifier = Modifier,  ) { -    if (SceneContainerFlag.isEnabled && contentScope != null) { -        contentScope.MediaCarousel( -            modifier = modifier.fillMaxSize(), -            isVisible = true, -            mediaHost = viewModel.mediaHost, -            carouselController = viewModel.mediaCarouselController, -        ) -    } else { -        UmoLegacy(viewModel, modifier) +    val showNextActionLabel = stringResource(R.string.accessibility_action_label_umo_show_next) +    val showPreviousActionLabel = +        stringResource(R.string.accessibility_action_label_umo_show_previous) + +    Box( +        modifier = +            modifier.thenIf(!viewModel.isEditMode) { +                Modifier.semantics { +                    customActions = +                        listOf( +                            CustomAccessibilityAction(showNextActionLabel) { +                                viewModel.onShowNextMedia() +                                true +                            }, +                            CustomAccessibilityAction(showPreviousActionLabel) { +                                viewModel.onShowPreviousMedia() +                                true +                            }, +                        ) +                } +            } +    ) { +        if (SceneContainerFlag.isEnabled && contentScope != null) { +            contentScope.MediaCarousel( +                modifier = modifier.fillMaxSize(), +                isVisible = true, +                mediaHost = viewModel.mediaHost, +                carouselController = viewModel.mediaCarouselController, +            ) +        } else { +            UmoLegacy(viewModel, modifier) +        }      }  } @@ -1724,7 +1747,7 @@ private fun UmoLegacy(viewModel: BaseCommunalViewModel, modifier: Modifier = Mod              modifier                  .clip(                      shape = -                        RoundedCornerShape(dimensionResource(system_app_widget_background_radius)) +                        RoundedCornerShape(dimensionResource(R.dimen.notification_corner_radius))                  )                  .background(MaterialTheme.colorScheme.primary)                  .pointerInput(Unit) { 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/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt index 85155157eda2..433894b58350 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt @@ -78,6 +78,7 @@ import com.android.systemui.kosmos.testScope  import com.android.systemui.log.logcatLogBuffer  import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager  import com.android.systemui.media.controls.ui.controller.mediaCarouselController +import com.android.systemui.media.controls.ui.view.MediaCarouselScrollHandler  import com.android.systemui.media.controls.ui.view.MediaHost  import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest  import com.android.systemui.power.domain.interactor.powerInteractor @@ -120,6 +121,7 @@ import platform.test.runner.parameterized.Parameters  @RunWith(ParameterizedAndroidJunit4::class)  class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {      @Mock private lateinit var mediaHost: MediaHost +    @Mock private lateinit var mediaCarouselScrollHandler: MediaCarouselScrollHandler      @Mock private lateinit var metricsLogger: CommunalMetricsLogger      private val kosmos = testKosmos() @@ -161,6 +163,8 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {          kosmos.fakeUserTracker.set(userInfos = listOf(MAIN_USER_INFO), selectedUserIndex = 0)          whenever(mediaHost.visible).thenReturn(true) +        whenever(kosmos.mediaCarouselController.mediaCarouselScrollHandler) +            .thenReturn(mediaCarouselScrollHandler)          kosmos.powerInteractor.setAwakeForTest() @@ -903,6 +907,20 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {          }      @Test +    fun onShowPreviousMedia_scrollHandler_isCalled() = +        testScope.runTest { +            underTest.onShowPreviousMedia() +            verify(mediaCarouselScrollHandler).scrollByStep(-1) +        } + +    @Test +    fun onShowNextMedia_scrollHandler_isCalled() = +        testScope.runTest { +            underTest.onShowNextMedia() +            verify(mediaCarouselScrollHandler).scrollByStep(1) +        } + +    @Test      @EnableFlags(FLAG_BOUNCER_UI_REVAMP)      fun uiIsBlurred_whenPrimaryBouncerIsShowing() =          testScope.runTest { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt index d073cf1ac9db..46940297e673 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt @@ -16,8 +16,11 @@  package com.android.systemui.media.controls.ui.view +import android.content.res.Resources  import android.testing.TestableLooper  import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup  import androidx.test.ext.junit.runners.AndroidJUnit4  import androidx.test.filters.SmallTest  import com.android.systemui.SysuiTestCase @@ -25,16 +28,19 @@ import com.android.systemui.media.controls.util.MediaUiEventLogger  import com.android.systemui.plugins.FalsingManager  import com.android.systemui.qs.PageIndicator  import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.whenever  import com.android.systemui.util.time.FakeSystemClock  import org.junit.Before  import org.junit.Test  import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyFloat  import org.mockito.Mock  import org.mockito.Mockito.anyInt +import org.mockito.Mockito.eq +import org.mockito.Mockito.never  import org.mockito.Mockito.verify  import org.mockito.MockitoAnnotations +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever  @SmallTest  @TestableLooper.RunWithLooper(setAsMainLooper = true) @@ -42,6 +48,7 @@ import org.mockito.MockitoAnnotations  class MediaCarouselScrollHandlerTest : SysuiTestCase() {      private val carouselWidth = 1038 +    private val settingsButtonWidth = 200      private val motionEventUp = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0f, 0f, 0)      @Mock lateinit var mediaCarousel: MediaScrollView @@ -53,6 +60,9 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() {      @Mock lateinit var falsingManager: FalsingManager      @Mock lateinit var logSmartspaceImpression: (Boolean) -> Unit      @Mock lateinit var logger: MediaUiEventLogger +    @Mock lateinit var contentContainer: ViewGroup +    @Mock lateinit var settingsButton: View +    @Mock lateinit var resources: Resources      lateinit var executor: FakeExecutor      private val clock = FakeSystemClock() @@ -63,6 +73,7 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() {      fun setup() {          MockitoAnnotations.initMocks(this)          executor = FakeExecutor(clock) +        whenever(mediaCarousel.contentContainer).thenReturn(contentContainer)          mediaCarouselScrollHandler =              MediaCarouselScrollHandler(                  mediaCarousel, @@ -74,10 +85,9 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() {                  closeGuts,                  falsingManager,                  logSmartspaceImpression, -                logger +                logger,              )          mediaCarouselScrollHandler.playerWidthPlusPadding = carouselWidth -          whenever(mediaCarousel.touchListener).thenReturn(mediaCarouselScrollHandler.touchListener)      } @@ -128,4 +138,107 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() {          verify(mediaCarousel).smoothScrollTo(eq(0), anyInt())      } + +    @Test +    fun testCarouselScrollByStep_scrollRight() { +        setupMediaContainer(visibleIndex = 0) + +        mediaCarouselScrollHandler.scrollByStep(1) +        clock.advanceTime(DISMISS_DELAY) +        executor.runAllReady() + +        verify(mediaCarousel).smoothScrollTo(eq(carouselWidth), anyInt()) +    } + +    @Test +    fun testCarouselScrollByStep_scrollLeft() { +        setupMediaContainer(visibleIndex = 1) + +        mediaCarouselScrollHandler.scrollByStep(-1) +        clock.advanceTime(DISMISS_DELAY) +        executor.runAllReady() + +        verify(mediaCarousel).smoothScrollTo(eq(0), anyInt()) +    } + +    @Test +    fun testCarouselScrollByStep_scrollRight_alreadyAtEnd() { +        setupMediaContainer(visibleIndex = 1) + +        mediaCarouselScrollHandler.scrollByStep(1) +        clock.advanceTime(DISMISS_DELAY) +        executor.runAllReady() + +        verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt()) +        verify(mediaCarousel).animationTargetX = eq(-settingsButtonWidth.toFloat()) +    } + +    @Test +    fun testCarouselScrollByStep_scrollLeft_alreadyAtStart() { +        setupMediaContainer(visibleIndex = 0) + +        mediaCarouselScrollHandler.scrollByStep(-1) +        clock.advanceTime(DISMISS_DELAY) +        executor.runAllReady() + +        verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt()) +        verify(mediaCarousel).animationTargetX = eq(settingsButtonWidth.toFloat()) +    } + +    @Test +    fun testCarouselScrollByStep_scrollLeft_alreadyAtStart_isRTL() { +        setupMediaContainer(visibleIndex = 0) +        whenever(mediaCarousel.isLayoutRtl).thenReturn(true) + +        mediaCarouselScrollHandler.scrollByStep(-1) +        clock.advanceTime(DISMISS_DELAY) +        executor.runAllReady() + +        verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt()) +        verify(mediaCarousel).animationTargetX = eq(-settingsButtonWidth.toFloat()) +    } + +    @Test +    fun testCarouselScrollByStep_scrollRight_alreadyAtEnd_isRTL() { +        setupMediaContainer(visibleIndex = 1) +        whenever(mediaCarousel.isLayoutRtl).thenReturn(true) + +        mediaCarouselScrollHandler.scrollByStep(1) +        clock.advanceTime(DISMISS_DELAY) +        executor.runAllReady() + +        verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt()) +        verify(mediaCarousel).animationTargetX = eq(settingsButtonWidth.toFloat()) +    } + +    @Test +    fun testScrollByStep_noScroll_notDismissible() { +        setupMediaContainer(visibleIndex = 1, showsSettingsButton = false) + +        mediaCarouselScrollHandler.scrollByStep(1) +        clock.advanceTime(DISMISS_DELAY) +        executor.runAllReady() + +        verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt()) +        verify(mediaCarousel, never()).animationTargetX = anyFloat() +    } + +    private fun setupMediaContainer(visibleIndex: Int, showsSettingsButton: Boolean = true) { +        whenever(contentContainer.childCount).thenReturn(2) +        val child1: View = mock() +        val child2: View = mock() +        whenever(child1.left).thenReturn(0) +        whenever(child2.left).thenReturn(carouselWidth) +        whenever(contentContainer.getChildAt(0)).thenReturn(child1) +        whenever(contentContainer.getChildAt(1)).thenReturn(child2) + +        whenever(settingsButton.width).thenReturn(settingsButtonWidth) +        whenever(settingsButton.context).thenReturn(context) +        whenever(settingsButton.resources).thenReturn(resources) +        whenever(settingsButton.resources.getDimensionPixelSize(anyInt())).thenReturn(20) +        mediaCarouselScrollHandler.onSettingsButtonUpdated(settingsButton) + +        mediaCarouselScrollHandler.visibleMediaIndex = visibleIndex +        mediaCarouselScrollHandler.showsSettingsButton = showsSettingsButton +    }  } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt index 8eea2a8e6121..048028cdc0fa 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt @@ -21,52 +21,44 @@ import androidx.test.ext.junit.runners.AndroidJUnit4  import androidx.test.filters.SmallTest  import com.android.systemui.SysuiTestCase  import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.power.data.repository.FakePowerRepository -import com.android.systemui.power.domain.interactor.PowerInteractorFactory -import com.android.systemui.statusbar.LockscreenShadeTransitionController -import com.android.systemui.statusbar.phone.ScreenOffAnimationController -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.whenever +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testCase +import com.android.systemui.plugins.statusbar.statusBarStateController +import com.android.systemui.power.data.repository.fakePowerRepository +import com.android.systemui.statusbar.lockscreenShadeTransitionController +import com.android.systemui.statusbar.phone.screenOffAnimationController  import com.google.common.truth.Truth.assertThat  import kotlinx.coroutines.test.runTest  import org.junit.Test  import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.eq  import org.mockito.Mockito.isNull  import org.mockito.Mockito.verify +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever  @RunWith(AndroidJUnit4::class)  @SmallTest  class NotificationShelfInteractorTest : SysuiTestCase() { -    private val keyguardRepository = FakeKeyguardRepository() -    private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository() - -    private val screenOffAnimationController = -        mock<ScreenOffAnimationController>().also { -            whenever(it.allowWakeUpIfDozing()).thenReturn(true) +    private val kosmos = +        Kosmos().apply { +            testCase = this@NotificationShelfInteractorTest +            lockscreenShadeTransitionController = mock() +            screenOffAnimationController = mock() +            statusBarStateController = mock() +            whenever(screenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true)          } -    private val statusBarStateController: StatusBarStateController = mock() -    private val powerRepository = FakePowerRepository() -    private val powerInteractor = -        PowerInteractorFactory.create( -                repository = powerRepository, -                screenOffAnimationController = screenOffAnimationController, -                statusBarStateController = statusBarStateController, -            ) -            .powerInteractor - -    private val keyguardTransitionController: LockscreenShadeTransitionController = mock() -    private val underTest = -        NotificationShelfInteractor( -            keyguardRepository, -            deviceEntryFaceAuthRepository, -            powerInteractor, -            keyguardTransitionController, -        ) +    private val underTest = kosmos.notificationShelfInteractor + +    private val keyguardRepository = kosmos.fakeKeyguardRepository +    private val deviceEntryFaceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository + +    private val statusBarStateController = kosmos.statusBarStateController +    private val powerRepository = kosmos.fakePowerRepository +    private val keyguardTransitionController = kosmos.lockscreenShadeTransitionController      @Test      fun shelfIsNotStatic_whenKeyguardNotShowing() = runTest { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt index e2fb3ba11a02..d570f18e35d8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt @@ -19,73 +19,53 @@ package com.android.systemui.statusbar.notification.shelf.ui.viewmodel  import android.os.PowerManager  import androidx.test.ext.junit.runners.AndroidJUnit4  import androidx.test.filters.SmallTest -import com.android.systemui.SysUITestComponent -import com.android.systemui.SysUITestModule  import com.android.systemui.SysuiTestCase -import com.android.systemui.TestMocksModule -import com.android.systemui.collectLastValue -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.power.data.repository.FakePowerRepository -import com.android.systemui.runTest -import com.android.systemui.statusbar.LockscreenShadeTransitionController -import com.android.systemui.statusbar.SysuiStatusBarStateController -import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule -import com.android.systemui.statusbar.phone.ScreenOffAnimationController -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.whenever +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.testCase +import com.android.systemui.plugins.statusbar.statusBarStateController +import com.android.systemui.power.data.repository.fakePowerRepository +import com.android.systemui.shade.domain.interactor.enableDualShade +import com.android.systemui.shade.domain.interactor.enableSingleShade +import com.android.systemui.shade.domain.interactor.enableSplitShade +import com.android.systemui.statusbar.lockscreenShadeTransitionController +import com.android.systemui.statusbar.phone.screenOffAnimationController  import com.google.common.truth.Truth.assertThat -import dagger.BindsInstance -import dagger.Component +import kotlinx.coroutines.test.runTest  import org.junit.Test  import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.eq  import org.mockito.Mockito  import org.mockito.Mockito.verify +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever  @RunWith(AndroidJUnit4::class)  @SmallTest  class NotificationShelfViewModelTest : SysuiTestCase() { -    @Component(modules = [SysUITestModule::class, ActivatableNotificationViewModelModule::class]) -    @SysUISingleton -    interface TestComponent : SysUITestComponent<NotificationShelfViewModel> { - -        val deviceEntryFaceAuthRepository: FakeDeviceEntryFaceAuthRepository -        val keyguardRepository: FakeKeyguardRepository -        val powerRepository: FakePowerRepository - -        @Component.Factory -        interface Factory { -            fun create( -                @BindsInstance test: SysuiTestCase, -                mocks: TestMocksModule, -            ): TestComponent +    private val kosmos = +        Kosmos().apply { +            testCase = this@NotificationShelfViewModelTest +            lockscreenShadeTransitionController = mock() +            screenOffAnimationController = mock() +            statusBarStateController = mock() +            whenever(screenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true)          } -    } - -    private val keyguardTransitionController: LockscreenShadeTransitionController = mock() -    private val screenOffAnimationController: ScreenOffAnimationController = mock { -        whenever(allowWakeUpIfDozing()).thenReturn(true) -    } -    private val statusBarStateController: SysuiStatusBarStateController = mock() - -    private val testComponent: TestComponent = -        DaggerNotificationShelfViewModelTest_TestComponent.factory() -            .create( -                test = this, -                mocks = -                    TestMocksModule( -                        lockscreenShadeTransitionController = keyguardTransitionController, -                        screenOffAnimationController = screenOffAnimationController, -                        statusBarStateController = statusBarStateController, -                    ) -            ) +    private val underTest = kosmos.notificationShelfViewModel +    private val deviceEntryFaceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository +    private val keyguardRepository = kosmos.fakeKeyguardRepository +    private val keyguardTransitionController = kosmos.lockscreenShadeTransitionController +    private val powerRepository = kosmos.fakePowerRepository      @Test      fun canModifyColorOfNotifications_whenKeyguardNotShowing() = -        testComponent.runTest { +        kosmos.runTest {              val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)              keyguardRepository.setKeyguardShowing(false) @@ -95,7 +75,7 @@ class NotificationShelfViewModelTest : SysuiTestCase() {      @Test      fun canModifyColorOfNotifications_whenKeyguardShowingAndNotBypass() = -        testComponent.runTest { +        kosmos.runTest {              val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)              keyguardRepository.setKeyguardShowing(true) @@ -106,7 +86,7 @@ class NotificationShelfViewModelTest : SysuiTestCase() {      @Test      fun cannotModifyColorOfNotifications_whenBypass() = -        testComponent.runTest { +        kosmos.runTest {              val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)              keyguardRepository.setKeyguardShowing(true) @@ -117,7 +97,7 @@ class NotificationShelfViewModelTest : SysuiTestCase() {      @Test      fun isClickable_whenKeyguardShowing() = -        testComponent.runTest { +        kosmos.runTest {              val isClickable by collectLastValue(underTest.isClickable)              keyguardRepository.setKeyguardShowing(true) @@ -127,7 +107,7 @@ class NotificationShelfViewModelTest : SysuiTestCase() {      @Test      fun isNotClickable_whenKeyguardNotShowing() = -        testComponent.runTest { +        kosmos.runTest {              val isClickable by collectLastValue(underTest.isClickable)              keyguardRepository.setKeyguardShowing(false) @@ -137,7 +117,7 @@ class NotificationShelfViewModelTest : SysuiTestCase() {      @Test      fun onClicked_goesToLockedShade() = -        with(testComponent) { +        kosmos.runTest {              whenever(statusBarStateController.isDozing).thenReturn(true)              underTest.onShelfClicked() @@ -146,4 +126,48 @@ class NotificationShelfViewModelTest : SysuiTestCase() {              assertThat(powerRepository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_GESTURE)              verify(keyguardTransitionController).goToLockedShade(Mockito.isNull(), eq(true))          } + +    @Test +    @EnableSceneContainer +    fun isAlignedToEnd_splitShade_true() = +        kosmos.runTest { +            val isShelfAlignedToEnd by collectLastValue(underTest.isAlignedToEnd) + +            kosmos.enableSplitShade() + +            assertThat(isShelfAlignedToEnd).isTrue() +        } + +    @Test +    @EnableSceneContainer +    fun isAlignedToEnd_singleShade_false() = +        kosmos.runTest { +            val isShelfAlignedToEnd by collectLastValue(underTest.isAlignedToEnd) + +            kosmos.enableSingleShade() + +            assertThat(isShelfAlignedToEnd).isFalse() +        } + +    @Test +    @EnableSceneContainer +    fun isAlignedToEnd_dualShade_wideScreen_false() = +        kosmos.runTest { +            val isShelfAlignedToEnd by collectLastValue(underTest.isAlignedToEnd) + +            kosmos.enableDualShade(wideLayout = true) + +            assertThat(isShelfAlignedToEnd).isFalse() +        } + +    @Test +    @EnableSceneContainer +    fun isAlignedToEnd_dualShade_narrowScreen_false() = +        kosmos.runTest { +            val isShelfAlignedToEnd by collectLastValue(underTest.isAlignedToEnd) + +            kosmos.enableDualShade(wideLayout = false) + +            assertThat(isShelfAlignedToEnd).isFalse() +        }  } 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/res/layout/biometric_prompt_one_pane_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_one_pane_layout.xml index 91cd019c85d1..43808f215a81 100644 --- a/packages/SystemUI/res/layout/biometric_prompt_one_pane_layout.xml +++ b/packages/SystemUI/res/layout/biometric_prompt_one_pane_layout.xml @@ -149,9 +149,9 @@          style="@style/TextAppearance.AuthCredential.Indicator"          android:layout_width="0dp"          android:layout_height="wrap_content" -        android:layout_marginTop="24dp"          android:layout_marginHorizontal="24dp" -        android:accessibilityLiveRegion="assertive" +        android:layout_marginTop="24dp" +        android:accessibilityLiveRegion="polite"          android:fadingEdge="horizontal"          android:gravity="center_horizontal"          android:scrollHorizontally="true" diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index d18a90a17abe..86292039d93d 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1351,6 +1351,10 @@      <string name="accessibility_action_label_shrink_widget">Decrease height</string>      <!-- Label for accessibility action to expand a widget in edit mode. [CHAR LIMIT=NONE] -->      <string name="accessibility_action_label_expand_widget">Increase height</string> +    <!-- Label for accessibility action to show the next media player. [CHAR LIMIT=NONE] --> +    <string name="accessibility_action_label_umo_show_next">Show next</string> +    <!-- Label for accessibility action to show the previous media player. [CHAR LIMIT=NONE] --> +    <string name="accessibility_action_label_umo_show_previous">Show previous</string>      <!-- Title shown above information regarding lock screen widgets. [CHAR LIMIT=50] -->      <string name="communal_widgets_disclaimer_title">Lock screen widgets</string>      <!-- Information about lock screen widgets presented to the user. [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 5ef4d4014ba6..7f2c89346423 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -258,7 +258,7 @@      <style name="TextAppearance.AuthNonBioCredential.Title">          <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>          <item name="android:layout_marginTop">24dp</item> -        <item name="android:textSize">36dp</item> +        <item name="android:textSize">36sp</item>          <item name="android:focusable">true</item>          <item name="android:textColor">@androidprv:color/materialColorOnSurface</item>      </style> 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/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt index 6cd763a9d3d0..bbf9a19012a4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt @@ -31,6 +31,7 @@ import com.airbnb.lottie.LottieAnimationView  import com.airbnb.lottie.LottieComposition  import com.airbnb.lottie.LottieProperty  import com.android.app.animation.Interpolators +import com.android.app.tracing.coroutines.launchTraced as launch  import com.android.keyguard.KeyguardPINView  import com.android.systemui.CoreStartable  import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor @@ -50,7 +51,6 @@ import dagger.Lazy  import javax.inject.Inject  import kotlinx.coroutines.CoroutineScope  import kotlinx.coroutines.flow.combine -import com.android.app.tracing.coroutines.launchTraced as launch  /** Binds the side fingerprint sensor indicator view to [SideFpsOverlayViewModel]. */  @SysUISingleton @@ -65,51 +65,53 @@ constructor(      private val layoutInflater: Lazy<LayoutInflater>,      private val sideFpsProgressBarViewModel: Lazy<SideFpsProgressBarViewModel>,      private val sfpsSensorInteractor: Lazy<SideFpsSensorInteractor>, -    private val windowManager: Lazy<WindowManager> +    private val windowManager: Lazy<WindowManager>,  ) : CoreStartable {      override fun start() { -        applicationScope -            .launch { -                sfpsSensorInteractor.get().isAvailable.collect { isSfpsAvailable -> -                    if (isSfpsAvailable) { -                        combine( -                                biometricStatusInteractor.get().sfpsAuthenticationReason, -                                deviceEntrySideFpsOverlayInteractor -                                    .get() -                                    .showIndicatorForDeviceEntry, -                                sideFpsProgressBarViewModel.get().isVisible, -                                ::Triple +        applicationScope.launch { +            sfpsSensorInteractor.get().isAvailable.collect { isSfpsAvailable -> +                if (isSfpsAvailable) { +                    combine( +                            biometricStatusInteractor.get().sfpsAuthenticationReason, +                            deviceEntrySideFpsOverlayInteractor.get().showIndicatorForDeviceEntry, +                            sideFpsProgressBarViewModel.get().isVisible, +                            ::Triple, +                        ) +                        .sample(displayStateInteractor.get().isInRearDisplayMode, ::Pair) +                        .collect { (combinedFlows, isInRearDisplayMode: Boolean) -> +                            val ( +                                systemServerAuthReason, +                                showIndicatorForDeviceEntry, +                                progressBarIsVisible) = +                                combinedFlows +                            Log.d( +                                TAG, +                                "systemServerAuthReason = $systemServerAuthReason, " + +                                    "showIndicatorForDeviceEntry = " + +                                    "$showIndicatorForDeviceEntry, " + +                                    "progressBarIsVisible = $progressBarIsVisible",                              ) -                            .sample(displayStateInteractor.get().isInRearDisplayMode, ::Pair) -                            .collect { (combinedFlows, isInRearDisplayMode: Boolean) -> -                                val ( -                                    systemServerAuthReason, -                                    showIndicatorForDeviceEntry, -                                    progressBarIsVisible) = -                                    combinedFlows -                                Log.d( -                                    TAG, -                                    "systemServerAuthReason = $systemServerAuthReason, " + -                                        "showIndicatorForDeviceEntry = " + -                                        "$showIndicatorForDeviceEntry, " + -                                        "progressBarIsVisible = $progressBarIsVisible" -                                ) -                                if (!isInRearDisplayMode) { -                                    if (progressBarIsVisible) { -                                        hide() -                                    } else if (systemServerAuthReason != NotRunning) { -                                        show() -                                    } else if (showIndicatorForDeviceEntry) { -                                        show() -                                    } else { -                                        hide() -                                    } +                            if (!isInRearDisplayMode) { +                                if (progressBarIsVisible) { +                                    hide() +                                } else if (systemServerAuthReason != NotRunning) { +                                    show() +                                } else if (showIndicatorForDeviceEntry) { +                                    show() +                                    overlayView?.announceForAccessibility( +                                        applicationContext.resources.getString( +                                            R.string.accessibility_side_fingerprint_indicator_label +                                        ) +                                    ) +                                } else { +                                    hide()                                  }                              } -                    } +                        }                  }              } +        }      }      private var overlayView: View? = null @@ -119,7 +121,7 @@ constructor(          if (overlayView?.isAttachedToWindow == true) {              Log.d(                  TAG, -                "show(): overlayView $overlayView isAttachedToWindow already, ignoring show request" +                "show(): overlayView $overlayView isAttachedToWindow already, ignoring show request",              )              return          } @@ -137,11 +139,6 @@ constructor(          overlayView!!.visibility = View.INVISIBLE          Log.d(TAG, "show(): adding overlayView $overlayView")          windowManager.get().addView(overlayView, overlayViewModel.defaultOverlayViewParams) -        overlayView!!.announceForAccessibility( -            applicationContext.resources.getString( -                R.string.accessibility_side_fingerprint_indicator_label -            ) -        )      }      /** Hide the side fingerprint sensor indicator */ @@ -163,7 +160,7 @@ constructor(          fun bind(              overlayView: View,              viewModel: SideFpsOverlayViewModel, -            windowManager: WindowManager +            windowManager: WindowManager,          ) {              overlayView.repeatWhenAttached {                  val lottie = it.requireViewById<LottieAnimationView>(R.id.sidefps_animation) @@ -186,7 +183,7 @@ constructor(                      object : View.AccessibilityDelegate() {                          override fun dispatchPopulateAccessibilityEvent(                              host: View, -                            event: AccessibilityEvent +                            event: AccessibilityEvent,                          ): Boolean {                              return if (                                  event.getEventType() === diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt index 49003a735fbd..a4860dfc47ce 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt @@ -202,6 +202,12 @@ abstract class BaseCommunalViewModel(      /** Called as the user request to show the customize widget button. */      open fun onLongClick() {} +    /** Called as the user requests to switch to the previous player in UMO. */ +    open fun onShowPreviousMedia() {} + +    /** Called as the user requests to switch to the next player in UMO. */ +    open fun onShowNextMedia() {} +      /** Called as the UI determines that a new widget has been added to the grid. */      open fun onNewWidgetAdded(provider: AppWidgetProviderInfo) {} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt index 4bc44005d2fc..dd4018a9d7b9 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt @@ -254,6 +254,14 @@ constructor(          }      } +    override fun onShowPreviousMedia() { +        mediaCarouselController.mediaCarouselScrollHandler.scrollByStep(-1) +    } + +    override fun onShowNextMedia() { +        mediaCarouselController.mediaCarouselScrollHandler.scrollByStep(1) +    } +      override fun onTapWidget(componentName: ComponentName, rank: Int) {          metricsLogger.logTapWidget(componentName.flattenToString(), rank)      } 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/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/media/controls/ui/view/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt index d63c2e07b94f..0107a5278e3e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt @@ -23,11 +23,11 @@ import android.view.MotionEvent  import android.view.View  import android.view.ViewGroup  import android.view.ViewOutlineProvider +import androidx.annotation.VisibleForTesting  import androidx.core.view.GestureDetectorCompat  import androidx.dynamicanimation.animation.FloatPropertyCompat  import androidx.dynamicanimation.animation.SpringForce  import com.android.app.tracing.TraceStateLogger -import com.android.internal.annotations.VisibleForTesting  import com.android.settingslib.Utils  import com.android.systemui.Gefingerpoken  import com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS @@ -38,9 +38,10 @@ import com.android.systemui.res.R  import com.android.systemui.util.animation.TransitionLayout  import com.android.systemui.util.concurrency.DelayableExecutor  import com.android.wm.shell.shared.animation.PhysicsAnimator +import kotlin.math.sign  private const val FLING_SLOP = 1000000 -private const val DISMISS_DELAY = 100L +@VisibleForTesting const val DISMISS_DELAY = 100L  private const val SCROLL_DELAY = 100L  private const val RUBBERBAND_FACTOR = 0.2f  private const val SETTINGS_BUTTON_TRANSLATION_FRACTION = 0.3f @@ -64,7 +65,7 @@ class MediaCarouselScrollHandler(      private val closeGuts: (immediate: Boolean) -> Unit,      private val falsingManager: FalsingManager,      private val logSmartspaceImpression: (Boolean) -> Unit, -    private val logger: MediaUiEventLogger +    private val logger: MediaUiEventLogger,  ) {      /** Trace state logger for media carousel visibility */      private val visibleStateLogger = TraceStateLogger("$TAG#visibleToUser") @@ -96,7 +97,7 @@ class MediaCarouselScrollHandler(      /** What's the currently visible player index? */      var visibleMediaIndex: Int = 0 -        private set +        @VisibleForTesting set      /** How much are we scrolled into the current media? */      private var scrollIntoCurrentMedia: Int = 0 @@ -137,14 +138,14 @@ class MediaCarouselScrollHandler(                  eStart: MotionEvent?,                  eCurrent: MotionEvent,                  vX: Float, -                vY: Float +                vY: Float,              ) = onFling(vX, vY)              override fun onScroll(                  down: MotionEvent?,                  lastMotion: MotionEvent,                  distanceX: Float, -                distanceY: Float +                distanceY: Float,              ) = onScroll(down!!, lastMotion, distanceX)              override fun onDown(e: MotionEvent): Boolean { @@ -157,6 +158,7 @@ class MediaCarouselScrollHandler(      val touchListener =          object : Gefingerpoken {              override fun onTouchEvent(motionEvent: MotionEvent?) = onTouch(motionEvent!!) +              override fun onInterceptTouchEvent(ev: MotionEvent?) = onInterceptTouch(ev!!)          } @@ -168,7 +170,7 @@ class MediaCarouselScrollHandler(                  scrollX: Int,                  scrollY: Int,                  oldScrollX: Int, -                oldScrollY: Int +                oldScrollY: Int,              ) {                  if (playerWidthPlusPadding == 0) {                      return @@ -177,7 +179,7 @@ class MediaCarouselScrollHandler(                  val relativeScrollX = scrollView.relativeScrollX                  onMediaScrollingChanged(                      relativeScrollX / playerWidthPlusPadding, -                    relativeScrollX % playerWidthPlusPadding +                    relativeScrollX % playerWidthPlusPadding,                  )              }          } @@ -209,7 +211,7 @@ class MediaCarouselScrollHandler(                          0,                          carouselWidth,                          carouselHeight, -                        cornerRadius.toFloat() +                        cornerRadius.toFloat(),                      )                  }              } @@ -235,7 +237,7 @@ class MediaCarouselScrollHandler(                      getMaxTranslation().toFloat(),                      0.0f,                      1.0f, -                    Math.abs(contentTranslation) +                    Math.abs(contentTranslation),                  )              val settingsTranslation =                  (1.0f - settingsOffset) * @@ -323,7 +325,7 @@ class MediaCarouselScrollHandler(                          CONTENT_TRANSLATION,                          newTranslation,                          startVelocity = 0.0f, -                        config = translationConfig +                        config = translationConfig,                      )                      .start()                  scrollView.animationTargetX = newTranslation @@ -391,7 +393,7 @@ class MediaCarouselScrollHandler(                          CONTENT_TRANSLATION,                          newTranslation,                          startVelocity = 0.0f, -                        config = translationConfig +                        config = translationConfig,                      )                      .start()              } else { @@ -430,7 +432,7 @@ class MediaCarouselScrollHandler(                      CONTENT_TRANSLATION,                      newTranslation,                      startVelocity = vX, -                    config = translationConfig +                    config = translationConfig,                  )                  .start()              scrollView.animationTargetX = newTranslation @@ -583,10 +585,35 @@ class MediaCarouselScrollHandler(          // We need to post this to wait for the active player becomes visible.          mainExecutor.executeDelayed(              { scrollView.smoothScrollTo(view.left, scrollView.scrollY) }, -            SCROLL_DELAY +            SCROLL_DELAY,          )      } +    /** +     * Scrolls the media carousel by the number of players specified by [step]. If scrolling beyond +     * the carousel's bounds: +     * - If the carousel is not dismissible, the settings button is displayed. +     * - If the carousel is dismissible, no action taken. +     * +     * @param step A positive number means next, and negative means previous. +     */ +    fun scrollByStep(step: Int) { +        val destIndex = visibleMediaIndex + step +        if (destIndex >= mediaContent.childCount || destIndex < 0) { +            if (!showsSettingsButton) return +            var translation = getMaxTranslation() * sign(-step.toFloat()) +            translation = if (isRtl) -translation else translation +            PhysicsAnimator.getInstance(this) +                .spring(CONTENT_TRANSLATION, translation, config = translationConfig) +                .start() +            scrollView.animationTargetX = translation +        } else if (scrollView.getContentTranslation() != 0.0f) { +            resetTranslation(true) +        } else { +            scrollToPlayer(destIndex = destIndex) +        } +    } +      companion object {          private val CONTENT_TRANSLATION =              object : FloatPropertyCompat<MediaCarouselScrollHandler>("contentTranslation") { 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/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 155049f512d8..31fdec6147f2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -93,6 +93,7 @@ public class NotificationShelf extends ActivatableNotificationView {      private int mPaddingBetweenElements;      private int mNotGoneIndex;      private boolean mHasItemsInStableShelf; +    private boolean mAlignedToEnd;      private int mScrollFastThreshold;      private boolean mInteractive;      private boolean mAnimationsEnabled = true; @@ -412,8 +413,22 @@ public class NotificationShelf extends ActivatableNotificationView {      public boolean isAlignedToEnd() {          if (!NotificationMinimalism.isEnabled()) {              return false; +        } else if (SceneContainerFlag.isEnabled()) { +            return mAlignedToEnd; +        } else { +            return mAmbientState.getUseSplitShade(); +        } +    } + +    /** @see #isAlignedToEnd() */ +    public void setAlignedToEnd(boolean alignedToEnd) { +        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) { +            return; +        } +        if (mAlignedToEnd != alignedToEnd) { +            mAlignedToEnd = alignedToEnd; +            requestLayout();          } -        return mAmbientState.getUseSplitShade();      }      @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 09cc3f23032e..9dc651ed507a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -643,6 +643,10 @@ public final class NotificationEntry extends ListEntry {          return row.isMediaRow();      } +    public boolean containsCustomViews() { +        return getSbn().getNotification().containsCustomViews(); +    } +      public void resetUserExpansion() {          if (row != null) row.resetUserExpansion();      } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt index 6491223e6e10..f9e9bee4d809 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt @@ -12,7 +12,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow  import com.android.systemui.util.children  /** Walks view hiearchy of a given notification to estimate its memory use. */ -internal object NotificationMemoryViewWalker { +object NotificationMemoryViewWalker {      private const val TAG = "NotificationMemory" @@ -26,9 +26,13 @@ internal object NotificationMemoryViewWalker {          private var softwareBitmaps = 0          fun addSmallIcon(smallIconUse: Int) = apply { smallIcon += smallIconUse } +          fun addLargeIcon(largeIconUse: Int) = apply { largeIcon += largeIconUse } +          fun addSystem(systemIconUse: Int) = apply { systemIcons += systemIconUse } +          fun addStyle(styleUse: Int) = apply { style += styleUse } +          fun addSoftwareBitmapPenalty(softwareBitmapUse: Int) = apply {              softwareBitmaps += softwareBitmapUse          } @@ -67,14 +71,14 @@ internal object NotificationMemoryViewWalker {                      getViewUsage(ViewType.PRIVATE_EXPANDED_VIEW, row.privateLayout?.expandedChild),                      getViewUsage(                          ViewType.PRIVATE_CONTRACTED_VIEW, -                        row.privateLayout?.contractedChild +                        row.privateLayout?.contractedChild,                      ),                      getViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, row.privateLayout?.headsUpChild),                      getViewUsage(                          ViewType.PUBLIC_VIEW,                          row.publicLayout?.expandedChild,                          row.publicLayout?.contractedChild, -                        row.publicLayout?.headsUpChild +                        row.publicLayout?.headsUpChild,                      ),                  )                  .filterNotNull() @@ -107,14 +111,14 @@ internal object NotificationMemoryViewWalker {              row.publicLayout?.expandedChild,              row.publicLayout?.contractedChild,              row.publicLayout?.headsUpChild, -            seenObjects = seenObjects +            seenObjects = seenObjects,          )      }      private fun getViewUsage(          type: ViewType,          vararg rootViews: View?, -        seenObjects: HashSet<Int> = hashSetOf() +        seenObjects: HashSet<Int> = hashSetOf(),      ): NotificationViewUsage? {          val usageBuilder = lazy { UsageBuilder() }          rootViews.forEach { rootView -> 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/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index c7e15fdb98c7..73e8246907aa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -901,6 +901,13 @@ public class NotificationContentInflater implements NotificationRowContentBinder          if (!satisfiesMinHeightRequirement(view, entry, resources)) {              return "inflated notification does not meet minimum height requirement";          } + +        if (NotificationCustomContentMemoryVerifier.requiresImageViewMemorySizeCheck(entry)) { +            if (!NotificationCustomContentMemoryVerifier.satisfiesMemoryLimits(view, entry)) { +                return "inflated notification does not meet maximum memory size requirement"; +            } +        } +          return null;      } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentCompat.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentCompat.java new file mode 100644 index 000000000000..c55cb6725e45 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentCompat.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2025 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.notification.row; + +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; +import android.os.Build; + +/** + * Holds compat {@link ChangeId} for {@link NotificationCustomContentMemoryVerifier}. + */ +final class NotificationCustomContentCompat { +    /** +     * Enables memory size checking of custom views included in notifications to ensure that +     * they conform to the size limit set in `config_notificationStripRemoteViewSizeBytes` +     * config.xml parameter. +     * Notifications exceeding the size will be rejected. +     */ +    @ChangeId +    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.BAKLAVA) +    public static final long CHECK_SIZE_OF_INFLATED_CUSTOM_VIEWS = 270553691L; +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifier.kt new file mode 100644 index 000000000000..a3e6a5cddc94 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifier.kt @@ -0,0 +1,175 @@ +/* + * 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.notification.row + +import android.app.compat.CompatChanges +import android.content.Context +import android.graphics.drawable.AdaptiveIconDrawable +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import android.os.Build +import android.util.Log +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import androidx.annotation.VisibleForTesting +import com.android.app.tracing.traceSection +import com.android.systemui.statusbar.notification.collection.NotificationEntry + +/** Checks whether Notifications with Custom content views conform to configured memory limits. */ +object NotificationCustomContentMemoryVerifier { + +    private const val NOTIFICATION_SERVICE_TAG = "NotificationService" + +    /** Notifications with custom views need to conform to maximum memory consumption. */ +    @JvmStatic +    fun requiresImageViewMemorySizeCheck(entry: NotificationEntry): Boolean { +        if (!com.android.server.notification.Flags.notificationCustomViewUriRestriction()) { +            return false +        } + +        return entry.containsCustomViews() +    } + +    /** +     * This walks the custom view hierarchy contained in the passed Notification view and determines +     * if the total memory consumption of all image views satisfies the limit set by +     * [getStripViewSizeLimit]. It will also log to logcat if the limit exceeds +     * [getWarnViewSizeLimit]. +     * +     * @return true if the Notification conforms to the view size limits. +     */ +    @JvmStatic +    fun satisfiesMemoryLimits(view: View, entry: NotificationEntry): Boolean { +        val mainColumnView = +            view.findViewById<View>(com.android.internal.R.id.notification_main_column) +        if (mainColumnView == null) { +            Log.wtf( +                NOTIFICATION_SERVICE_TAG, +                "R.id.notification_main_column view should not be null!", +            ) +            return true +        } + +        val memorySize = +            traceSection("computeViewHiearchyImageViewSize") { +                computeViewHierarchyImageViewSize(view) +            } + +        if (memorySize > getStripViewSizeLimit(view.context)) { +            val stripOversizedView = isCompatChangeEnabledForUid(entry.sbn.uid) +            if (stripOversizedView) { +                Log.w( +                    NOTIFICATION_SERVICE_TAG, +                    "Dropped notification due to too large RemoteViews ($memorySize bytes) on " + +                        "pkg: ${entry.sbn.packageName} tag: ${entry.sbn.tag} id: ${entry.sbn.id}", +                ) +            } else { +                Log.w( +                    NOTIFICATION_SERVICE_TAG, +                    "RemoteViews too large on pkg: ${entry.sbn.packageName} " + +                        "tag: ${entry.sbn.tag} id: ${entry.sbn.id} " + +                        "this WILL notification WILL be dropped when targetSdk " + +                        "is set to ${Build.VERSION_CODES.BAKLAVA}!", +                ) +            } + +            // We still warn for size, but return "satisfies = ok" if the target SDK +            // is too low. +            return !stripOversizedView +        } + +        if (memorySize > getWarnViewSizeLimit(view.context)) { +            // We emit the same warning as NotificationManagerService does to keep some consistency +            // for developers. +            Log.w( +                NOTIFICATION_SERVICE_TAG, +                "RemoteViews too large on pkg: ${entry.sbn.packageName} " + +                    "tag: ${entry.sbn.tag} id: ${entry.sbn.id} " + +                    "this notifications might be dropped in a future release", +            ) +        } +        return true +    } + +    private fun isCompatChangeEnabledForUid(uid: Int): Boolean = +        try { +            CompatChanges.isChangeEnabled( +                NotificationCustomContentCompat.CHECK_SIZE_OF_INFLATED_CUSTOM_VIEWS, +                uid, +            ) +        } catch (e: RuntimeException) { +            Log.wtf(NOTIFICATION_SERVICE_TAG, "Failed to contact system_server for compat change.") +            false +        } + +    @VisibleForTesting +    @JvmStatic +    fun computeViewHierarchyImageViewSize(view: View): Int = +        when (view) { +            is ViewGroup -> { +                var use = 0 +                for (i in 0 until view.childCount) { +                    use += computeViewHierarchyImageViewSize(view.getChildAt(i)) +                } +                use +            } +            is ImageView -> computeImageViewSize(view) +            else -> 0 +        } + +    /** +     * Returns the memory size of a Bitmap contained in a passed [ImageView] in bytes. If the view +     * contains any other kind of drawable, the memory size is estimated from its intrinsic +     * dimensions. +     * +     * @return Bitmap size in bytes or 0 if no drawable is set. +     */ +    private fun computeImageViewSize(view: ImageView): Int { +        val drawable = view.drawable +        return computeDrawableSize(drawable) +    } + +    private fun computeDrawableSize(drawable: Drawable?): Int { +        return when (drawable) { +            null -> 0 +            is AdaptiveIconDrawable -> +                computeDrawableSize(drawable.foreground) + +                    computeDrawableSize(drawable.background) + +                    computeDrawableSize(drawable.monochrome) +            is BitmapDrawable -> drawable.bitmap.allocationByteCount +            // People can sneak large drawables into those custom memory views via resources - +            // we use the intrisic size as a proxy for how much memory rendering those will +            // take. +            else -> drawable.intrinsicWidth * drawable.intrinsicHeight * 4 +        } +    } + +    /** @return Size of remote views after which a size warning is logged. */ +    @VisibleForTesting +    fun getWarnViewSizeLimit(context: Context): Int = +        context.resources.getInteger( +            com.android.internal.R.integer.config_notificationWarnRemoteViewSizeBytes +        ) + +    /** @return Size of remote views after which the notification is dropped. */ +    @VisibleForTesting +    fun getStripViewSizeLimit(context: Context): Int = +        context.resources.getInteger( +            com.android.internal.R.integer.config_notificationStripRemoteViewSizeBytes +        ) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt index 20c3464536e9..589e5b8be240 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt @@ -1396,9 +1396,17 @@ constructor(           */          @VisibleForTesting          fun isValidView(view: View, entry: NotificationEntry, resources: Resources): String? { -            return if (!satisfiesMinHeightRequirement(view, entry, resources)) { -                "inflated notification does not meet minimum height requirement" -            } else null +            if (!satisfiesMinHeightRequirement(view, entry, resources)) { +                return "inflated notification does not meet minimum height requirement" +            } + +            if (NotificationCustomContentMemoryVerifier.requiresImageViewMemorySizeCheck(entry)) { +                if (!NotificationCustomContentMemoryVerifier.satisfiesMemoryLimits(view, entry)) { +                    return "inflated notification does not meet maximum memory size requirement" +                } +            } + +            return null          }          private fun satisfiesMinHeightRequirement( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt index 9fdd0bcc4ee9..0703f2de250d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt @@ -21,11 +21,14 @@ import com.android.systemui.dagger.SysUISingleton  import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository  import com.android.systemui.keyguard.data.repository.KeyguardRepository  import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.shade.domain.interactor.ShadeModeInteractor +import com.android.systemui.shade.shared.model.ShadeMode  import com.android.systemui.statusbar.LockscreenShadeTransitionController  import com.android.systemui.statusbar.NotificationShelf  import javax.inject.Inject  import kotlinx.coroutines.flow.Flow  import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map  /** Interactor for the [NotificationShelf] */  @SysUISingleton @@ -35,6 +38,7 @@ constructor(      private val keyguardRepository: KeyguardRepository,      private val deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository,      private val powerInteractor: PowerInteractor, +    private val shadeModeInteractor: ShadeModeInteractor,      private val keyguardTransitionController: LockscreenShadeTransitionController,  ) {      /** Is the shelf showing on the keyguard? */ @@ -51,6 +55,16 @@ constructor(                  isKeyguardShowing && isBypassEnabled              } +    /** Should the shelf be aligned to the end in the current configuration? */ +    val isAlignedToEnd: Flow<Boolean> +        get() = +            shadeModeInteractor.shadeMode.map { shadeMode -> +                when (shadeMode) { +                    ShadeMode.Split -> true +                    else -> false +                } +            } +      /** Transition keyguard to the locked shade, triggered by the shelf. */      fun goToLockedShadeFromShelf() {          powerInteractor.wakeUpIfDozing("SHADE_CLICK", PowerManager.WAKE_REASON_GESTURE) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt index 0352a304a5c1..f663ea019319 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt @@ -16,15 +16,16 @@  package com.android.systemui.statusbar.notification.shelf.ui.viewbinder +import com.android.app.tracing.coroutines.launchTraced as launch  import com.android.app.tracing.traceSection  import com.android.systemui.plugins.FalsingManager +import com.android.systemui.scene.shared.flag.SceneContainerFlag  import com.android.systemui.statusbar.NotificationShelf  import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerShelfViewBinder  import com.android.systemui.statusbar.notification.row.ui.viewbinder.ActivatableNotificationViewBinder  import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel  import kotlinx.coroutines.awaitCancellation  import kotlinx.coroutines.coroutineScope -import com.android.app.tracing.coroutines.launchTraced as launch  /** Binds a [NotificationShelf] to its [view model][NotificationShelfViewModel]. */  object NotificationShelfViewBinder { @@ -41,6 +42,11 @@ object NotificationShelfViewBinder {                  viewModel.canModifyColorOfNotifications.collect(::setCanModifyColorOfNotifications)              }              launch { viewModel.isClickable.collect(::setCanInteract) } + +            if (SceneContainerFlag.isEnabled) { +                launch { viewModel.isAlignedToEnd.collect(::setAlignedToEnd) } +            } +              registerViewListenersWhileAttached(shelf, viewModel)          }      } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt index 5ca8b53d0704..96cdda6d4a23 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt @@ -17,11 +17,13 @@  package com.android.systemui.statusbar.notification.shelf.ui.viewmodel  import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.scene.shared.flag.SceneContainerFlag  import com.android.systemui.statusbar.NotificationShelf  import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModel  import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor  import javax.inject.Inject  import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf  import kotlinx.coroutines.flow.map  /** ViewModel for [NotificationShelf]. */ @@ -40,6 +42,15 @@ constructor(      val canModifyColorOfNotifications: Flow<Boolean>          get() = interactor.isShelfStatic.map { static -> !static } +    /** Is the shelf aligned to the end in the current configuration? */ +    val isAlignedToEnd: Flow<Boolean> by lazy { +        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) { +            flowOf(false) +        } else { +            interactor.isAlignedToEnd +        } +    } +      /** Notifies that the user has clicked the shelf. */      fun onShelfClicked() {          interactor.goToLockedShadeFromShelf() 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/statusbar/policy/SplitShadeStateController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateController.kt index 72d093c65a91..9f05850f3405 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateController.kt @@ -22,11 +22,11 @@ interface SplitShadeStateController {      /** Returns true if the device should use the split notification shade. */      @Deprecated( -        message = "This is deprecated, please use ShadeInteractor#shadeMode instead", +        message = "This is deprecated, please use ShadeModeInteractor#shadeMode instead",          replaceWith =              ReplaceWith( -                "shadeInteractor.shadeMode", -                "com.android.systemui.shade.domain.interactor.ShadeInteractor", +                "shadeModeInteractor.shadeMode", +                "com.android.systemui.shade.domain.interactor.ShadeModeInteractor",              ),      )      fun shouldUseSplitNotificationShade(resources: Resources): Boolean diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt index 4b01ded16495..f1abf105be8e 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt @@ -25,7 +25,7 @@ import kotlin.coroutines.CoroutineContext  import kotlinx.coroutines.CoroutineDispatcher  /** - * Repository for observing values of [Settings.Secure] for the currently active user. That means + * Repository for observing values of [Settings.System] for the currently active user. That means   * when user is switched and the new user has different value, flow will emit new value.   */  // TODO: b/377244768 - Make internal once call sites inject SystemSettingsRepository instead. 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/res/layout/custom_view_flipper.xml b/packages/SystemUI/tests/res/layout/custom_view_flipper.xml new file mode 100644 index 000000000000..eb3ba82b043b --- /dev/null +++ b/packages/SystemUI/tests/res/layout/custom_view_flipper.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" +    android:orientation="vertical" +    android:layout_width="match_parent" +    android:layout_height="match_parent"> + +    <ViewFlipper +        android:id="@+id/flipper" +        android:layout_width="match_parent" +        android:layout_height="400dp" +        android:flipInterval="1000" +        /> + +</FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/tests/res/layout/custom_view_flipper_image.xml b/packages/SystemUI/tests/res/layout/custom_view_flipper_image.xml new file mode 100644 index 000000000000..e2a00bd845cd --- /dev/null +++ b/packages/SystemUI/tests/res/layout/custom_view_flipper_image.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<ImageView xmlns:android="http://schemas.android.com/apk/res/android" +    android:id="@+id/imageview" +    android:layout_width="match_parent" +    android:layout_height="400dp" />
\ No newline at end of file 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/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifierFlagDisabledTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifierFlagDisabledTest.java new file mode 100644 index 000000000000..09fa3871f6e3 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifierFlagDisabledTest.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2025 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.notification.row; + +import static com.android.systemui.statusbar.notification.row.NotificationCustomContentNotificationBuilder.buildAcceptableNotificationEntry; + +import static com.google.common.truth.Truth.assertThat; + +import android.compat.testing.PlatformCompatChangeRule; +import android.platform.test.annotations.DisableFlags; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.server.notification.Flags; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; + +import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class NotificationCustomContentMemoryVerifierFlagDisabledTest extends SysuiTestCase { + +    @Rule +    public PlatformCompatChangeRule mCompatChangeRule = new PlatformCompatChangeRule(); + +    @Test +    @DisableFlags(Flags.FLAG_NOTIFICATION_CUSTOM_VIEW_URI_RESTRICTION) +    @EnableCompatChanges({ +            NotificationCustomContentCompat.CHECK_SIZE_OF_INFLATED_CUSTOM_VIEWS +    }) +    public void requiresImageViewMemorySizeCheck_flagDisabled_returnsFalse() { +        NotificationEntry entry = buildAcceptableNotificationEntry(mContext); +        assertThat(NotificationCustomContentMemoryVerifier.requiresImageViewMemorySizeCheck(entry)) +                .isFalse(); +    } + +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifierTest.java new file mode 100644 index 000000000000..1cadb3c0a909 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifierTest.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2025 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.notification.row; + +import static com.android.systemui.statusbar.notification.row.NotificationCustomContentNotificationBuilder.buildAcceptableNotification; +import static com.android.systemui.statusbar.notification.row.NotificationCustomContentNotificationBuilder.buildAcceptableNotificationEntry; +import static com.android.systemui.statusbar.notification.row.NotificationCustomContentNotificationBuilder.buildOversizedNotification; +import static com.android.systemui.statusbar.notification.row.NotificationCustomContentNotificationBuilder.buildWarningSizedNotification; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.Notification; +import android.compat.testing.PlatformCompatChangeRule; +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.Context; +import android.content.pm.ProviderInfo; +import android.content.res.AssetFileDescriptor; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.CancellationSignal; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.platform.test.annotations.EnableFlags; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.RemoteViews; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.server.notification.Flags; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; + +import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; +import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.FileNotFoundException; + +@SmallTest +@RunWith(AndroidJUnit4.class) +@EnableFlags(Flags.FLAG_NOTIFICATION_CUSTOM_VIEW_URI_RESTRICTION) +public class NotificationCustomContentMemoryVerifierTest extends SysuiTestCase { + +    private static final String AUTHORITY = "notification.memory.test.authority"; +    private static final Uri TEST_URI = new Uri.Builder() +            .scheme("content") +            .authority(AUTHORITY) +            .path("path") +            .build(); + +    @Rule +    public PlatformCompatChangeRule mCompatChangeRule = new PlatformCompatChangeRule(); + +    @Before +    public void setUp() { +        TestImageContentProvider provider = new TestImageContentProvider(mContext); +        mContext.getContentResolver().addProvider(AUTHORITY, provider); +        provider.onCreate(); +    } + +    @Test +    @EnableCompatChanges({ +            NotificationCustomContentCompat.CHECK_SIZE_OF_INFLATED_CUSTOM_VIEWS}) +    public void requiresImageViewMemorySizeCheck_customViewNotification_returnsTrue() { +        NotificationEntry entry = +                buildAcceptableNotificationEntry( +                        mContext); +        assertThat(NotificationCustomContentMemoryVerifier.requiresImageViewMemorySizeCheck(entry)) +                .isTrue(); +    } + +    @Test +    @EnableCompatChanges({ +            NotificationCustomContentCompat.CHECK_SIZE_OF_INFLATED_CUSTOM_VIEWS}) +    public void requiresImageViewMemorySizeCheck_plainNotification_returnsFalse() { +        Notification notification = +                new Notification.Builder(mContext, "ChannelId") +                        .setContentTitle("Just a notification") +                        .setContentText("Yep") +                        .build(); +        NotificationEntry entry = new NotificationEntryBuilder().setNotification( +                notification).build(); +        assertThat(NotificationCustomContentMemoryVerifier.requiresImageViewMemorySizeCheck(entry)) +                .isFalse(); +    } + + +    @Test +    @EnableCompatChanges({ +            NotificationCustomContentCompat.CHECK_SIZE_OF_INFLATED_CUSTOM_VIEWS}) +    public void satisfiesMemoryLimits_smallNotification_returnsTrue() { +        Notification.Builder notification = +                buildAcceptableNotification(mContext, +                        TEST_URI); +        NotificationEntry entry = toEntry(notification); +        View inflatedView = inflateNotification(notification); +        assertThat( +                NotificationCustomContentMemoryVerifier.satisfiesMemoryLimits(inflatedView, entry) +        ) +                .isTrue(); +    } + +    @Test +    @EnableCompatChanges({ +            NotificationCustomContentCompat.CHECK_SIZE_OF_INFLATED_CUSTOM_VIEWS}) +    public void satisfiesMemoryLimits_oversizedNotification_returnsFalse() { +        Notification.Builder notification = +                buildOversizedNotification(mContext, +                        TEST_URI); +        NotificationEntry entry = toEntry(notification); +        View inflatedView = inflateNotification(notification); +        assertThat( +                NotificationCustomContentMemoryVerifier.satisfiesMemoryLimits(inflatedView, entry) +        ).isFalse(); +    } + +    @Test +    @DisableCompatChanges( +            {NotificationCustomContentCompat.CHECK_SIZE_OF_INFLATED_CUSTOM_VIEWS} +    ) +    public void satisfiesMemoryLimits_oversizedNotification_compatDisabled_returnsTrue() { +        Notification.Builder notification = +                buildOversizedNotification(mContext, +                        TEST_URI); +        NotificationEntry entry = toEntry(notification); +        View inflatedView = inflateNotification(notification); +        assertThat( +                NotificationCustomContentMemoryVerifier.satisfiesMemoryLimits(inflatedView, entry) +        ).isTrue(); +    } + +    @Test +    @EnableCompatChanges({ +            NotificationCustomContentCompat.CHECK_SIZE_OF_INFLATED_CUSTOM_VIEWS}) +    public void satisfiesMemoryLimits_warningSizedNotification_returnsTrue() { +        Notification.Builder notification = +                buildWarningSizedNotification(mContext, +                        TEST_URI); +        NotificationEntry entry = toEntry(notification); +        View inflatedView = inflateNotification(notification); +        assertThat( +                NotificationCustomContentMemoryVerifier.satisfiesMemoryLimits(inflatedView, entry) +        ) +                .isTrue(); +    } + +    @Test +    @EnableCompatChanges({ +            NotificationCustomContentCompat.CHECK_SIZE_OF_INFLATED_CUSTOM_VIEWS}) +    public void satisfiesMemoryLimits_viewWithoutCustomNotificationRoot_returnsTrue() { +        NotificationEntry entry = new NotificationEntryBuilder().build(); +        View view = new FrameLayout(mContext); +        assertThat(NotificationCustomContentMemoryVerifier.satisfiesMemoryLimits(view, entry)) +                .isTrue(); +    } + +    @Test +    @EnableCompatChanges({ +            NotificationCustomContentCompat.CHECK_SIZE_OF_INFLATED_CUSTOM_VIEWS}) +    public void computeViewHierarchyImageViewSize_smallNotification_returnsSensibleValue() { +        Notification.Builder notification = +                buildAcceptableNotification(mContext, +                        TEST_URI); +        // This should have a size of a single image +        View inflatedView = inflateNotification(notification); +        assertThat( +                NotificationCustomContentMemoryVerifier.computeViewHierarchyImageViewSize( +                        inflatedView)) +                .isGreaterThan(170000); +    } + +    private View inflateNotification(Notification.Builder builder) { +        RemoteViews remoteViews = builder.createBigContentView(); +        return remoteViews.apply(mContext, new FrameLayout(mContext)); +    } + +    private NotificationEntry toEntry(Notification.Builder builder) { +        return new NotificationEntryBuilder().setNotification(builder.build()) +                .setUid(Process.myUid()).build(); +    } + + +    /** This provider serves the images for inflation. */ +    class TestImageContentProvider extends ContentProvider { + +        TestImageContentProvider(Context context) { +            ProviderInfo info = new ProviderInfo(); +            info.authority = AUTHORITY; +            info.exported = true; +            attachInfoForTesting(context, info); +            setAuthorities(AUTHORITY); +        } + +        @Override +        public boolean onCreate() { +            return true; +        } + +        @Override +        public ParcelFileDescriptor openFile(Uri uri, String mode) { +            return getContext().getResources().openRawResourceFd( +                    NotificationCustomContentNotificationBuilder.getDRAWABLE_IMAGE_RESOURCE()) +                        .getParcelFileDescriptor(); +        } + +        @Override +        public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts) { +            return getContext().getResources().openRawResourceFd( +                    NotificationCustomContentNotificationBuilder.getDRAWABLE_IMAGE_RESOURCE()); +        } + +        @Override +        public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts, +                CancellationSignal signal) throws FileNotFoundException { +            return openTypedAssetFile(uri, mimeTypeFilter, opts); +        } + +        @Override +        public int delete(Uri uri, Bundle extras) { +            return 0; +        } + +        @Override +        public int delete(Uri uri, String selection, String[] selectionArgs) { +            return 0; +        } + +        @Override +        public String getType(Uri uri) { +            return "image/png"; +        } + +        @Override +        public Uri insert(Uri uri, ContentValues values) { +            return null; +        } + +        @Override +        public Uri insert(Uri uri, ContentValues values, Bundle extras) { +            return super.insert(uri, values, extras); +        } + +        @Override +        public Cursor query(Uri uri, String[] projection, Bundle queryArgs, +                CancellationSignal cancellationSignal) { +            return super.query(uri, projection, queryArgs, cancellationSignal); +        } + +        @Override +        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, +                String sortOrder) { +            return null; +        } + +        @Override +        public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { +            return 0; +        } +    } + + +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentNotificationBuilder.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentNotificationBuilder.kt new file mode 100644 index 000000000000..ca4f24da3c08 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentNotificationBuilder.kt @@ -0,0 +1,94 @@ +/* + * 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 + */ + +@file:JvmName("NotificationCustomContentNotificationBuilder") + +package com.android.systemui.statusbar.notification.row + +import android.app.Notification +import android.app.Notification.DecoratedCustomViewStyle +import android.content.Context +import android.graphics.drawable.BitmapDrawable +import android.net.Uri +import android.os.Process +import android.widget.RemoteViews +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.tests.R +import org.hamcrest.Matchers.lessThan +import org.junit.Assume.assumeThat + +public val DRAWABLE_IMAGE_RESOURCE = R.drawable.romainguy_rockaway + +fun buildAcceptableNotificationEntry(context: Context): NotificationEntry { +    return NotificationEntryBuilder() +        .setNotification(buildAcceptableNotification(context, null).build()) +        .setUid(Process.myUid()) +        .build() +} + +fun buildAcceptableNotification(context: Context, uri: Uri?): Notification.Builder = +    buildNotification(context, uri, 1) + +fun buildOversizedNotification(context: Context, uri: Uri): Notification.Builder { +    val numImagesForOversize = +        (NotificationCustomContentMemoryVerifier.getStripViewSizeLimit(context) / +            drawableSizeOnDevice(context)) + 2 +    return buildNotification(context, uri, numImagesForOversize) +} + +fun buildWarningSizedNotification(context: Context, uri: Uri): Notification.Builder { +    val numImagesForOversize = +        (NotificationCustomContentMemoryVerifier.getWarnViewSizeLimit(context) / +            drawableSizeOnDevice(context)) + 1 +    // The size needs to be smaller than outright stripping size. +    assumeThat( +        numImagesForOversize * drawableSizeOnDevice(context), +        lessThan(NotificationCustomContentMemoryVerifier.getStripViewSizeLimit(context)), +    ) +    return buildNotification(context, uri, numImagesForOversize) +} + +fun buildNotification(context: Context, uri: Uri?, numImages: Int): Notification.Builder { +    val remoteViews = RemoteViews(context.packageName, R.layout.custom_view_flipper) +    repeat(numImages) { i -> +        val remoteViewFlipperImageView = +            RemoteViews(context.packageName, R.layout.custom_view_flipper_image) + +        if (uri == null) { +            remoteViewFlipperImageView.setImageViewResource( +                R.id.imageview, +                R.drawable.romainguy_rockaway, +            ) +        } else { +            val imageUri = uri.buildUpon().appendPath(i.toString()).build() +            remoteViewFlipperImageView.setImageViewUri(R.id.imageview, imageUri) +        } +        remoteViews.addView(R.id.flipper, remoteViewFlipperImageView) +    } + +    return Notification.Builder(context, "ChannelId") +        .setSmallIcon(android.R.drawable.ic_info) +        .setStyle(DecoratedCustomViewStyle()) +        .setCustomContentView(remoteViews) +        .setCustomBigContentView(remoteViews) +        .setContentTitle("This is a remote view!") +} + +fun drawableSizeOnDevice(context: Context): Int { +    val drawable = context.resources.getDrawable(DRAWABLE_IMAGE_RESOURCE) +    return (drawable as BitmapDrawable).bitmap.allocationByteCount +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorKosmos.kt index 2057b849c069..c7380c91f703 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorKosmos.kt @@ -21,6 +21,7 @@ import com.android.systemui.keyguard.data.repository.keyguardRepository  import com.android.systemui.kosmos.Kosmos  import com.android.systemui.kosmos.Kosmos.Fixture  import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.shade.domain.interactor.shadeModeInteractor  import com.android.systemui.statusbar.lockscreenShadeTransitionController  val Kosmos.notificationShelfInteractor by Fixture { @@ -28,6 +29,7 @@ val Kosmos.notificationShelfInteractor by Fixture {          keyguardRepository = keyguardRepository,          deviceEntryFaceAuthRepository = deviceEntryFaceAuthRepository,          powerInteractor = powerInteractor, +        shadeModeInteractor = shadeModeInteractor,          keyguardTransitionController = lockscreenShadeTransitionController,      )  } diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index b75728e9f97c..f03e8c713228 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -110,6 +110,7 @@ import android.util.SparseIntArray;  import android.view.Display;  import android.view.WindowManager;  import android.widget.Toast; +import android.window.DisplayWindowPolicyController;  import com.android.internal.annotations.GuardedBy;  import com.android.internal.annotations.VisibleForTesting; @@ -1411,8 +1412,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub          return mirroredDisplayId == Display.INVALID_DISPLAY ? displayId : mirroredDisplayId;      } -    @GuardedBy("mVirtualDeviceLock") -    private GenericWindowPolicyController createWindowPolicyControllerLocked( +    private GenericWindowPolicyController createWindowPolicyController(              @NonNull Set<String> displayCategories) {          final boolean activityLaunchAllowedByDefault =                  getDevicePolicy(POLICY_TYPE_ACTIVITY) == DEVICE_POLICY_DEFAULT; @@ -1421,28 +1421,28 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub          final boolean showTasksInHostDeviceRecents =                  getDevicePolicy(POLICY_TYPE_RECENTS) == DEVICE_POLICY_DEFAULT; -        if (mActivityListenerAdapter == null) { -            mActivityListenerAdapter = new GwpcActivityListener(); -        } - -        final GenericWindowPolicyController gwpc = new GenericWindowPolicyController( -                WindowManager.LayoutParams.FLAG_SECURE, -                WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, -                mAttributionSource, -                getAllowedUserHandles(), -                activityLaunchAllowedByDefault, -                mActivityPolicyExemptions, -                mActivityPolicyPackageExemptions, -                crossTaskNavigationAllowedByDefault, -                /* crossTaskNavigationExemptions= */crossTaskNavigationAllowedByDefault -                        ? mParams.getBlockedCrossTaskNavigations() -                        : mParams.getAllowedCrossTaskNavigations(), -                mActivityListenerAdapter, -                displayCategories, -                showTasksInHostDeviceRecents, -                mParams.getHomeComponent()); -        gwpc.registerRunningAppsChangedListener(/* listener= */ this); -        return gwpc; +        synchronized (mVirtualDeviceLock) { +            if (mActivityListenerAdapter == null) { +                mActivityListenerAdapter = new GwpcActivityListener(); +            } + +            return new GenericWindowPolicyController( +                    WindowManager.LayoutParams.FLAG_SECURE, +                    WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, +                    mAttributionSource, +                    getAllowedUserHandles(), +                    activityLaunchAllowedByDefault, +                    mActivityPolicyExemptions, +                    mActivityPolicyPackageExemptions, +                    crossTaskNavigationAllowedByDefault, +                    /* crossTaskNavigationExemptions= */crossTaskNavigationAllowedByDefault +                            ? mParams.getBlockedCrossTaskNavigations() +                            : mParams.getAllowedCrossTaskNavigations(), +                    mActivityListenerAdapter, +                    displayCategories, +                    showTasksInHostDeviceRecents, +                    mParams.getHomeComponent()); +        }      }      @Override // Binder call @@ -1450,55 +1450,54 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub              @NonNull IVirtualDisplayCallback callback) {          checkCallerIsDeviceOwner(); -        int displayId; -        boolean showPointer; -        boolean isTrustedDisplay; -        GenericWindowPolicyController gwpc; -        synchronized (mVirtualDeviceLock) { -            gwpc = createWindowPolicyControllerLocked(virtualDisplayConfig.getDisplayCategories()); -            displayId = mDisplayManagerInternal.createVirtualDisplay(virtualDisplayConfig, +        final boolean isTrustedDisplay = +                (virtualDisplayConfig.getFlags() & DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED) +                        == DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED; +        if (!isTrustedDisplay && getDevicePolicy(POLICY_TYPE_CLIPBOARD) != DEVICE_POLICY_DEFAULT) { +            throw new SecurityException( +                "All displays must be trusted for devices with custom clipboard policy."); +        } + +        GenericWindowPolicyController gwpc = +                createWindowPolicyController(virtualDisplayConfig.getDisplayCategories()); + +        // Create the display outside of the lock to avoid deadlock. DisplayManagerService will +        // acquire the global WM lock while creating the display. At the same time, WM may query +        // VDM and this virtual device to get policies, display ownership, etc. +        int displayId = mDisplayManagerInternal.createVirtualDisplay(virtualDisplayConfig,                      callback, this, gwpc, mOwnerPackageName); -            boolean isMirrorDisplay = -                    mDisplayManagerInternal.getDisplayIdToMirror(displayId) -                            != Display.INVALID_DISPLAY; -            gwpc.setDisplayId(displayId, isMirrorDisplay); -            isTrustedDisplay = -                    (mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED) -                            == Display.FLAG_TRUSTED; -            if (!isTrustedDisplay -                    && getDevicePolicy(POLICY_TYPE_CLIPBOARD) != DEVICE_POLICY_DEFAULT) { -                throw new SecurityException("All displays must be trusted for devices with " -                        + "custom clipboard policy."); -            } +        if (displayId == Display.INVALID_DISPLAY) { +            return displayId; +        } -            if (mVirtualDisplays.contains(displayId)) { -                gwpc.unregisterRunningAppsChangedListener(this); -                throw new IllegalStateException( -                        "Virtual device already has a virtual display with ID " + displayId); +        // DisplayManagerService will call onVirtualDisplayCreated() after the display is created, +        // while holding its own lock to ensure that this device knows about the display before any +        // other display listeners are notified about the display creation. +        VirtualDisplayWrapper displayWrapper; +        boolean showPointer; +        synchronized (mVirtualDeviceLock) { +            if (!mVirtualDisplays.contains(displayId)) { +                throw new IllegalStateException("Virtual device was not notified about the " +                        + "creation of display with ID " + displayId);              } - -            PowerManager.WakeLock wakeLock = -                    isTrustedDisplay ? createAndAcquireWakeLockForDisplay(displayId) : null; -            mVirtualDisplays.put(displayId, new VirtualDisplayWrapper(callback, gwpc, wakeLock, -                    isTrustedDisplay, isMirrorDisplay)); +            displayWrapper = mVirtualDisplays.get(displayId);              showPointer = mDefaultShowPointerIcon;          } +        displayWrapper.acquireWakeLock(); +        gwpc.registerRunningAppsChangedListener(/* listener= */ this); -        final long token = Binder.clearCallingIdentity(); -        try { +        Binder.withCleanCallingIdentity(() -> {              mInputController.setMouseScalingEnabled(false, displayId);              mInputController.setDisplayEligibilityForPointerCapture(/* isEligible= */ false,                      displayId); -            if (isTrustedDisplay) { +            if (displayWrapper.isTrusted()) {                  mInputController.setShowPointerIcon(showPointer, displayId);                  mInputController.setDisplayImePolicy(displayId,                          WindowManager.DISPLAY_IME_POLICY_LOCAL);              } else {                  gwpc.setShowInHostDeviceRecents(true);              } -        } finally { -            Binder.restoreCallingIdentity(token); -        } +        });          Counter.logIncrementWithUid(                  "virtual_devices.value_virtual_display_created_count", @@ -1506,7 +1505,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub          return displayId;      } -    private PowerManager.WakeLock createAndAcquireWakeLockForDisplay(int displayId) { +    private PowerManager.WakeLock createWakeLockForDisplay(int displayId) {          if (Flags.deviceAwareDisplayPower()) {              return null;          } @@ -1516,7 +1515,6 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub              PowerManager.WakeLock wakeLock = powerManager.newWakeLock(                      PowerManager.SCREEN_BRIGHT_WAKE_LOCK,                      TAG + ":" + displayId, displayId); -            wakeLock.acquire();              return wakeLock;          } finally {              Binder.restoreCallingIdentity(token); @@ -1561,17 +1559,47 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub          return result;      } +    /** +     * DisplayManagerService is notifying this virtual device about the display creation. This +     * should happen before the DisplayManagerInternal#createVirtualDisplay() call above +     * returns. +     * This is called while holding the DisplayManagerService lock, so no heavy-weight work must +     * be done here and especially *** no calls to WindowManager! *** +     */ +    public void onVirtualDisplayCreated(int displayId, IVirtualDisplayCallback callback, +            DisplayWindowPolicyController dwpc) { +        final boolean isMirrorDisplay = +                mDisplayManagerInternal.getDisplayIdToMirror(displayId) != Display.INVALID_DISPLAY; +        final boolean isTrustedDisplay = +                (mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED) +                        == Display.FLAG_TRUSTED; + +        GenericWindowPolicyController gwpc = (GenericWindowPolicyController) dwpc; +        gwpc.setDisplayId(displayId, isMirrorDisplay); +        PowerManager.WakeLock wakeLock = +                isTrustedDisplay ? createWakeLockForDisplay(displayId) : null; +        synchronized (mVirtualDeviceLock) { +            if (mVirtualDisplays.contains(displayId)) { +                Slog.wtf(TAG, "Virtual device already has a virtual display with ID " + displayId); +                return; +            } +            mVirtualDisplays.put(displayId, new VirtualDisplayWrapper(callback, gwpc, wakeLock, +                    isTrustedDisplay, isMirrorDisplay)); +        } +    } + +    /** +     * This is callback invoked by VirtualDeviceManagerService when VirtualDisplay was released +     * by DisplayManager (most probably caused by someone calling VirtualDisplay.close()). +     * At this point, the display is already released, but we still need to release the +     * corresponding wakeLock and unregister the RunningAppsChangedListener from corresponding +     * WindowPolicyController. +     * +     * Note that when the display is destroyed during VirtualDeviceImpl.close() call, +     * this callback won't be invoked because the display is removed from +     * VirtualDeviceManagerService before any resources are released. +     */      void onVirtualDisplayRemoved(int displayId) { -        /* This is callback invoked by VirtualDeviceManagerService when VirtualDisplay was released -         * by DisplayManager (most probably caused by someone calling VirtualDisplay.close()). -         * At this point, the display is already released, but we still need to release the -         * corresponding wakeLock and unregister the RunningAppsChangedListener from corresponding -         * WindowPolicyController. -         * -         * Note that when the display is destroyed during VirtualDeviceImpl.close() call, -         * this callback won't be invoked because the display is removed from -         * VirtualDeviceManagerService before any resources are released. -         */          VirtualDisplayWrapper virtualDisplayWrapper;          synchronized (mVirtualDeviceLock) {              virtualDisplayWrapper = mVirtualDisplays.removeReturnOld(displayId); @@ -1847,6 +1875,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub              return mWindowPolicyController;          } +        void acquireWakeLock() { +            if (mWakeLock != null && !mWakeLock.isHeld()) { +                mWakeLock.acquire(); +            } +        } +          void releaseWakeLock() {              if (mWakeLock != null && mWakeLock.isHeld()) {                  mWakeLock.release(); diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index 8a0b85859b66..ff82ca00b840 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -40,7 +40,6 @@ import android.companion.virtual.IVirtualDeviceSoundEffectListener;  import android.companion.virtual.VirtualDevice;  import android.companion.virtual.VirtualDeviceManager;  import android.companion.virtual.VirtualDeviceParams; -import android.companion.virtual.flags.Flags;  import android.companion.virtual.sensor.VirtualSensor;  import android.companion.virtualnative.IVirtualDeviceManagerNative;  import android.compat.annotation.ChangeId; @@ -49,6 +48,7 @@ import android.content.AttributionSource;  import android.content.Context;  import android.content.Intent;  import android.hardware.display.DisplayManagerInternal; +import android.hardware.display.IVirtualDisplayCallback;  import android.os.Binder;  import android.os.Build;  import android.os.Handler; @@ -67,6 +67,7 @@ import android.util.Slog;  import android.util.SparseArray;  import android.view.Display;  import android.widget.Toast; +import android.window.DisplayWindowPolicyController;  import com.android.internal.R;  import com.android.internal.annotations.GuardedBy; @@ -751,6 +752,16 @@ public class VirtualDeviceManagerService extends SystemService {          }          @Override +        public void onVirtualDisplayCreated(IVirtualDevice virtualDevice, int displayId, +                IVirtualDisplayCallback callback, DisplayWindowPolicyController dwpc) { +            VirtualDeviceImpl virtualDeviceImpl = getVirtualDeviceForId( +                    ((VirtualDeviceImpl) virtualDevice).getDeviceId()); +            if (virtualDeviceImpl != null) { +                virtualDeviceImpl.onVirtualDisplayCreated(displayId, callback, dwpc); +            } +        } + +        @Override          public void onVirtualDisplayRemoved(IVirtualDevice virtualDevice, int displayId) {              VirtualDeviceImpl virtualDeviceImpl = getVirtualDeviceForId(                      ((VirtualDeviceImpl) virtualDevice).getDeviceId()); diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index c8b0a57fe9f0..5ff6999e40b3 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -3705,8 +3705,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub      @Override      public void takeUidSnapshotsAsync(int[] requestUids, ResultReceiver resultReceiver) {          if (!onlyCaller(requestUids)) { -            mContext.enforceCallingOrSelfPermission( -                    android.Manifest.permission.BATTERY_STATS, null); +            try { +                mContext.enforceCallingOrSelfPermission( +                        android.Manifest.permission.BATTERY_STATS, null); +            } catch (SecurityException ex) { +                resultReceiver.send(IBatteryStats.RESULT_SECURITY_EXCEPTION, +                        Bundle.forPair(IBatteryStats.KEY_EXCEPTION_MESSAGE, ex.getMessage())); +                return; +            }          }          if (shouldCollectExternalStats()) { @@ -3727,13 +3733,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub                  }                  Bundle resultData = new Bundle(1);                  resultData.putParcelableArray(IBatteryStats.KEY_UID_SNAPSHOTS, results); -                resultReceiver.send(0, resultData); +                resultReceiver.send(IBatteryStats.RESULT_OK, resultData);              } catch (Exception ex) {                  if (DBG) {                      Slog.d(TAG, "Crashed while returning results for takeUidSnapshots("                              + Arrays.toString(requestUids) + ") i=" + i, ex);                  } -                throw ex; +                resultReceiver.send(IBatteryStats.RESULT_RUNTIME_EXCEPTION, +                        Bundle.forPair(IBatteryStats.KEY_EXCEPTION_MESSAGE, ex.getMessage()));              } finally {                  Binder.restoreCallingIdentity(ident);              } 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/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 2219ecc77167..6f79f7073b89 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -66,7 +66,6 @@ import static com.android.media.audio.Flags.equalScoLeaVcIndexRange;  import static com.android.media.audio.Flags.replaceStreamBtSco;  import static com.android.media.audio.Flags.ringMyCar;  import static com.android.media.audio.Flags.ringerModeAffectsAlarm; -import static com.android.media.audio.Flags.vgsVssSyncMuteOrder;  import static com.android.media.flags.Flags.enableAudioInputDeviceRoutingAndVolumeControl;  import static com.android.server.audio.SoundDoseHelper.ACTION_CHECK_MUSIC_ACTIVE;  import static com.android.server.utils.EventLogger.Event.ALOGE; @@ -4977,9 +4976,8 @@ public class AudioService extends IAudioService.Stub                  + roForegroundAudioControl());          pw.println("\tandroid.media.audio.scoManagedByAudio:"                  + scoManagedByAudio()); -        pw.println("\tcom.android.media.audio.vgsVssSyncMuteOrder:" -                + vgsVssSyncMuteOrder());          pw.println("\tcom.android.media.audio.absVolumeIndexFix - EOL"); +        pw.println("\tcom.android.media.audio.vgsVssSyncMuteOrder - EOL");          pw.println("\tcom.android.media.audio.replaceStreamBtSco:"                  + replaceStreamBtSco());          pw.println("\tcom.android.media.audio.equalScoLeaVcIndexRange:" @@ -9010,22 +9008,13 @@ public class AudioService extends IAudioService.Stub                                          synced = true;                                          continue;                                      } -                                    if (vgsVssSyncMuteOrder()) { -                                        if ((isMuted() != streamMuted) && isVssMuteBijective( -                                                stream)) { -                                            vss.mute(isMuted(), "VGS.applyAllVolumes#1"); -                                        } +                                    if ((isMuted() != streamMuted) && isVssMuteBijective(stream)) { +                                        vss.mute(isMuted(), "VGS.applyAllVolumes#1");                                      }                                      if (indexForStream != index) {                                          vss.setIndex(index * 10, device,                                                  caller, true /*hasModifyAudioSettings*/);                                      } -                                    if (!vgsVssSyncMuteOrder()) { -                                        if ((isMuted() != streamMuted) && isVssMuteBijective( -                                                stream)) { -                                            vss.mute(isMuted(), "VGS.applyAllVolumes#1"); -                                        } -                                    }                                  }                              }                          } diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java index 471b7b4ddfc8..d412277d2605 100644 --- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java +++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java @@ -24,8 +24,10 @@ import android.companion.virtual.VirtualDeviceManager;  import android.companion.virtual.VirtualDeviceParams;  import android.companion.virtual.sensor.VirtualSensor;  import android.content.Context; +import android.hardware.display.IVirtualDisplayCallback;  import android.os.LocaleList;  import android.util.ArraySet; +import android.window.DisplayWindowPolicyController;  import java.util.Set;  import java.util.function.Consumer; @@ -104,6 +106,17 @@ public abstract class VirtualDeviceManagerInternal {      public abstract @NonNull ArraySet<Integer> getDeviceIdsForUid(int uid);      /** +     * Notifies that a virtual display was created. +     * +     * @param virtualDevice The virtual device that owns the virtual display. +     * @param displayId     The display id of the created virtual display. +     * @param callback      The callback of the virtual display. +     * @param dwpc          The DisplayWindowPolicyController of the created virtual display. +     */ +    public abstract void onVirtualDisplayCreated(IVirtualDevice virtualDevice, int displayId, +            IVirtualDisplayCallback callback, DisplayWindowPolicyController dwpc); + +    /**       * Notifies that a virtual display is removed.       *       * @param virtualDevice The virtual device where the virtual display located. diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index e83efc573ea8..854b0dd7676b 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -2041,6 +2041,7 @@ public final class DisplayManagerService extends SystemService {                                  packageName,                                  displayUniqueId,                                  virtualDevice, +                                dwpc,                                  surface,                                  flags,                                  virtualDisplayConfig); @@ -2135,6 +2136,7 @@ public final class DisplayManagerService extends SystemService {              String packageName,              String uniqueId,              IVirtualDevice virtualDevice, +            DisplayWindowPolicyController dwpc,              Surface surface,              int flags,              VirtualDisplayConfig virtualDisplayConfig) { @@ -2188,6 +2190,16 @@ public final class DisplayManagerService extends SystemService {          final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);          if (display != null) { +            // Notify the virtual device that the display has been created. This needs to be called +            // in this locked section before the repository had the chance to notify any listeners +            // to ensure that the device is aware of the new display before others know about it. +            if (virtualDevice != null) { +                final VirtualDeviceManagerInternal vdm = +                        getLocalService(VirtualDeviceManagerInternal.class); +                vdm.onVirtualDisplayCreated( +                        virtualDevice, display.getDisplayIdLocked(), callback, dwpc); +            } +              return display.getDisplayIdLocked();          } diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index b49c01b3e2a8..83ca563e0534 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -781,6 +781,11 @@ final class LocalDisplayAdapter extends DisplayAdapter {                      if (isDisplayPrivate(physicalAddress)) {                          mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE;                      } + +                    if (isDisplayStealTopFocusDisabled(physicalAddress)) { +                        mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_FOCUS; +                        mInfo.flags |= DisplayDeviceInfo.FLAG_STEAL_TOP_FOCUS_DISABLED; +                    }                  }                  if (DisplayCutout.getMaskBuiltInDisplayCutout(res, mInfo.uniqueId)) { @@ -1467,6 +1472,23 @@ final class LocalDisplayAdapter extends DisplayAdapter {              }              return false;          } + +        private boolean isDisplayStealTopFocusDisabled(DisplayAddress.Physical physicalAddress) { +            if (physicalAddress == null) { +                return false; +            } +            final Resources res = getOverlayContext().getResources(); +            int[] ports = res.getIntArray(R.array.config_localNotStealTopFocusDisplayPorts); +            if (ports != null) { +                int port = physicalAddress.getPort(); +                for (int p : ports) { +                    if (p == port) { +                        return true; +                    } +                } +            } +            return false; +        }      }      private boolean hdrTypesEqual(int[] modeHdrTypes, int[] recordHdrTypes) { diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java index b529853c63a4..058bbc08a9ef 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java @@ -267,6 +267,50 @@ import java.util.stream.Stream;          notifyRequestFailed(requestId, MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE);      } +    @Override +    public void selectRoute(long requestId, String sessionId, String routeId) { +        if (SYSTEM_SESSION_ID.equals(sessionId)) { +            super.selectRoute(requestId, sessionId, routeId); +            return; +        } +        synchronized (mLock) { +            var sessionRecord = getSessionRecordByOriginalId(sessionId); +            var proxyRecord = sessionRecord != null ? sessionRecord.getProxyRecord() : null; +            if (proxyRecord != null) { +                var targetSourceRouteId = +                        proxyRecord.mNewOriginalIdToSourceOriginalIdMap.get(routeId); +                if (targetSourceRouteId != null) { +                    proxyRecord.mProxy.selectRoute( +                            requestId, sessionRecord.getServiceSessionId(), targetSourceRouteId); +                } +                return; +            } +        } +        notifyRequestFailed(requestId, MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE); +    } + +    @Override +    public void deselectRoute(long requestId, String sessionId, String routeId) { +        if (SYSTEM_SESSION_ID.equals(sessionId)) { +            super.selectRoute(requestId, sessionId, routeId); +            return; +        } +        synchronized (mLock) { +            var sessionRecord = getSessionRecordByOriginalId(sessionId); +            var proxyRecord = sessionRecord != null ? sessionRecord.getProxyRecord() : null; +            if (proxyRecord != null) { +                var targetSourceRouteId = +                        proxyRecord.mNewOriginalIdToSourceOriginalIdMap.get(routeId); +                if (targetSourceRouteId != null) { +                    proxyRecord.mProxy.deselectRoute( +                            requestId, sessionRecord.getServiceSessionId(), targetSourceRouteId); +                } +                return; +            } +        } +        notifyRequestFailed(requestId, MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE); +    } +      @GuardedBy("mLock")      private SystemMediaSessionRecord getSessionRecordByOriginalId(String sessionOriginalId) {          if (FORCE_GLOBAL_ROUTING_SESSION) { 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/core/java/com/android/server/wm/AppWarnings.java b/services/core/java/com/android/server/wm/AppWarnings.java index 576e5d5d0cd2..439b503c0c57 100644 --- a/services/core/java/com/android/server/wm/AppWarnings.java +++ b/services/core/java/com/android/server/wm/AppWarnings.java @@ -506,6 +506,10 @@ class AppWarnings {              context =  new ContextThemeWrapper(context, context.getThemeResId()) {                  @Override                  public void startActivity(Intent intent) { +                    // PageSizeMismatch dialog stays on top of the browser even after opening link +                    // set broadcast to close the dialog when link has been clicked. +                    sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); +                      intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);                      super.startActivity(intent);                  } 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/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java index 5393e20889c0..b9cea0c72306 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -388,6 +388,34 @@ public class LocalDisplayAdapterTest {                  PORT_C, false);      } +    /** +     * Confirm that display is marked as trusted, has own focus, disables steal top focus when it +     * is listed in com.android.internal.R.array.config_localNotStealTopFocusDisplayPorts. +     */ +    @Test +    public void testStealTopFocusDisabledDisplay() throws Exception { +        setUpDisplay(new FakeDisplay(PORT_A)); +        setUpDisplay(new FakeDisplay(PORT_B)); +        setUpDisplay(new FakeDisplay(PORT_C)); +        updateAvailableDisplays(); + +        doReturn(new int[]{ PORT_B }).when(mMockedResources).getIntArray( +                com.android.internal.R.array.config_localNotStealTopFocusDisplayPorts); +        mAdapter.registerLocked(); + +        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + +        // This should not have the flags +        assertNotStealTopFocusFlag(mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(), +                PORT_A, false); +        // This should have the flags +        assertNotStealTopFocusFlag(mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(), +                PORT_B, true); +        // This should not have the flags +        assertNotStealTopFocusFlag(mListener.addedDisplays.get(2).getDisplayDeviceInfoLocked(), +                PORT_C, false); +    } +      @Test      public void testSupportedDisplayModesGetOverriddenWhenDisplayIsUpdated()              throws InterruptedException { @@ -452,6 +480,42 @@ public class LocalDisplayAdapterTest {      }      /** +     * Confirm that all local displays are not trusted, do not have their own focus, and do not +     * steal top focus when config_localNotStealTopFocusDisplayPorts is empty: +     */ +    @Test +    public void testDisplayFlagsForNoConfigLocalNotStealTopFocusDisplayPorts() throws Exception { +        setUpDisplay(new FakeDisplay(PORT_A)); +        setUpDisplay(new FakeDisplay(PORT_C)); +        updateAvailableDisplays(); + +        // config_localNotStealTopFocusDisplayPorts is null +        mAdapter.registerLocked(); + +        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + +        // This should not have the flags +        assertNotStealTopFocusFlag(mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(), +                PORT_A, false); +        // This should not have the flags +        assertNotStealTopFocusFlag(mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(), +                PORT_C, false); +    } + +    private static void assertNotStealTopFocusFlag( +            DisplayDeviceInfo info, int expectedPort, boolean shouldHaveFlags) { +        final DisplayAddress.Physical address = (DisplayAddress.Physical) info.address; +        assertNotNull(address); +        assertEquals(expectedPort, address.getPort()); +        assertEquals(DISPLAY_MODEL, address.getModel()); +        assertEquals(shouldHaveFlags, +                (info.flags & DisplayDeviceInfo.FLAG_STEAL_TOP_FOCUS_DISABLED) != 0); +        assertEquals(shouldHaveFlags, (info.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) != 0); +        // display is always trusted since it is created by the system +        assertEquals(true, (info.flags & DisplayDeviceInfo.FLAG_TRUSTED) != 0); +    } + +    /**       * Confirm that external display uses physical density.       */      @Test diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java index cd365396c74b..bc04fd94c719 100644 --- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java @@ -148,6 +148,7 @@ public class WallpaperManagerServiceTests {      private static ComponentName sImageWallpaperComponentName;      private static ComponentName sDefaultWallpaperComponent; +    private static WallpaperDescription sDefaultWallpaperDescription;      private static ComponentName sFallbackWallpaperComponentName; @@ -214,6 +215,8 @@ public class WallpaperManagerServiceTests {          } else {              sContext.addMockService(sDefaultWallpaperComponent, sWallpaperService);          } +        sDefaultWallpaperDescription = new WallpaperDescription.Builder().setComponent( +                sDefaultWallpaperComponent).build();          sContext.addMockService(sImageWallpaperComponentName, sWallpaperService);          sContext.addMockService(TEST_WALLPAPER_COMPONENT, sWallpaperService); @@ -489,11 +492,12 @@ public class WallpaperManagerServiceTests {      }      @Test -    @EnableFlags(Flags.FLAG_REMOVE_NEXT_WALLPAPER_COMPONENT) +    @EnableFlags({Flags.FLAG_REMOVE_NEXT_WALLPAPER_COMPONENT, +            Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING})      public void testSaveLoadSettings_withoutWallpaperDescription()              throws IOException, XmlPullParserException {          WallpaperData expectedData = mService.getCurrentWallpaperData(FLAG_SYSTEM, 0); -        expectedData.setComponent(sDefaultWallpaperComponent); +        expectedData.setDescription(sDefaultWallpaperDescription);          expectedData.primaryColors = new WallpaperColors(Color.valueOf(Color.RED),                  Color.valueOf(Color.BLUE), null);          expectedData.mWallpaperDimAmount = 0.5f; @@ -529,11 +533,12 @@ public class WallpaperManagerServiceTests {      }      @Test -    @EnableFlags(Flags.FLAG_REMOVE_NEXT_WALLPAPER_COMPONENT) +    @EnableFlags({Flags.FLAG_REMOVE_NEXT_WALLPAPER_COMPONENT, +            Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING})      public void testSaveLoadSettings_withWallpaperDescription()              throws IOException, XmlPullParserException {          WallpaperData expectedData = mService.getCurrentWallpaperData(FLAG_SYSTEM, 0); -        expectedData.setComponent(sDefaultWallpaperComponent); +        expectedData.setDescription(sDefaultWallpaperDescription);          PersistableBundle content = new PersistableBundle();          content.putString("ckey", "cvalue");          WallpaperDescription description = new WallpaperDescription.Builder() @@ -561,7 +566,8 @@ public class WallpaperManagerServiceTests {      }      @Test -    @DisableFlags(Flags.FLAG_REMOVE_NEXT_WALLPAPER_COMPONENT) +    @DisableFlags({Flags.FLAG_REMOVE_NEXT_WALLPAPER_COMPONENT, +            Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING})      public void testSaveLoadSettings_legacyNextComponent()              throws IOException, XmlPullParserException {          WallpaperData systemWallpaperData = mService.getCurrentWallpaperData(FLAG_SYSTEM, 0); 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;          } diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index ffcb96120b19..ab7b4da269db 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -73,6 +73,7 @@ import android.content.IntentFilter;  import android.content.pm.ActivityInfo;  import android.content.pm.ApplicationInfo;  import android.hardware.Sensor; +import android.hardware.display.DisplayManager;  import android.hardware.display.DisplayManagerGlobal;  import android.hardware.display.DisplayManagerInternal;  import android.hardware.display.IDisplayManager; @@ -173,8 +174,7 @@ public class VirtualDeviceManagerServiceTest {      private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000;      private static final int VIRTUAL_DEVICE_ID_1 = 42;      private static final int VIRTUAL_DEVICE_ID_2 = 43; -    private static final VirtualDisplayConfig VIRTUAL_DISPLAY_CONFIG = -            new VirtualDisplayConfig.Builder("virtual_display", 640, 480, 400).build(); +      private static final VirtualDpadConfig DPAD_CONFIG =              new VirtualDpadConfig.Builder()                      .setVendorId(VENDOR_ID) @@ -284,7 +284,12 @@ public class VirtualDeviceManagerServiceTest {      private Intent createRestrictedActivityBlockedIntent(Set<String> displayCategories,              String targetDisplayCategory) {          when(mDisplayManagerInternalMock.createVirtualDisplay(any(), any(), any(), any(), -                eq(VIRTUAL_DEVICE_OWNER_PACKAGE))).thenReturn(DISPLAY_ID_1); +                eq(VIRTUAL_DEVICE_OWNER_PACKAGE))) +                .thenAnswer(inv -> { +                    mLocalService.onVirtualDisplayCreated( +                            mDeviceImpl, DISPLAY_ID_1, inv.getArgument(1), inv.getArgument(3)); +                    return DISPLAY_ID_1; +                });          VirtualDisplayConfig config = new VirtualDisplayConfig.Builder("display", 640, 480,                  420).setDisplayCategories(displayCategories).build();          mDeviceImpl.createVirtualDisplay(config, mVirtualDisplayCallback); @@ -997,8 +1002,7 @@ public class VirtualDeviceManagerServiceTest {      public void onVirtualDisplayCreatedLocked_duplicateCalls_onlyOneWakeLockIsAcquired()              throws RemoteException {          addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED); -        assertThrows(IllegalStateException.class, -                () -> addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1)); +        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);          TestableLooper.get(this).processAllMessages();          verify(mIPowerManagerMock).acquireWakeLock(any(Binder.class), anyInt(),                  nullable(String.class), nullable(String.class), nullable(WorkSource.class), @@ -1871,8 +1875,6 @@ public class VirtualDeviceManagerServiceTest {      }      private void addVirtualDisplay(VirtualDeviceImpl virtualDevice, int displayId, int flags) { -        when(mDisplayManagerInternalMock.createVirtualDisplay(any(), eq(mVirtualDisplayCallback), -                eq(virtualDevice), any(), any())).thenReturn(displayId);          final String uniqueId = UNIQUE_ID + displayId;          doAnswer(inv -> {              final DisplayInfo displayInfo = new DisplayInfo(); @@ -1880,7 +1882,22 @@ public class VirtualDeviceManagerServiceTest {              displayInfo.flags = flags;              return displayInfo;          }).when(mDisplayManagerInternalMock).getDisplayInfo(eq(displayId)); -        virtualDevice.createVirtualDisplay(VIRTUAL_DISPLAY_CONFIG, mVirtualDisplayCallback); + +        when(mDisplayManagerInternalMock.createVirtualDisplay(any(), eq(mVirtualDisplayCallback), +                eq(virtualDevice), any(), any())).thenAnswer(inv -> { +                    mLocalService.onVirtualDisplayCreated( +                            virtualDevice, displayId, mVirtualDisplayCallback, inv.getArgument(3)); +                    return displayId; +                }); + +        final int virtualDisplayFlags = (flags & Display.FLAG_TRUSTED) == 0 +                ? 0 +                : DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED; +        VirtualDisplayConfig virtualDisplayConfig = +                new VirtualDisplayConfig.Builder("virtual_display", 640, 480, 400) +                        .setFlags(virtualDisplayFlags) +                        .build(); +        virtualDevice.createVirtualDisplay(virtualDisplayConfig, mVirtualDisplayCallback);          mInputManagerMockHelper.addDisplayIdMapping(uniqueId, displayId);      }  |