diff options
137 files changed, 2541 insertions, 3007 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 5ef775955956..7a1add3eb57e 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -242,6 +242,12 @@ cc_aconfig_library { aconfig_declarations: "com.android.text.flags-aconfig", } +rust_aconfig_library { + name: "libandroid_text_flags_rust", + crate_name: "android_text_flags", + aconfig_declarations: "com.android.text.flags-aconfig", +} + // Location aconfig_declarations { name: "android.location.flags-aconfig", diff --git a/Android.bp b/Android.bp index f0aa62cc37ae..eabd9c7565da 100644 --- a/Android.bp +++ b/Android.bp @@ -417,7 +417,6 @@ java_defaults { "modules-utils-fastxmlserializer", "modules-utils-preconditions", "modules-utils-statemachine", - "modules-utils-synchronous-result-receiver", "modules-utils-os", "modules-utils-uieventlogger-interface", "framework-permission-aidl-java", diff --git a/apct-tests/perftests/multiuser/Android.bp b/apct-tests/perftests/multiuser/Android.bp index 1653edc77de9..856dba3f804c 100644 --- a/apct-tests/perftests/multiuser/Android.bp +++ b/apct-tests/perftests/multiuser/Android.bp @@ -38,3 +38,10 @@ android_test { ], certificate: "platform", } + +filegroup { + name: "multi_user_trace_config", + srcs: [ + "trace_configs/trace_config_multi_user.textproto", + ], +} diff --git a/api/api.go b/api/api.go index f0d1f42f61d4..b6b1a7e44510 100644 --- a/api/api.go +++ b/api/api.go @@ -63,7 +63,6 @@ type CombinedApisProperties struct { type CombinedApis struct { android.ModuleBase - android.DefaultableModuleBase properties CombinedApisProperties } @@ -74,7 +73,6 @@ func init() { func registerBuildComponents(ctx android.RegistrationContext) { ctx.RegisterModuleType("combined_apis", combinedApisModuleFactory) - ctx.RegisterModuleType("combined_apis_defaults", CombinedApisModuleDefaultsFactory) } var PrepareForCombinedApisTest = android.FixtureRegisterWithContext(registerBuildComponents) @@ -576,7 +574,6 @@ func combinedApisModuleFactory() android.Module { module := &CombinedApis{} module.AddProperties(&module.properties) android.InitAndroidModule(module) - android.InitDefaultableModule(module) android.AddLoadHook(module, func(ctx android.LoadHookContext) { module.createInternalModules(ctx) }) return module } @@ -613,16 +610,3 @@ func remove(s []string, v string) []string { } return s2 } - -// Defaults -type CombinedApisModuleDefaults struct { - android.ModuleBase - android.DefaultsModuleBase -} - -func CombinedApisModuleDefaultsFactory() android.Module { - module := &CombinedApisModuleDefaults{} - module.AddProperties(&CombinedApisProperties{}) - android.InitDefaultsModule(module) - return module -} diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 36b1eaba89df..6df971a9cea8 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -2105,8 +2105,7 @@ public final class ActivityThread extends ClientTransactionHandler @Override public void scheduleTaskFragmentTransaction(@NonNull ITaskFragmentOrganizer organizer, @NonNull TaskFragmentTransaction transaction) throws RemoteException { - // TODO(b/260873529): ITaskFragmentOrganizer can be cleanup to be a IBinder token - // after flag removal. + // TODO(b/352665082): ITaskFragmentOrganizer can be cleanup to be a IBinder token organizer.onTransactionReady(transaction); } diff --git a/core/java/android/app/servertransaction/ObjectPool.java b/core/java/android/app/servertransaction/ObjectPool.java index 598bd8a75637..e86ca37b99ca 100644 --- a/core/java/android/app/servertransaction/ObjectPool.java +++ b/core/java/android/app/servertransaction/ObjectPool.java @@ -16,70 +16,39 @@ package android.app.servertransaction; -import com.android.window.flags.Flags; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - /** * An object pool that can provide reused objects if available. + * * @hide + * @deprecated This class is deprecated. Directly create new instances of objects instead of + * obtaining them from this pool. + * TODO(b/311089192): Clean up usages of the pool. */ +@Deprecated class ObjectPool { - private static final Object sPoolSync = new Object(); - private static final Map<Class, ArrayList<? extends ObjectPoolItem>> sPoolMap = - new HashMap<>(); - - private static final int MAX_POOL_SIZE = 50; - /** * Obtain an instance of a specific class from the pool - * @param itemClass The class of the object we're looking for. + * + * @param ignoredItemClass The class of the object we're looking for. * @return An instance or null if there is none. + * @deprecated This method is deprecated. Directly create new instances of objects instead of + * obtaining them from this pool. */ - public static <T extends ObjectPoolItem> T obtain(Class<T> itemClass) { - if (Flags.disableObjectPool()) { - return null; - } - synchronized (sPoolSync) { - @SuppressWarnings("unchecked") - final ArrayList<T> itemPool = (ArrayList<T>) sPoolMap.get(itemClass); - if (itemPool != null && !itemPool.isEmpty()) { - return itemPool.remove(itemPool.size() - 1); - } - return null; - } + @Deprecated + public static <T extends ObjectPoolItem> T obtain(Class<T> ignoredItemClass) { + return null; } /** * Recycle the object to the pool. The object should be properly cleared before this. - * @param item The object to recycle. + * + * @param ignoredItem The object to recycle. * @see ObjectPoolItem#recycle() + * @deprecated This method is deprecated. The object pool is no longer used, so there's + * no need to recycle objects. */ - public static <T extends ObjectPoolItem> void recycle(T item) { - if (Flags.disableObjectPool()) { - return; - } - synchronized (sPoolSync) { - @SuppressWarnings("unchecked") - ArrayList<T> itemPool = (ArrayList<T>) sPoolMap.get(item.getClass()); - if (itemPool == null) { - itemPool = new ArrayList<>(); - sPoolMap.put(item.getClass(), itemPool); - } - // Check if the item is already in the pool - final int size = itemPool.size(); - for (int i = 0; i < size; i++) { - if (itemPool.get(i) == item) { - throw new IllegalStateException("Trying to recycle already recycled item"); - } - } - - if (size < MAX_POOL_SIZE) { - itemPool.add(item); - } - } + @Deprecated + public static <T extends ObjectPoolItem> void recycle(T ignoredItem) { } } diff --git a/core/java/android/app/servertransaction/ObjectPoolItem.java b/core/java/android/app/servertransaction/ObjectPoolItem.java index 17bd4f30640f..0141f6eff53b 100644 --- a/core/java/android/app/servertransaction/ObjectPoolItem.java +++ b/core/java/android/app/servertransaction/ObjectPoolItem.java @@ -18,12 +18,20 @@ package android.app.servertransaction; /** * Base interface for all lifecycle items that can be put in object pool. + * * @hide + * @deprecated This interface is deprecated. Objects should no longer be pooled. + * TODO(b/311089192): Clean up usages of this interface. */ +@Deprecated public interface ObjectPoolItem { /** * Clear the contents of the item and putting it to a pool. The implementation should call * {@link ObjectPool#recycle(ObjectPoolItem)} passing itself. + * + * @deprecated This method is deprecated. The object pool is no longer used, so there's + * no need to recycle objects. */ + @Deprecated void recycle(); } diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java index 0be2d3e30c33..e95c6a44c281 100644 --- a/core/java/android/os/ServiceManager.java +++ b/core/java/android/os/ServiceManager.java @@ -277,7 +277,7 @@ public final class ServiceManager { if (service != null) { return service; } else { - return Binder.allowBlocking(getIServiceManager().checkService(name)); + return Binder.allowBlocking(getIServiceManager().checkService(name).getBinder()); } } catch (RemoteException e) { Log.e(TAG, "error in checkService", e); @@ -425,7 +425,7 @@ public final class ServiceManager { private static IBinder rawGetService(String name) throws RemoteException { final long start = sStatLogger.getTime(); - final IBinder binder = getIServiceManager().getService(name); + final IBinder binder = getIServiceManager().getService(name).getBinder(); final int time = (int) sStatLogger.logDurationStat(Stats.GET_SERVICE, start); diff --git a/core/java/android/os/ServiceManagerNative.java b/core/java/android/os/ServiceManagerNative.java index 7b91dd5822e7..6c9a5c7f9fff 100644 --- a/core/java/android/os/ServiceManagerNative.java +++ b/core/java/android/os/ServiceManagerNative.java @@ -58,12 +58,12 @@ class ServiceManagerProxy implements IServiceManager { } @UnsupportedAppUsage - public IBinder getService(String name) throws RemoteException { + public Service getService(String name) throws RemoteException { // Same as checkService (old versions of servicemanager had both methods). - return mServiceManager.checkService(name); + return checkService(name); } - public IBinder checkService(String name) throws RemoteException { + public Service checkService(String name) throws RemoteException { return mServiceManager.checkService(name); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 2562c8e31095..ff389208a579 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -11075,6 +11075,13 @@ public final class Settings { public static final String MANDATORY_BIOMETRICS = "mandatory_biometrics"; /** + * Whether or not requirements for mandatory biometrics is satisfied. + * @hide + */ + public static final String MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED = + "mandatory_biometrics_requirements_satisfied"; + + /** * Whether or not active unlock triggers on wake. * @hide */ diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index e4fc1cd9dad0..fbeab84fa96d 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -787,7 +787,6 @@ public class DreamService extends Service implements Window.Callback { */ public void setInteractive(boolean interactive) { mInteractive = interactive; - updateAccessibilityMessage(); } /** @@ -1641,9 +1640,9 @@ public class DreamService extends Service implements Window.Callback { if (mWindow == null) return; if (mDreamAccessibility == null) { final View rootView = mWindow.getDecorView(); - mDreamAccessibility = new DreamAccessibility(this, rootView); + mDreamAccessibility = new DreamAccessibility(this, rootView, this::wakeUp); } - mDreamAccessibility.updateAccessibilityConfiguration(isInteractive()); + mDreamAccessibility.updateAccessibilityConfiguration(); } private boolean getWindowFlagValue(int flag, boolean defaultValue) { diff --git a/core/java/android/service/dreams/utils/DreamAccessibility.java b/core/java/android/service/dreams/utils/DreamAccessibility.java index c38f41bab5a6..f504ff7d4650 100644 --- a/core/java/android/service/dreams/utils/DreamAccessibility.java +++ b/core/java/android/service/dreams/utils/DreamAccessibility.java @@ -18,6 +18,7 @@ package android.service.dreams.utils; import android.annotation.NonNull; import android.content.Context; +import android.os.Bundle; import android.view.View; import android.view.accessibility.AccessibilityNodeInfo; @@ -32,22 +33,22 @@ public class DreamAccessibility { private final Context mContext; private final View mView; private final View.AccessibilityDelegate mAccessibilityDelegate; + private final Runnable mDismissCallback; - public DreamAccessibility(@NonNull Context context, @NonNull View view) { + public DreamAccessibility(@NonNull Context context, @NonNull View view, + @NonNull Runnable dismissCallback) { mContext = context; mView = view; mAccessibilityDelegate = createNewAccessibilityDelegate(mContext); + mDismissCallback = dismissCallback; } /** - * @param interactive - * Removes and add accessibility configuration depending if the dream is interactive or not + * Adds default accessibility configuration if none exist on the dream */ - public void updateAccessibilityConfiguration(Boolean interactive) { - if (!interactive) { + public void updateAccessibilityConfiguration() { + if (mView.getAccessibilityDelegate() == null) { addAccessibilityConfiguration(); - } else { - removeCustomAccessibilityAction(); } } @@ -58,31 +59,28 @@ public class DreamAccessibility { mView.setAccessibilityDelegate(mAccessibilityDelegate); } - /** - * Removes Configured the accessibility actions for the given root view. - */ - private void removeCustomAccessibilityAction() { - if (mView.getAccessibilityDelegate() == mAccessibilityDelegate) { - mView.setAccessibilityDelegate(null); - } - } - private View.AccessibilityDelegate createNewAccessibilityDelegate(Context context) { return new View.AccessibilityDelegate() { @Override public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(host, info); - for (AccessibilityNodeInfo.AccessibilityAction action : info.getActionList()) { - if (action.getId() == AccessibilityNodeInfo.ACTION_CLICK) { - info.removeAction(action); - break; - } - } info.addAction(new AccessibilityNodeInfo.AccessibilityAction( - AccessibilityNodeInfo.ACTION_CLICK, + AccessibilityNodeInfo.ACTION_DISMISS, context.getResources().getString(R.string.dream_accessibility_action_click) )); } + + @Override + public boolean performAccessibilityAction(View host, int action, Bundle args) { + switch(action){ + case AccessibilityNodeInfo.ACTION_DISMISS: + if (mDismissCallback != null) { + mDismissCallback.run(); + } + break; + } + return true; + } }; } } diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java index 5d84d17bdb6e..b07534f5fe52 100644 --- a/core/java/android/text/ClientFlags.java +++ b/core/java/android/text/ClientFlags.java @@ -68,4 +68,11 @@ public class ClientFlags { public static boolean fixMisalignedContextMenu() { return TextFlags.isFeatureEnabled(Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU); } + + /** + * @see Flags#clearFontVariationSettings() + */ + public static boolean clearFontVariationSettings() { + return TextFlags.isFeatureEnabled(Flags.FLAG_CLEAR_FONT_VARIATION_SETTINGS); + } } diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java index 9e02460d2637..4dca284b8a4d 100644 --- a/core/java/android/text/TextFlags.java +++ b/core/java/android/text/TextFlags.java @@ -61,6 +61,7 @@ public final class TextFlags { Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE, Flags.FLAG_ICU_BIDI_MIGRATION, Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU, + Flags.FLAG_CLEAR_FONT_VARIATION_SETTINGS, }; /** @@ -75,6 +76,7 @@ public final class TextFlags { Flags.fixLineHeightForLocale(), Flags.icuBidiMigration(), Flags.fixMisalignedContextMenu(), + Flags.clearFontVariationSettings(), }; /** diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig index 8836c8a3a113..02c63db2c8a6 100644 --- a/core/java/android/text/flags/flags.aconfig +++ b/core/java/android/text/flags/flags.aconfig @@ -220,3 +220,23 @@ flag { is_fixed_read_only: true bug: "346915432" } + +flag { + name: "clear_font_variation_settings" + namespace: "text" + description: "The font variation settings must be cleared when the new Typeface is set" + bug: "353609778" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "portuguese_hyphenator" + namespace: "text" + description: "Portuguese taiored hyphenator" + bug: "344656282" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index fedbe4a65e07..42d66ce6bf1b 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -366,13 +366,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall Log.e(TAG, "Received invalid input event"); return; } - try { - vri.processingBackKey(true); - vri.enqueueInputEvent(keyEvent, null /* receiver */, 0 /* flags */, - true /* processImmediately */); - } finally { - vri.processingBackKey(false); - } + vri.enqueueInputEvent(keyEvent, null /* receiver */, 0 /* flags */, + true /* processImmediately */); }); } }; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 3a1d833b686e..2f204f9b1be9 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -727,8 +727,6 @@ public final class ViewRootImpl implements ViewParent, boolean mUpcomingWindowFocus; @GuardedBy("this") boolean mUpcomingInTouchMode; - // While set, allow this VRI to handle back key without drop it. - private boolean mProcessingBackKey; /** * Compatibility {@link OnBackInvokedCallback} for windowless window, to forward the back * key event host app. @@ -7269,7 +7267,7 @@ public final class ViewRootImpl implements ViewParent, // Find a reason for dropping or canceling the event. final String reason; // The embedded window is focused, allow this VRI to handle back key. - if (!mAttachInfo.mHasWindowFocus && !(mProcessingBackKey && isBack(q.mEvent)) + if (!mAttachInfo.mHasWindowFocus && !isBack(q.mEvent) && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER) && !isAutofillUiShowing()) { // This is a non-pointer event and the window doesn't currently have input focus @@ -11218,11 +11216,6 @@ public final class ViewRootImpl implements ViewParent, mHandler.obtainMessage(MSG_REQUEST_SCROLL_CAPTURE, listener).sendToTarget(); } - // Make this VRI able to process back key without drop it. - void processingBackKey(boolean processing) { - mProcessingBackKey = processing; - } - /** * Collect and include any ScrollCaptureCallback instances registered with the window. * @@ -12554,15 +12547,8 @@ public final class ViewRootImpl implements ViewParent, * @return whether the event was handled (i.e. onKeyPreIme consumed it if preImeOnly=true) */ public boolean injectBackKeyEvents(boolean preImeOnly) { - boolean consumed; - try { - processingBackKey(true); - sendBackKeyEvent(KeyEvent.ACTION_DOWN, preImeOnly); - consumed = sendBackKeyEvent(KeyEvent.ACTION_UP, preImeOnly); - } finally { - processingBackKey(false); - } - return consumed; + sendBackKeyEvent(KeyEvent.ACTION_DOWN, preImeOnly); + return sendBackKeyEvent(KeyEvent.ACTION_UP, preImeOnly); } private boolean sendBackKeyEvent(int action, boolean preImeOnly) { diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 9512347b0143..0dadbe374aa4 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -7438,8 +7438,7 @@ public class RemoteViews implements Parcelable, Filter { // If the user interacts with a visible element it is safe to assume they consent that // something is going to start. opts.setPendingIntentBackgroundActivityStartMode( - ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); - opts.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true); + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS); return Pair.create(intent, opts); } } diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig index 48fb2b3ab129..f739622a1b92 100644 --- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig +++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig @@ -98,6 +98,13 @@ flag { } flag { + name: "scrolling_from_letterbox" + namespace: "large_screen_experiences_app_compat" + description: "Whether to enable app scrolling from gestures from letterbox area" + bug: "353697519" +} + +flag { name: "app_compat_refactoring" namespace: "large_screen_experiences_app_compat" description: "Whether the changes about app compat refactoring are enabled./n" diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index ae9d757e1e0b..13d465f183c9 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -3,16 +3,6 @@ container: "system" # Project link: https://gantry.corp.google.com/projects/android_platform_windowing_sdk/changes -# Using a fixed read only flag because there are ClientTransaction scheduling before -# WindowManagerService creation. -flag { - namespace: "windowing_sdk" - name: "bundle_client_transaction_flag" - description: "To bundle multiple ClientTransactionItems into one ClientTransaction" - bug: "260873529" - is_fixed_read_only: true -} - flag { namespace: "windowing_sdk" name: "activity_embedding_overlay_presentation_flag" diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java index 24971f51aabf..488e06f9a1ad 100644 --- a/core/java/com/android/internal/os/PowerStats.java +++ b/core/java/com/android/internal/os/PowerStats.java @@ -580,10 +580,15 @@ public final class PowerStats { } PowerStatsFormatter uidStatsFormatter = descriptor.getUidStatsFormatter(); for (int i = 0; i < uidStats.size(); i++) { + String formattedStats = uidStatsFormatter.format(uidStats.valueAt(i)); + if (formattedStats.isBlank()) { + continue; + } + pw.print("UID "); pw.print(UserHandle.formatUid(uidStats.keyAt(i))); pw.print(": "); - pw.print(uidStatsFormatter.format(uidStats.valueAt(i))); + pw.print(formattedStats); pw.println(); } pw.decreaseIndent(); diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java index 918235bafd16..e429cfc687bb 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java @@ -21,12 +21,7 @@ import static android.app.servertransaction.TestUtils.mergedConfig; import static android.app.servertransaction.TestUtils.referrerIntentList; import static android.app.servertransaction.TestUtils.resultInfoList; -import static com.android.window.flags.Flags.FLAG_DISABLE_OBJECT_POOL; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertSame; import android.annotation.NonNull; import android.app.ActivityOptions; @@ -41,14 +36,11 @@ import android.os.Bundle; import android.os.IBinder; import android.os.PersistableBundle; import android.platform.test.annotations.Presubmit; -import android.platform.test.flag.junit.FlagsParameterization; -import android.platform.test.flag.junit.SetFlagsRule; import android.window.ActivityWindowInfo; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import com.android.window.flags.Flags; - import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -56,12 +48,8 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; -import java.util.List; import java.util.function.Supplier; -import platform.test.runner.parameterized.ParameterizedAndroidJunit4; -import platform.test.runner.parameterized.Parameters; - /** * Tests for {@link ObjectPool}. * @@ -71,31 +59,19 @@ import platform.test.runner.parameterized.Parameters; * <p>This test class is a part of Window Manager Service tests and specified in * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}. */ -@RunWith(ParameterizedAndroidJunit4.class) +@RunWith(AndroidJUnit4.class) @SmallTest @Presubmit public class ObjectPoolTests { - @Parameters(name = "{0}") - public static List<FlagsParameterization> getParams() { - return FlagsParameterization.allCombinationsOf(FLAG_DISABLE_OBJECT_POOL); - } - @Rule public final MockitoRule mocks = MockitoJUnit.rule(); - @Rule - public SetFlagsRule mSetFlagsRule; - @Mock private IApplicationThread mApplicationThread; @Mock private IBinder mActivityToken; - public ObjectPoolTests(FlagsParameterization flags) { - mSetFlagsRule = new SetFlagsRule(flags); - } - // 1. Check if two obtained objects from pool are not the same. // 2. Check if the state of the object is cleared after recycling. // 3. Check if the same object is obtained from pool after recycling. @@ -219,30 +195,11 @@ public class ObjectPoolTests { item.recycle(); final ObjectPoolItem item2 = obtain.get(); - if (Flags.disableObjectPool()) { - assertNotSame(item, item2); // Different instance. - } else { - assertSame(item, item2); - } + assertNotSame(item, item2); // Different instance. // Create new object when the pool is empty. final ObjectPoolItem item3 = obtain.get(); assertNotSame(item, item3); - if (Flags.disableObjectPool()) { - // Skip recycle if flag enabled, compare unnecessary. - return; - } - assertEquals(item, item3); - - // Reset fields after recycle. - item.recycle(); - - assertNotEquals(item, item3); - - // Recycled objects are equal. - item3.recycle(); - - assertEquals(item, item3); } } diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index df95a91d72d7..b83931fa0615 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -35,6 +35,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.graphics.fonts.FontVariationAxis; import android.os.Build; import android.os.LocaleList; +import android.text.ClientFlags; import android.text.GraphicsOperations; import android.text.SpannableString; import android.text.SpannedString; @@ -1540,8 +1541,21 @@ public class Paint { * @return typeface */ public Typeface setTypeface(Typeface typeface) { + return setTypefaceInternal(typeface, true); + } + + private Typeface setTypefaceInternal(Typeface typeface, boolean clearFontVariationSettings) { final long typefaceNative = typeface == null ? 0 : typeface.native_instance; nSetTypeface(mNativePaint, typefaceNative); + + if (ClientFlags.clearFontVariationSettings()) { + if (clearFontVariationSettings && !Objects.equals(mTypeface, typeface)) { + // We cannot call setFontVariationSetting with empty string or null because it calls + // setTypeface method. To avoid recursive setTypeface call, manually resetting + // mFontVariationSettings. + mFontVariationSettings = null; + } + } mTypeface = typeface; return typeface; } @@ -2037,6 +2051,14 @@ public class Paint { * </li> * </ul> * + * Note: This method replaces the Typeface previously set to this instance. + * Until API {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, any caller of + * {@link #setTypeface(Typeface)} should call this method with empty settings, then call + * {@link #setTypeface(Typeface)}, then call this method with preferred variation settings. + * The device API more than {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, the + * {@link #setTypeface(Typeface)} method clears font variation settings. So caller of + * {@link #setTypeface(Typeface)} should call this method again for applying variation settings. + * * @param fontVariationSettings font variation settings. You can pass null or empty string as * no variation settings. * @@ -2059,8 +2081,8 @@ public class Paint { if (settings == null || settings.length() == 0) { mFontVariationSettings = null; - setTypeface(Typeface.createFromTypefaceWithVariation(mTypeface, - Collections.emptyList())); + setTypefaceInternal(Typeface.createFromTypefaceWithVariation(mTypeface, + Collections.emptyList()), false); return true; } @@ -2078,7 +2100,8 @@ public class Paint { return false; } mFontVariationSettings = settings; - setTypeface(Typeface.createFromTypefaceWithVariation(targetTypeface, filteredAxes)); + setTypefaceInternal(Typeface.createFromTypefaceWithVariation(targetTypeface, filteredAxes), + false); return true; } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java index ecf47209a802..7f11feaa585e 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java @@ -39,6 +39,8 @@ import androidx.window.extensions.embedding.SplitController; import androidx.window.extensions.layout.WindowLayoutComponent; import androidx.window.extensions.layout.WindowLayoutComponentImpl; +import com.android.window.flags.Flags; + import java.util.Objects; @@ -55,11 +57,9 @@ class WindowExtensionsImpl implements WindowExtensions { */ private static final int NO_LEVEL_OVERRIDE = -1; - /** - * The min version of the WM Extensions that must be supported in the current platform version. - */ - @VisibleForTesting - static final int EXTENSIONS_VERSION_CURRENT_PLATFORM = 6; + private static final int EXTENSIONS_VERSION_V7 = 7; + + private static final int EXTENSIONS_VERSION_V6 = 6; private final Object mLock = new Object(); private volatile DeviceStateManagerFoldingFeatureProducer mFoldingFeatureProducer; @@ -67,7 +67,6 @@ class WindowExtensionsImpl implements WindowExtensions { private volatile SplitController mSplitController; private volatile WindowAreaComponent mWindowAreaComponent; - private final int mVersion = EXTENSIONS_VERSION_CURRENT_PLATFORM; private final boolean mIsActivityEmbeddingEnabled; WindowExtensionsImpl() { @@ -76,9 +75,22 @@ class WindowExtensionsImpl implements WindowExtensions { Log.i(TAG, generateLogMessage()); } + /** + * The min version of the WM Extensions that must be supported in the current platform version. + */ + @VisibleForTesting + static int getExtensionsVersionCurrentPlatform() { + if (Flags.activityEmbeddingAnimationCustomizationFlag()) { + // Activity Embedding animation customization is the only major feature for v7. + return EXTENSIONS_VERSION_V7; + } else { + return EXTENSIONS_VERSION_V6; + } + } + private String generateLogMessage() { final StringBuilder logBuilder = new StringBuilder("Initializing Window Extensions, " - + "vendor API level=" + mVersion); + + "vendor API level=" + getExtensionsVersionCurrentPlatform()); final int levelOverride = getLevelOverride(); if (levelOverride != NO_LEVEL_OVERRIDE) { logBuilder.append(", override to ").append(levelOverride); @@ -91,7 +103,12 @@ class WindowExtensionsImpl implements WindowExtensions { @Override public int getVendorApiLevel() { final int levelOverride = getLevelOverride(); - return (levelOverride != NO_LEVEL_OVERRIDE) ? levelOverride : mVersion; + return hasLevelOverride() ? levelOverride : getExtensionsVersionCurrentPlatform(); + } + + @VisibleForTesting + boolean hasLevelOverride() { + return getLevelOverride() != NO_LEVEL_OVERRIDE; } private int getLevelOverride() { diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java index c5aaddc4e0ed..92f48141b607 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java @@ -16,7 +16,7 @@ package androidx.window.extensions; -import static androidx.window.extensions.WindowExtensionsImpl.EXTENSIONS_VERSION_CURRENT_PLATFORM; +import static androidx.window.extensions.WindowExtensionsImpl.getExtensionsVersionCurrentPlatform; import static com.google.common.truth.Truth.assertThat; @@ -59,7 +59,8 @@ public class WindowExtensionsTest { @Test public void testGetVendorApiLevel_extensionsEnabled_matchesCurrentVersion() { assumeTrue(WindowManager.hasWindowExtensionsEnabled()); - assertThat(mVersion).isEqualTo(EXTENSIONS_VERSION_CURRENT_PLATFORM); + assumeFalse(((WindowExtensionsImpl) mExtensions).hasLevelOverride()); + assertThat(mVersion).isEqualTo(getExtensionsVersionCurrentPlatform()); } @Test diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt index f0d80a02243a..d3fc49bcb766 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt @@ -39,8 +39,6 @@ enum class DesktopModeFlags( /** * Determines state of flag based on the actual flag and desktop mode developer option overrides. - * - * Note, this method makes sure that a constant developer toggle overrides is read until reboot. */ fun isEnabled(context: Context): Boolean = if (!Flags.showDesktopWindowingDevOption() || @@ -65,7 +63,7 @@ enum class DesktopModeFlags( ?: run { val override = getToggleOverrideFromSystem(context) // Cache toggle override the first time we encounter context. Override does not change - // with context, as context is just used to fetch System Property and Settings.Global + // with context, as context is just used to fetch Settings.Global cachedToggleOverride = override Log.d(TAG, "Toggle override initialized to: $override") override @@ -74,29 +72,13 @@ enum class DesktopModeFlags( return override } - private fun getToggleOverrideFromSystem(context: Context): ToggleOverride { - // A non-persistent System Property is used to store override to ensure it remains - // constant till reboot. - val overrideFromSystemProperties: ToggleOverride? = - System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, null).convertToToggleOverride() - return overrideFromSystemProperties - ?: run { - // Read Setting Global if System Property is not present (just after reboot) - // or not valid (user manually changed the value) - val overrideFromSettingsGlobal = - convertToToggleOverrideWithFallback( - Settings.Global.getInt( - context.contentResolver, - Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, - ToggleOverride.OVERRIDE_UNSET.setting), - ToggleOverride.OVERRIDE_UNSET) - // Initialize System Property - System.setProperty( - SYSTEM_PROPERTY_OVERRIDE_KEY, overrideFromSettingsGlobal.setting.toString()) - - overrideFromSettingsGlobal - } - } + private fun getToggleOverrideFromSystem(context: Context): ToggleOverride = + convertToToggleOverrideWithFallback( + Settings.Global.getInt( + context.contentResolver, + Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, + ToggleOverride.OVERRIDE_UNSET.setting), + ToggleOverride.OVERRIDE_UNSET) /** * Override state of desktop mode developer option toggle. @@ -113,27 +95,12 @@ enum class DesktopModeFlags( OVERRIDE_ON(1) } - private fun String?.convertToToggleOverride(): ToggleOverride? { - val intValue = this?.toIntOrNull() ?: return null - return settingToToggleOverrideMap[intValue] - ?: run { - Log.w(TAG, "Unknown toggleOverride int $intValue") - null - } - } - companion object { private const val TAG = "DesktopModeFlags" /** - * Key for non-persistent System Property which is used to store desktop windowing developer - * option overrides. - */ - private const val SYSTEM_PROPERTY_OVERRIDE_KEY = "sys.wmshell.desktopmode.dev_toggle_override" - - /** * Local cache for toggle override, which is initialized once on its first access. It needs to - * be refreshed only on reboots as overridden state takes effect on reboots. + * be refreshed only on reboots as overridden state is expected to take effect on reboots. */ private var cachedToggleOverride: ToggleOverride? = null diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java index 64a1b0c804da..140d7765e5c1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java @@ -30,6 +30,7 @@ import android.graphics.Rect; import android.os.RemoteException; import android.util.ArraySet; import android.util.Size; +import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLog; @@ -42,9 +43,7 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.function.Consumer; @@ -69,26 +68,36 @@ public class PipBoundsState { @Retention(RetentionPolicy.SOURCE) public @interface StashType {} + public static final int NAMED_KCA_LAUNCHER_SHELF = 0; + public static final int NAMED_KCA_TABLETOP_MODE = 1; + + @IntDef(prefix = { "NAMED_KCA_" }, value = { + NAMED_KCA_LAUNCHER_SHELF, + NAMED_KCA_TABLETOP_MODE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface NamedKca {} + private static final String TAG = PipBoundsState.class.getSimpleName(); - private final @NonNull Rect mBounds = new Rect(); - private final @NonNull Rect mMovementBounds = new Rect(); - private final @NonNull Rect mNormalBounds = new Rect(); - private final @NonNull Rect mExpandedBounds = new Rect(); - private final @NonNull Rect mNormalMovementBounds = new Rect(); - private final @NonNull Rect mExpandedMovementBounds = new Rect(); - private final @NonNull PipDisplayLayoutState mPipDisplayLayoutState; + @NonNull private final Rect mBounds = new Rect(); + @NonNull private final Rect mMovementBounds = new Rect(); + @NonNull private final Rect mNormalBounds = new Rect(); + @NonNull private final Rect mExpandedBounds = new Rect(); + @NonNull private final Rect mNormalMovementBounds = new Rect(); + @NonNull private final Rect mExpandedMovementBounds = new Rect(); + @NonNull private final PipDisplayLayoutState mPipDisplayLayoutState; private final Point mMaxSize = new Point(); private final Point mMinSize = new Point(); - private final @NonNull Context mContext; + @NonNull private final Context mContext; private float mAspectRatio; private int mStashedState = STASH_TYPE_NONE; private int mStashOffset; - private @Nullable PipReentryState mPipReentryState; + @Nullable private PipReentryState mPipReentryState; private final LauncherState mLauncherState = new LauncherState(); - private final @NonNull SizeSpecSource mSizeSpecSource; - private @Nullable ComponentName mLastPipComponentName; - private final @NonNull MotionBoundsState mMotionBoundsState = new MotionBoundsState(); + @NonNull private final SizeSpecSource mSizeSpecSource; + @Nullable private ComponentName mLastPipComponentName; + @NonNull private final MotionBoundsState mMotionBoundsState = new MotionBoundsState(); private boolean mIsImeShowing; private int mImeHeight; private boolean mIsShelfShowing; @@ -120,12 +129,18 @@ public class PipBoundsState { * as unrestricted keep clear area. Values in this map would be appended to * {@link #getUnrestrictedKeepClearAreas()} and this is meant for internal usage only. */ - private final Map<String, Rect> mNamedUnrestrictedKeepClearAreas = new HashMap<>(); + private final SparseArray<Rect> mNamedUnrestrictedKeepClearAreas = new SparseArray<>(); - private @Nullable Runnable mOnMinimalSizeChangeCallback; - private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback; - private List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>(); - private List<Consumer<Float>> mOnAspectRatioChangedCallbacks = new ArrayList<>(); + @Nullable private Runnable mOnMinimalSizeChangeCallback; + @Nullable private TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback; + private final List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>(); + private final List<Consumer<Float>> mOnAspectRatioChangedCallbacks = new ArrayList<>(); + + /** + * This is used to set the launcher shelf height ahead of non-auto-enter-pip animation, + * to avoid the race condition. See also {@link #NAMED_KCA_LAUNCHER_SHELF}. + */ + public final Rect mCachedLauncherShelfHeightKeepClearArea = new Rect(); // the size of the current bounds relative to the max size spec private float mBoundsScale; @@ -430,17 +445,32 @@ public class PipBoundsState { mUnrestrictedKeepClearAreas.addAll(unrestrictedAreas); } - /** Add a named unrestricted keep clear area. */ - public void addNamedUnrestrictedKeepClearArea(@NonNull String name, Rect unrestrictedArea) { - mNamedUnrestrictedKeepClearAreas.put(name, unrestrictedArea); + /** Set a named unrestricted keep clear area. */ + public void setNamedUnrestrictedKeepClearArea( + @NamedKca int tag, @Nullable Rect unrestrictedArea) { + if (unrestrictedArea == null) { + mNamedUnrestrictedKeepClearAreas.remove(tag); + } else { + mNamedUnrestrictedKeepClearAreas.put(tag, unrestrictedArea); + if (tag == NAMED_KCA_LAUNCHER_SHELF) { + mCachedLauncherShelfHeightKeepClearArea.set(unrestrictedArea); + } + } } - /** Remove a named unrestricted keep clear area. */ - public void removeNamedUnrestrictedKeepClearArea(@NonNull String name) { - mNamedUnrestrictedKeepClearAreas.remove(name); + /** + * Forcefully set the keep-clear-area for launcher shelf height if applicable. + * This is used for entering PiP in button navigation mode to make sure the destination bounds + * calculation includes the shelf height, to avoid race conditions that such callback is sent + * from Launcher after the entering animation is started. + */ + public void mayUseCachedLauncherShelfHeight() { + if (!mCachedLauncherShelfHeightKeepClearArea.isEmpty()) { + setNamedUnrestrictedKeepClearArea( + NAMED_KCA_LAUNCHER_SHELF, mCachedLauncherShelfHeightKeepClearArea); + } } - /** * @return restricted keep clear areas. */ @@ -454,9 +484,12 @@ public class PipBoundsState { */ @NonNull public Set<Rect> getUnrestrictedKeepClearAreas() { - if (mNamedUnrestrictedKeepClearAreas.isEmpty()) return mUnrestrictedKeepClearAreas; + if (mNamedUnrestrictedKeepClearAreas.size() == 0) return mUnrestrictedKeepClearAreas; final Set<Rect> unrestrictedAreas = new ArraySet<>(mUnrestrictedKeepClearAreas); - unrestrictedAreas.addAll(mNamedUnrestrictedKeepClearAreas.values()); + for (int i = 0; i < mNamedUnrestrictedKeepClearAreas.size(); i++) { + final int key = mNamedUnrestrictedKeepClearAreas.keyAt(i); + unrestrictedAreas.add(mNamedUnrestrictedKeepClearAreas.get(key)); + } return unrestrictedAreas; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 80f6a637ba1c..700742acfb43 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -534,7 +534,8 @@ public abstract class WMShellModule { MultiInstanceHelper multiInstanceHelper, @ShellMainThread ShellExecutor mainExecutor, Optional<DesktopTasksLimiter> desktopTasksLimiter, - Optional<RecentTasksController> recentTasksController) { + Optional<RecentTasksController> recentTasksController, + InteractionJankMonitor interactionJankMonitor) { return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController, displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, dragAndDropController, transitions, keyguardManager, enterDesktopTransitionHandler, @@ -542,7 +543,8 @@ public abstract class WMShellModule { dragToDesktopTransitionHandler, desktopModeTaskRepository, desktopModeLoggerTransitionObserver, launchAdjacentController, recentsTransitionHandler, multiInstanceHelper, - mainExecutor, desktopTasksLimiter, recentTasksController.orElse(null)); + mainExecutor, desktopTasksLimiter, recentTasksController.orElse(null), + interactionJankMonitor); } @WMSingleton @@ -568,9 +570,10 @@ public abstract class WMShellModule { Context context, Transitions transitions, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, - Optional<DesktopTasksLimiter> desktopTasksLimiter) { + Optional<DesktopTasksLimiter> desktopTasksLimiter, + InteractionJankMonitor interactionJankMonitor) { return new DragToDesktopTransitionHandler(context, transitions, - rootTaskDisplayAreaOrganizer); + rootTaskDisplayAreaOrganizer, interactionJankMonitor); } @WMSingleton 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 de901b5dabd8..9fd2c274df2d 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 @@ -49,6 +49,9 @@ import android.window.TransitionRequestInfo import android.window.WindowContainerTransaction import androidx.annotation.BinderThread import com.android.internal.annotations.VisibleForTesting +import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD +import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE +import com.android.internal.jank.InteractionJankMonitor import com.android.internal.policy.ScreenDecorationsUtils import com.android.internal.protolog.ProtoLog import com.android.window.flags.Flags @@ -123,7 +126,8 @@ class DesktopTasksController( private val multiInstanceHelper: MultiInstanceHelper, @ShellMainThread private val mainExecutor: ShellExecutor, private val desktopTasksLimiter: Optional<DesktopTasksLimiter>, - private val recentTasksController: RecentTasksController? + private val recentTasksController: RecentTasksController?, + private val interactionJankMonitor: InteractionJankMonitor ) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler, @@ -378,12 +382,15 @@ class DesktopTasksController( fun startDragToDesktop( taskInfo: RunningTaskInfo, dragToDesktopValueAnimator: MoveToDesktopAnimator, + taskSurface: SurfaceControl, ) { ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: startDragToDesktop taskId=%d", taskInfo.taskId ) + interactionJankMonitor.begin(taskSurface, context, + CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD) dragToDesktopTransitionHandler.startDragToDesktopTransition( taskInfo.taskId, dragToDesktopValueAnimator @@ -1340,13 +1347,19 @@ class DesktopTasksController( fun onDragPositioningEndThroughStatusBar( inputCoordinates: PointF, taskInfo: RunningTaskInfo, + taskSurface: SurfaceControl, ): IndicatorType { + // End the drag_hold CUJ interaction. + interactionJankMonitor.end(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD) val indicator = getVisualIndicator() ?: return IndicatorType.NO_INDICATOR val indicatorType = indicator.updateIndicatorType(inputCoordinates, taskInfo.windowingMode) when (indicatorType) { IndicatorType.TO_DESKTOP_INDICATOR -> { val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return IndicatorType.NO_INDICATOR + // Start a new jank interaction for the drag release to desktop window animation. + interactionJankMonitor.begin(taskSurface, context, + CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE, "to_desktop") if (Flags.enableWindowingDynamicInitialBounds()) { finalizeDragToDesktop(taskInfo, calculateInitialBounds(displayLayout, taskInfo)) } else { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt index ddee8fac8f44..9e79eddb0e59 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt @@ -30,6 +30,9 @@ import android.window.TransitionRequestInfo import android.window.WindowContainerToken import android.window.WindowContainerTransaction import com.android.internal.protolog.ProtoLog +import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD +import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE +import com.android.internal.jank.InteractionJankMonitor import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT @@ -57,17 +60,20 @@ class DragToDesktopTransitionHandler( private val context: Context, private val transitions: Transitions, private val taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, - private val transactionSupplier: Supplier<SurfaceControl.Transaction> + private val interactionJankMonitor: InteractionJankMonitor, + private val transactionSupplier: Supplier<SurfaceControl.Transaction>, ) : TransitionHandler { constructor( context: Context, transitions: Transitions, - rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer + rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, + interactionJankMonitor: InteractionJankMonitor ) : this( context, transitions, rootTaskDisplayAreaOrganizer, + interactionJankMonitor, Supplier { SurfaceControl.Transaction() } ) @@ -567,6 +573,8 @@ class DragToDesktopTransitionHandler( onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId) startTransitionFinishCb.onTransitionFinished(null /* null */) clearState() + interactionJankMonitor.end( + CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE) } } ) @@ -604,6 +612,10 @@ class DragToDesktopTransitionHandler( "DragToDesktop: onTransitionConsumed() start transition aborted" ) state.startAborted = true + // Cancel CUJ interaction if the transition is aborted. + interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD) + } else if (state.cancelTransitionToken != transition) { + interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE) } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 9bb9d868243b..a52141c5f9d6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -1020,6 +1020,9 @@ public class PipTransition extends PipTransitionController { mPipMenuController.attach(leash); } + // Make sure we have the launcher shelf into destination bounds calculation + // before the animator starts. + mPipBoundsState.mayUseCachedLauncherShelfHeight(); final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); final Rect currentBounds = pipChange.getStartAbsBounds(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 0cb7e17e41e9..7451d2251588 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -646,9 +646,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb }); mTabletopModeController.registerOnTabletopModeChangedListener((isInTabletopMode) -> { - final String tag = "tabletop-mode"; if (!isInTabletopMode) { - mPipBoundsState.removeNamedUnrestrictedKeepClearArea(tag); + mPipBoundsState.setNamedUnrestrictedKeepClearArea( + PipBoundsState.NAMED_KCA_TABLETOP_MODE, null); return; } @@ -657,14 +657,16 @@ public class PipController implements PipTransitionController.PipTransitionCallb if (mTabletopModeController.getPreferredHalfInTabletopMode() == TabletopModeController.PREFERRED_TABLETOP_HALF_TOP) { // Prefer top, avoid the bottom half of the display. - mPipBoundsState.addNamedUnrestrictedKeepClearArea(tag, new Rect( - displayBounds.left, displayBounds.centerY(), - displayBounds.right, displayBounds.bottom)); + mPipBoundsState.setNamedUnrestrictedKeepClearArea( + PipBoundsState.NAMED_KCA_TABLETOP_MODE, new Rect( + displayBounds.left, displayBounds.centerY(), + displayBounds.right, displayBounds.bottom)); } else { // Prefer bottom, avoid the top half of the display. - mPipBoundsState.addNamedUnrestrictedKeepClearArea(tag, new Rect( - displayBounds.left, displayBounds.top, - displayBounds.right, displayBounds.centerY())); + mPipBoundsState.setNamedUnrestrictedKeepClearArea( + PipBoundsState.NAMED_KCA_TABLETOP_MODE, new Rect( + displayBounds.left, displayBounds.top, + displayBounds.right, displayBounds.centerY())); } // Try to move the PiP window if we have entered PiP mode. @@ -916,10 +918,12 @@ public class PipController implements PipTransitionController.PipTransitionCallb 0, mPipBoundsState.getDisplayBounds().bottom - height, mPipBoundsState.getDisplayBounds().right, mPipBoundsState.getDisplayBounds().bottom); - mPipBoundsState.addNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG, rect); + mPipBoundsState.setNamedUnrestrictedKeepClearArea( + PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, rect); updatePipPositionForKeepClearAreas(); } else { - mPipBoundsState.removeNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG); + mPipBoundsState.setNamedUnrestrictedKeepClearArea( + PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, null); // postpone moving in response to hide of Launcher in case there's another change mMainExecutor.removeCallbacks(mMovePipInResponseToKeepClearAreasChangeCallback); mMainExecutor.executeDelayed( @@ -968,8 +972,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb int launcherRotation, Rect hotseatKeepClearArea) { // preemptively add the keep clear area for Hotseat, so that it is taken into account // when calculating the entry destination bounds of PiP window - mPipBoundsState.addNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG, - hotseatKeepClearArea); + mPipBoundsState.setNamedUnrestrictedKeepClearArea( + PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, hotseatKeepClearArea); onDisplayRotationChangedNotInPip(mContext, launcherRotation); // cache current min/max size Point minSize = mPipBoundsState.getMinSize(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index b3dab8527617..48d17ec6963f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -68,6 +68,7 @@ class SplitScreenTransitions { DismissSession mPendingDismiss = null; EnterSession mPendingEnter = null; TransitSession mPendingResize = null; + TransitSession mPendingRemotePassthrough = null; private IBinder mAnimatingTransition = null; private OneShotRemoteHandler mActiveRemoteHandler = null; @@ -320,6 +321,11 @@ class SplitScreenTransitions { return mPendingResize != null && mPendingResize.mTransition == transition; } + boolean isPendingPassThrough(IBinder transition) { + return mPendingRemotePassthrough != null && + mPendingRemotePassthrough.mTransition == transition; + } + @Nullable private TransitSession getPendingTransition(IBinder transition) { if (isPendingEnter(transition)) { @@ -331,6 +337,9 @@ class SplitScreenTransitions { } else if (isPendingResize(transition)) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "\tresolved resize transition"); return mPendingResize; + } else if (isPendingPassThrough(transition)) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "\tresolved passThrough transition"); + return mPendingRemotePassthrough; } return null; } @@ -378,6 +387,19 @@ class SplitScreenTransitions { extraTransitType, resizeAnim); } + /** Sets a transition to enter split. */ + void setRemotePassThroughTransition(@NonNull IBinder transition, + @Nullable RemoteTransition remoteTransition) { + mPendingRemotePassthrough = new TransitSession( + transition, null, null, + remoteTransition, Transitions.TRANSIT_SPLIT_PASSTHROUGH); + + ProtoLog.v(WM_SHELL_TRANSITIONS, " splitTransition " + + " deduced remote passthrough split screen"); + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setRemotePassThrough: transitType=%d remote=%s", + Transitions.TRANSIT_SPLIT_PASSTHROUGH, remoteTransition); + } + /** Starts a transition to dismiss split. */ IBinder startDismissTransition(WindowContainerTransaction wct, Transitions.TransitionHandler handler, @SplitScreen.StageType int dismissTop, @@ -474,6 +496,12 @@ class SplitScreenTransitions { mPendingResize.onConsumed(aborted); mPendingResize = null; ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for resize transition"); + } else if (isPendingPassThrough(transition)) { + mPendingRemotePassthrough.onConsumed(aborted); + mPendingRemotePassthrough.mRemoteHandler.onTransitionConsumed(transition, aborted, + finishT); + mPendingRemotePassthrough = null; + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for passThrough transition"); } // TODO: handle transition consumed for active remote handler @@ -495,6 +523,10 @@ class SplitScreenTransitions { mPendingResize.onFinished(wct, mFinishTransaction); mPendingResize = null; ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinish for resize transition"); + } else if (isPendingPassThrough(mAnimatingTransition)) { + mPendingRemotePassthrough.onFinished(wct, mFinishTransaction); + mPendingRemotePassthrough = null; + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinish for passThrough transition"); } mActiveRemoteHandler = null; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index a4f32c45c0a9..d7ee563b562c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -2710,7 +2710,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Nullable TransitionRequestInfo request) { final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask(); if (triggerTask == null) { - if (isSplitScreenVisible()) { + if (isSplitActive()) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d display rotation", request.getDebugId()); // Check if the display is rotating. @@ -2720,6 +2720,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, && displayChange.getStartRotation() != displayChange.getEndRotation()) { mSplitLayout.setFreezeDividerWindow(true); } + if (request.getRemoteTransition() != null) { + mSplitTransitions.setRemotePassThroughTransition(transition, + request.getRemoteTransition()); + } // Still want to monitor everything while in split-screen, so return non-null. return new WindowContainerTransaction(); } else { @@ -3046,6 +3050,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, notifySplitAnimationFinished(); return true; } + } else if (mSplitTransitions.isPendingPassThrough(transition)) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, + "startAnimation: passThrough transition=%d", info.getDebugId()); + mSplitTransitions.mPendingRemotePassthrough.mRemoteHandler.startAnimation(transition, + info, startTransaction, finishTransaction, finishCallback); + notifySplitAnimationFinished(); + return true; } return startPendingAnimation(transition, info, startTransaction, finishTransaction, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index fc8b1d27ad02..874cca523ad1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -190,6 +190,9 @@ public class Transitions implements RemoteCallable<Transitions>, // TRANSIT_FIRST_CUSTOM + 17 TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_DRAG_RESIZE; + /** Remote Transition that split accepts but ultimately needs to be animated by the remote. */ + public static final int TRANSIT_SPLIT_PASSTHROUGH = TRANSIT_FIRST_CUSTOM + 18; + /** Transition type for desktop mode transitions. */ public static final int TRANSIT_DESKTOP_MODE_TYPES = WindowManager.TRANSIT_FIRST_CUSTOM + 100; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 8312aef110b9..0e8fd7c7d0a1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -970,7 +970,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { relevantDecor.updateHoverAndPressStatus(ev); DesktopModeVisualIndicator.IndicatorType resultType = mDesktopTasksController.onDragPositioningEndThroughStatusBar( - new PointF(ev.getRawX(), ev.getRawY()), relevantDecor.mTaskInfo); + new PointF(ev.getRawX(), ev.getRawY()), + relevantDecor.mTaskInfo, + relevantDecor.mTaskSurface); // If we are entering split select, handle will no longer be visible and // should not be receiving any input. if (resultType == TO_SPLIT_LEFT_INDICATOR @@ -1010,7 +1012,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mContext, mDragToDesktopAnimationStartBounds, relevantDecor.mTaskInfo, relevantDecor.mTaskSurface); mDesktopTasksController.startDragToDesktop(relevantDecor.mTaskInfo, - mMoveToDesktopAnimator); + mMoveToDesktopAnimator, relevantDecor.mTaskSurface); } } if (mMoveToDesktopAnimator != null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java index da268988bac7..3fd3656ccbc5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java @@ -19,7 +19,11 @@ package com.android.wm.shell.windowdecor; import static android.view.InputDevice.SOURCE_TOUCHSCREEN; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; +import static android.view.MotionEvent.ACTION_HOVER_ENTER; +import static android.view.MotionEvent.ACTION_HOVER_EXIT; +import static android.view.MotionEvent.ACTION_HOVER_MOVE; import static android.view.MotionEvent.ACTION_MOVE; +import static android.view.MotionEvent.ACTION_POINTER_UP; import static android.view.MotionEvent.ACTION_UP; import android.graphics.PointF; @@ -43,7 +47,7 @@ class DragDetector { private final PointF mInputDownPoint = new PointF(); private int mTouchSlop; private boolean mIsDragEvent; - private int mDragPointerId; + private int mDragPointerId = -1; private boolean mResultOfDownAction; @@ -67,7 +71,7 @@ class DragDetector { * * @return the result returned by {@link #mEventHandler}, or the result when * {@link #mEventHandler} handles the previous down event if the event shouldn't be passed - */ + */ boolean onMotionEvent(View v, MotionEvent ev) { final boolean isTouchScreen = (ev.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN; @@ -86,10 +90,14 @@ class DragDetector { return mResultOfDownAction; } case ACTION_MOVE: { - if (ev.findPointerIndex(mDragPointerId) == -1) { - mDragPointerId = ev.getPointerId(0); + if (mDragPointerId == -1) { + // The primary pointer was lifted, ignore the rest of the gesture. + return mResultOfDownAction; } final int dragPointerIndex = ev.findPointerIndex(mDragPointerId); + if (dragPointerIndex == -1) { + throw new IllegalStateException("Failed to find primary pointer!"); + } if (!mIsDragEvent) { float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x; float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y; @@ -99,22 +107,52 @@ class DragDetector { } // The event handler should only be notified about 'move' events if a drag has been // detected. - if (mIsDragEvent) { - return mEventHandler.handleMotionEvent(v, ev); - } else { + if (!mIsDragEvent) { return mResultOfDownAction; } + return mEventHandler.handleMotionEvent(v, + getSinglePointerEvent(ev, mDragPointerId)); + } + case ACTION_HOVER_ENTER: + case ACTION_HOVER_MOVE: + case ACTION_HOVER_EXIT: { + return mEventHandler.handleMotionEvent(v, + getSinglePointerEvent(ev, mDragPointerId)); + } + case ACTION_POINTER_UP: { + if (mDragPointerId == -1) { + // The primary pointer was lifted, ignore the rest of the gesture. + return mResultOfDownAction; + } + if (mDragPointerId != ev.getPointerId(ev.getActionIndex())) { + // Ignore a secondary pointer being lifted. + return mResultOfDownAction; + } + // The primary pointer is being lifted. + final int dragPointerId = mDragPointerId; + mDragPointerId = -1; + return mEventHandler.handleMotionEvent(v, getSinglePointerEvent(ev, dragPointerId)); } case ACTION_UP: case ACTION_CANCEL: { + final int dragPointerId = mDragPointerId; resetState(); - return mEventHandler.handleMotionEvent(v, ev); + if (dragPointerId == -1) { + // The primary pointer was lifted, ignore the rest of the gesture. + return mResultOfDownAction; + } + return mEventHandler.handleMotionEvent(v, getSinglePointerEvent(ev, dragPointerId)); } default: - return mEventHandler.handleMotionEvent(v, ev); + // Ignore other events. + return mResultOfDownAction; } } + private static MotionEvent getSinglePointerEvent(MotionEvent ev, int pointerId) { + return ev.getPointerCount() > 1 ? ev.split(1 << pointerId) : ev; + } + void setTouchSlop(int touchSlop) { mTouchSlop = touchSlop; } @@ -129,4 +167,4 @@ class DragDetector { interface MotionEventHandler { boolean handleMotionEvent(@Nullable View v, MotionEvent ev); } -} +}
\ No newline at end of file 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 8421365e594d..37510ef4ba21 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 @@ -66,6 +66,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.dx.mockito.inline.extended.ExtendedMockito.never import com.android.dx.mockito.inline.extended.StaticMockitoSession +import com.android.internal.jank.InteractionJankMonitor import com.android.window.flags.Flags import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE import com.android.wm.shell.MockToken @@ -166,6 +167,9 @@ class DesktopTasksControllerTest : ShellTestCase() { @Mock lateinit var desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver @Mock lateinit var desktopModeVisualIndicator: DesktopModeVisualIndicator @Mock lateinit var recentTasksController: RecentTasksController + @Mock + private lateinit var mockInteractionJankMonitor: InteractionJankMonitor + @Mock private lateinit var mockSurface: SurfaceControl private lateinit var mockitoSession: StaticMockitoSession private lateinit var controller: DesktopTasksController @@ -248,7 +252,8 @@ class DesktopTasksControllerTest : ShellTestCase() { multiInstanceHelper, shellExecutor, Optional.of(desktopTasksLimiter), - recentTasksController) + recentTasksController, + mockInteractionJankMonitor) } @After @@ -2016,7 +2021,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val task = setUpFullscreenTask() setUpLandscapeDisplay() - spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) val wct = getLatestDragToDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) } @@ -2032,7 +2037,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE) setUpLandscapeDisplay() - spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) val wct = getLatestDragToDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) } @@ -2049,7 +2054,7 @@ class DesktopTasksControllerTest : ShellTestCase() { setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_PORTRAIT, shouldLetterbox = true) setUpLandscapeDisplay() - spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) val wct = getLatestDragToDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_PORTRAIT_BOUNDS) } @@ -2066,7 +2071,7 @@ class DesktopTasksControllerTest : ShellTestCase() { setUpFullscreenTask(isResizable = false, screenOrientation = SCREEN_ORIENTATION_LANDSCAPE) setUpLandscapeDisplay() - spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) val wct = getLatestDragToDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) } @@ -2086,7 +2091,7 @@ class DesktopTasksControllerTest : ShellTestCase() { shouldLetterbox = true) setUpLandscapeDisplay() - spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) val wct = getLatestDragToDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS) } @@ -2102,7 +2107,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT) setUpPortraitDisplay() - spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) val wct = getLatestDragToDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) } @@ -2121,7 +2126,7 @@ class DesktopTasksControllerTest : ShellTestCase() { screenOrientation = SCREEN_ORIENTATION_PORTRAIT) setUpPortraitDisplay() - spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) val wct = getLatestDragToDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) } @@ -2141,7 +2146,7 @@ class DesktopTasksControllerTest : ShellTestCase() { shouldLetterbox = true) setUpPortraitDisplay() - spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) val wct = getLatestDragToDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_LANDSCAPE_BOUNDS) } @@ -2161,7 +2166,7 @@ class DesktopTasksControllerTest : ShellTestCase() { screenOrientation = SCREEN_ORIENTATION_PORTRAIT) setUpPortraitDisplay() - spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) val wct = getLatestDragToDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) } @@ -2182,7 +2187,7 @@ class DesktopTasksControllerTest : ShellTestCase() { shouldLetterbox = true) setUpPortraitDisplay() - spyController.onDragPositioningEndThroughStatusBar(PointF(200f, 200f), task) + spyController.onDragPositioningEndThroughStatusBar(PointF(200f, 200f), task, mockSurface) val wct = getLatestDragToDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt index bbf523bc40d2..e4e2bd216c94 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt @@ -15,6 +15,7 @@ import android.window.TransitionInfo import android.window.TransitionInfo.FLAG_IS_WALLPAPER import android.window.WindowContainerTransaction import androidx.test.filters.SmallTest +import com.android.internal.jank.InteractionJankMonitor import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestRunningTaskInfoBuilder @@ -51,6 +52,8 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { @Mock private lateinit var taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer @Mock private lateinit var splitScreenController: SplitScreenController @Mock private lateinit var dragAnimator: MoveToDesktopAnimator + @Mock + private lateinit var mockInteractionJankMonitor: InteractionJankMonitor private val transactionSupplier = Supplier { mock<SurfaceControl.Transaction>() } @@ -63,7 +66,8 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { context, transitions, taskDisplayAreaOrganizer, - transactionSupplier + mockInteractionJankMonitor, + transactionSupplier, ) .apply { setSplitScreenController(splitScreenController) } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt index b1d62f485a2a..dd19d76d88cf 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt @@ -184,108 +184,6 @@ class DesktopModeFlagsTest : ShellTestCase() { } @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - fun isEnabled_noSystemProperty_overrideOn_featureFlagOff_returnsTrueAndStoresPropertyOn() { - System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY) - setOverride(OVERRIDE_ON.setting) - - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() - // Store System Property if not present - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(OVERRIDE_ON.setting.toString()) - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - fun isEnabled_noSystemProperty_overrideUnset_featureFlagOn_returnsTrueAndStoresPropertyUnset() { - System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY) - setOverride(OVERRIDE_UNSET.setting) - - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() - // Store System Property if not present - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(OVERRIDE_UNSET.setting.toString()) - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - fun isEnabled_noSystemProperty_overrideUnset_featureFlagOff_returnsFalseAndStoresPropertyUnset() { - System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY) - setOverride(OVERRIDE_UNSET.setting) - - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() - // Store System Property if not present - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(OVERRIDE_UNSET.setting.toString()) - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - @Suppress("ktlint:standard:max-line-length") - fun isEnabled_systemPropertyNotInteger_overrideOff_featureFlagOn_returnsFalseAndStoresPropertyOff() { - System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, "abc") - setOverride(OVERRIDE_OFF.setting) - - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() - // Store System Property if currently invalid - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(OVERRIDE_OFF.setting.toString()) - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - @Suppress("ktlint:standard:max-line-length") - fun isEnabled_systemPropertyInvalidInteger_overrideOff_featureFlagOn_returnsFalseAndStoresPropertyOff() { - System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, "-2") - setOverride(OVERRIDE_OFF.setting) - - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() - // Store System Property if currently invalid - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(OVERRIDE_OFF.setting.toString()) - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - fun isEnabled_systemPropertyOff_overrideOn_featureFlagOn_returnsFalseAndDoesNotUpdateProperty() { - System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, OVERRIDE_OFF.setting.toString()) - setOverride(OVERRIDE_ON.setting) - - // Have a consistent override until reboot - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(OVERRIDE_OFF.setting.toString()) - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - fun isEnabled_systemPropertyOn_overrideOff_featureFlagOff_returnsTrueAndDoesNotUpdateProperty() { - System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, OVERRIDE_ON.setting.toString()) - setOverride(OVERRIDE_OFF.setting) - - // Have a consistent override until reboot - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(OVERRIDE_ON.setting.toString()) - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - @Suppress("ktlint:standard:max-line-length") - fun isEnabled_systemPropertyUnset_overrideOff_featureFlagOn_returnsTrueAndDoesNotUpdateProperty() { - System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, OVERRIDE_UNSET.setting.toString()) - setOverride(OVERRIDE_OFF.setting) - - // Have a consistent override until reboot - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(OVERRIDE_UNSET.setting.toString()) - } - - @Test @EnableFlags( FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE, @@ -445,12 +343,5 @@ class DesktopModeFlagsTest : ShellTestCase() { DesktopModeFlags::class.java.getDeclaredField("cachedToggleOverride") cachedToggleOverride.isAccessible = true cachedToggleOverride.set(null, null) - - // Clear override cache stored in System property - System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY) - } - - private companion object { - const val SYSTEM_PROPERTY_OVERRIDE_KEY = "sys.wmshell.desktopmode.dev_toggle_override" } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index 37ef7881bde7..22b408cda0af 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -51,8 +51,10 @@ import android.annotation.NonNull; import android.app.ActivityManager; import android.os.Handler; import android.os.IBinder; +import android.os.RemoteException; import android.view.SurfaceControl; import android.view.SurfaceSession; +import android.window.IRemoteTransition; import android.window.RemoteTransition; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; @@ -328,6 +330,32 @@ public class SplitTransitionTests extends ShellTestCase { @Test @UiThreadTest + public void testRemotePassThroughInvoked() throws RemoteException { + RemoteTransition remoteWrapper = mock(RemoteTransition.class); + IRemoteTransition remoteTransition = mock(IRemoteTransition.class); + IBinder remoteBinder = mock(IBinder.class); + doReturn(remoteBinder).when(remoteTransition).asBinder(); + doReturn(remoteTransition).when(remoteWrapper).getRemoteTransition(); + + TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_CHANGE, null, + remoteWrapper); + IBinder transition = mock(IBinder.class); + mMainStage.activate(new WindowContainerTransaction(), false); + mStageCoordinator.handleRequest(transition, request); + TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CHANGE, 0) + .build(); + boolean accepted = mStageCoordinator.startAnimation(transition, info, + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), + mock(Transitions.TransitionFinishCallback.class)); + assertTrue(accepted); + + verify(remoteTransition, times(1)).startAnimation(any(), + any(), any(), any()); + } + + @Test + @UiThreadTest public void testEnterRecentsAndRestore() { enterSplit(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt index 3fbab0f9e2bb..56224b4b4025 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt @@ -85,6 +85,23 @@ class DragDetectorTest { } @Test + fun testNoMove_mouse_passesDownAndUp() { + assertTrue(dragDetector.onMotionEvent( + createMotionEvent(MotionEvent.ACTION_DOWN, isTouch = false))) + verify(eventHandler).handleMotionEvent(any(), argThat { + return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y && + it.source == InputDevice.SOURCE_MOUSE + }) + + assertTrue(dragDetector.onMotionEvent( + createMotionEvent(MotionEvent.ACTION_UP, isTouch = false))) + verify(eventHandler).handleMotionEvent(any(), argThat { + return@argThat it.action == MotionEvent.ACTION_UP && it.x == X && it.y == Y && + it.source == InputDevice.SOURCE_MOUSE + }) + } + + @Test fun testMoveInSlop_touch_passesDownAndUp() { `when`(eventHandler.handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_DOWN @@ -166,6 +183,52 @@ class DragDetectorTest { } @Test + fun testDownMoveDown_shouldIgnoreTheSecondDownMotion() { + assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN))) + verify(eventHandler).handleMotionEvent(any(), argThat { + return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y && + it.source == InputDevice.SOURCE_TOUCHSCREEN + }) + + val newX = X + SLOP + 1 + assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y))) + verify(eventHandler).handleMotionEvent(any(), argThat { + return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y && + it.source == InputDevice.SOURCE_TOUCHSCREEN + }) + + assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN))) + verify(eventHandler).handleMotionEvent(any(), argThat { + return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y && + it.source == InputDevice.SOURCE_TOUCHSCREEN + }) + } + + @Test + fun testDownMouseMoveDownTouch_shouldIgnoreTheTouchDownMotion() { + assertTrue(dragDetector.onMotionEvent( + createMotionEvent(MotionEvent.ACTION_DOWN, isTouch = false))) + verify(eventHandler).handleMotionEvent(any(), argThat { + return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y && + it.source == InputDevice.SOURCE_MOUSE + }) + + val newX = X + SLOP + 1 + assertTrue(dragDetector.onMotionEvent( + createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y, isTouch = false))) + verify(eventHandler).handleMotionEvent(any(), argThat { + return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y && + it.source == InputDevice.SOURCE_MOUSE + }) + + assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN))) + verify(eventHandler).handleMotionEvent(any(), argThat { + return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y && + it.source == InputDevice.SOURCE_MOUSE + }) + } + + @Test fun testPassesHoverEnter() { `when`(eventHandler.handleMotionEvent(any(), argThat { it.action == MotionEvent.ACTION_HOVER_ENTER diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp index ad963dd913cf..93118aeafaaf 100644 --- a/libs/hwui/tests/common/TestUtils.cpp +++ b/libs/hwui/tests/common/TestUtils.cpp @@ -40,6 +40,7 @@ namespace android { namespace uirenderer { +std::mutex TestUtils::sMutex; std::unordered_map<int, TestUtils::CallCounts> TestUtils::sMockFunctorCounts{}; SkColor TestUtils::interpolateColor(float fraction, SkColor start, SkColor end) { diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h index 0ede902b1b95..8ab2b16601c3 100644 --- a/libs/hwui/tests/common/TestUtils.h +++ b/libs/hwui/tests/common/TestUtils.h @@ -305,22 +305,26 @@ public: .onSync = [](int functor, void* client_data, const WebViewSyncData& data) { expectOnRenderThread("onSync"); + std::scoped_lock lock(sMutex); sMockFunctorCounts[functor].sync++; }, .onContextDestroyed = [](int functor, void* client_data) { expectOnRenderThread("onContextDestroyed"); + std::scoped_lock lock(sMutex); sMockFunctorCounts[functor].contextDestroyed++; }, .onDestroyed = [](int functor, void* client_data) { expectOnRenderThread("onDestroyed"); + std::scoped_lock lock(sMutex); sMockFunctorCounts[functor].destroyed++; }, .removeOverlays = [](int functor, void* data, void (*mergeTransaction)(ASurfaceTransaction*)) { expectOnRenderThread("removeOverlays"); + std::scoped_lock lock(sMutex); sMockFunctorCounts[functor].removeOverlays++; }, }; @@ -329,6 +333,7 @@ public: callbacks.gles.draw = [](int functor, void* client_data, const DrawGlInfo& params, const WebViewOverlayData& overlay_params) { expectOnRenderThread("draw"); + std::scoped_lock lock(sMutex); sMockFunctorCounts[functor].glesDraw++; }; break; @@ -336,15 +341,18 @@ public: callbacks.vk.initialize = [](int functor, void* data, const VkFunctorInitParams& params) { expectOnRenderThread("initialize"); + std::scoped_lock lock(sMutex); sMockFunctorCounts[functor].vkInitialize++; }; callbacks.vk.draw = [](int functor, void* data, const VkFunctorDrawParams& params, const WebViewOverlayData& overlayParams) { expectOnRenderThread("draw"); + std::scoped_lock lock(sMutex); sMockFunctorCounts[functor].vkDraw++; }; callbacks.vk.postDraw = [](int functor, void* data) { expectOnRenderThread("postDraw"); + std::scoped_lock lock(sMutex); sMockFunctorCounts[functor].vkPostDraw++; }; break; @@ -352,11 +360,16 @@ public: return callbacks; } - static CallCounts& countsForFunctor(int functor) { return sMockFunctorCounts[functor]; } + static CallCounts copyCountsForFunctor(int functor) { + std::scoped_lock lock(sMutex); + return sMockFunctorCounts[functor]; + } static SkFont defaultFont(); private: + // guards sMockFunctorCounts + static std::mutex sMutex; static std::unordered_map<int, CallCounts> sMockFunctorCounts; static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) { diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp index e727ea899098..690a60a470bc 100644 --- a/libs/hwui/tests/unit/RenderNodeTests.cpp +++ b/libs/hwui/tests/unit/RenderNodeTests.cpp @@ -239,19 +239,21 @@ TEST(RenderNode, releasedCallback) { TestUtils::runOnRenderThreadUnmanaged([&] (RenderThread&) { TestUtils::syncHierarchyPropertiesAndDisplayList(node); }); - auto& counts = TestUtils::countsForFunctor(functor); + auto counts = TestUtils::copyCountsForFunctor(functor); EXPECT_EQ(1, counts.sync); EXPECT_EQ(0, counts.destroyed); TestUtils::recordNode(*node, [&](Canvas& canvas) { canvas.drawWebViewFunctor(functor); }); + counts = TestUtils::copyCountsForFunctor(functor); EXPECT_EQ(1, counts.sync); EXPECT_EQ(0, counts.destroyed); TestUtils::runOnRenderThreadUnmanaged([&] (RenderThread&) { TestUtils::syncHierarchyPropertiesAndDisplayList(node); }); + counts = TestUtils::copyCountsForFunctor(functor); EXPECT_EQ(2, counts.sync); EXPECT_EQ(0, counts.destroyed); @@ -265,6 +267,7 @@ TEST(RenderNode, releasedCallback) { }); // Fence on any remaining post'd work TestUtils::runOnRenderThreadUnmanaged([] (RenderThread&) {}); + counts = TestUtils::copyCountsForFunctor(functor); EXPECT_EQ(2, counts.sync); EXPECT_EQ(1, counts.destroyed); } diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp index 064d42ec8941..26b47290d149 100644 --- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp +++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp @@ -101,7 +101,7 @@ TEST(SkiaDisplayList, syncContexts) { SkCanvas dummyCanvas; int functor1 = TestUtils::createMockFunctor(); - auto& counts = TestUtils::countsForFunctor(functor1); + auto counts = TestUtils::copyCountsForFunctor(functor1); skiaDL.mChildFunctors.push_back( skiaDL.allocateDrawable<GLFunctorDrawable>(functor1, &dummyCanvas)); WebViewFunctor_release(functor1); @@ -118,6 +118,7 @@ TEST(SkiaDisplayList, syncContexts) { }); }); + counts = TestUtils::copyCountsForFunctor(functor1); EXPECT_EQ(counts.sync, 1); EXPECT_EQ(counts.destroyed, 0); EXPECT_EQ(vectorDrawable.mutateProperties()->getBounds(), bounds); @@ -126,6 +127,7 @@ TEST(SkiaDisplayList, syncContexts) { TestUtils::runOnRenderThread([](auto&) { // Fence }); + counts = TestUtils::copyCountsForFunctor(functor1); EXPECT_EQ(counts.destroyed, 1); } diff --git a/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp b/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp index 5e8f13d261c7..09ce98a2e53d 100644 --- a/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp +++ b/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp @@ -40,7 +40,7 @@ TEST(WebViewFunctor, createDestroyGLES) { TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) { // Empty, don't care }); - auto& counts = TestUtils::countsForFunctor(functor); + auto counts = TestUtils::copyCountsForFunctor(functor); // We never initialized, so contextDestroyed == 0 EXPECT_EQ(0, counts.contextDestroyed); EXPECT_EQ(1, counts.destroyed); @@ -59,7 +59,7 @@ TEST(WebViewFunctor, createSyncHandleGLES) { TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) { // fence }); - auto& counts = TestUtils::countsForFunctor(functor); + auto counts = TestUtils::copyCountsForFunctor(functor); EXPECT_EQ(0, counts.sync); EXPECT_EQ(0, counts.contextDestroyed); EXPECT_EQ(0, counts.destroyed); @@ -69,6 +69,7 @@ TEST(WebViewFunctor, createSyncHandleGLES) { handle->sync(syncData); }); + counts = TestUtils::copyCountsForFunctor(functor); EXPECT_EQ(1, counts.sync); TestUtils::runOnRenderThreadUnmanaged([&](auto&) { @@ -76,6 +77,7 @@ TEST(WebViewFunctor, createSyncHandleGLES) { handle->sync(syncData); }); + counts = TestUtils::copyCountsForFunctor(functor); EXPECT_EQ(2, counts.sync); handle.clear(); @@ -84,6 +86,7 @@ TEST(WebViewFunctor, createSyncHandleGLES) { // fence }); + counts = TestUtils::copyCountsForFunctor(functor); EXPECT_EQ(2, counts.sync); EXPECT_EQ(0, counts.contextDestroyed); EXPECT_EQ(1, counts.destroyed); @@ -98,7 +101,6 @@ TEST(WebViewFunctor, createSyncDrawGLES) { auto handle = WebViewFunctorManager::instance().handleFor(functor); ASSERT_TRUE(handle); WebViewFunctor_release(functor); - auto& counts = TestUtils::countsForFunctor(functor); for (int i = 0; i < 5; i++) { TestUtils::runOnRenderThreadUnmanaged([&](auto&) { WebViewSyncData syncData; @@ -112,6 +114,7 @@ TEST(WebViewFunctor, createSyncDrawGLES) { TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) { // fence }); + auto counts = TestUtils::copyCountsForFunctor(functor); EXPECT_EQ(5, counts.sync); EXPECT_EQ(10, counts.glesDraw); EXPECT_EQ(1, counts.contextDestroyed); @@ -127,13 +130,13 @@ TEST(WebViewFunctor, contextDestroyedGLES) { auto handle = WebViewFunctorManager::instance().handleFor(functor); ASSERT_TRUE(handle); WebViewFunctor_release(functor); - auto& counts = TestUtils::countsForFunctor(functor); TestUtils::runOnRenderThreadUnmanaged([&](auto&) { WebViewSyncData syncData; handle->sync(syncData); DrawGlInfo drawInfo; handle->drawGl(drawInfo); }); + auto counts = TestUtils::copyCountsForFunctor(functor); EXPECT_EQ(1, counts.sync); EXPECT_EQ(1, counts.glesDraw); EXPECT_EQ(0, counts.contextDestroyed); @@ -141,6 +144,7 @@ TEST(WebViewFunctor, contextDestroyedGLES) { TestUtils::runOnRenderThreadUnmanaged([](auto& rt) { rt.destroyRenderingContext(); }); + counts = TestUtils::copyCountsForFunctor(functor); EXPECT_EQ(1, counts.sync); EXPECT_EQ(1, counts.glesDraw); EXPECT_EQ(1, counts.contextDestroyed); @@ -151,6 +155,7 @@ TEST(WebViewFunctor, contextDestroyedGLES) { DrawGlInfo drawInfo; handle->drawGl(drawInfo); }); + counts = TestUtils::copyCountsForFunctor(functor); EXPECT_EQ(2, counts.sync); EXPECT_EQ(2, counts.glesDraw); EXPECT_EQ(1, counts.contextDestroyed); @@ -159,6 +164,7 @@ TEST(WebViewFunctor, contextDestroyedGLES) { TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) { // fence }); + counts = TestUtils::copyCountsForFunctor(functor); EXPECT_EQ(2, counts.sync); EXPECT_EQ(2, counts.glesDraw); EXPECT_EQ(2, counts.contextDestroyed); diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts index d6345cec7c30..f36344aa4846 100644 --- a/packages/SettingsLib/Spa/build.gradle.kts +++ b/packages/SettingsLib/Spa/build.gradle.kts @@ -29,7 +29,7 @@ val androidTop: String = File(rootDir, "../../../../..").canonicalPath allprojects { extra["androidTop"] = androidTop - extra["jetpackComposeVersion"] = "1.7.0-beta02" + extra["jetpackComposeVersion"] = "1.7.0-beta05" } subprojects { diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml index a842009a09bb..1cca73af9a42 100644 --- a/packages/SettingsLib/Spa/gradle/libs.versions.toml +++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml @@ -15,7 +15,7 @@ # [versions] -agp = "8.5.0" +agp = "8.5.1" compose-compiler = "1.5.11" dexmaker-mockito = "2.28.3" jvm = "17" diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.8-bin.zip b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.9-bin.zip Binary files differindex 77e6ad3f2117..9a97e4674448 100644 --- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.8-bin.zip +++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.9-bin.zip diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar Binary files differindex e6441136f3d4..2c3521197d7c 100644 --- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar +++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties index 91d2a3ab217d..9f29c77d55f6 100644 --- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties +++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties @@ -16,6 +16,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=gradle-8.8-bin.zip +distributionUrl=gradle-8.9-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/packages/SettingsLib/Spa/gradlew b/packages/SettingsLib/Spa/gradlew index b740cf13397a..f5feea6d6b11 100755 --- a/packages/SettingsLib/Spa/gradlew +++ b/packages/SettingsLib/Spa/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts index 492d7c065cac..ce3d96e2d388 100644 --- a/packages/SettingsLib/Spa/spa/build.gradle.kts +++ b/packages/SettingsLib/Spa/spa/build.gradle.kts @@ -53,14 +53,14 @@ android { dependencies { api(project(":SettingsLibColor")) - api("androidx.appcompat:appcompat:1.7.0-rc01") - api("androidx.compose.material3:material3:1.3.0-beta02") + api("androidx.appcompat:appcompat:1.7.0") + api("androidx.compose.material3:material3:1.3.0-beta04") api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion") api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion") api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion") api("androidx.lifecycle:lifecycle-livedata-ktx") api("androidx.lifecycle:lifecycle-runtime-compose") - api("androidx.navigation:navigation-compose:2.8.0-beta02") + api("androidx.navigation:navigation-compose:2.8.0-beta05") api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha") api("com.google.android.material:material:1.11.0") debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion") diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 5f236516785d..2b8b23e3dba8 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -282,5 +282,6 @@ public class SecureSettings { Settings.Secure.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS, Settings.Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS, Settings.Secure.MANDATORY_BIOMETRICS, + Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED, }; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index c8da8afc25c3..cc5302bdd99a 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -441,5 +441,7 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS, NONE_NEGATIVE_LONG_VALIDATOR); VALIDATORS.put(Secure.ACCESSIBILITY_MOUSE_KEYS_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.MANDATORY_BIOMETRICS, new InclusiveIntegerRangeValidator(0, 1)); + VALIDATORS.put(Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED, + new InclusiveIntegerRangeValidator(0, 1)); } } 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 1c02d3f7662b..68e968fdfb4e 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 @@ -1004,6 +1004,7 @@ private fun WidgetContent( } .thenIf(viewModel.isEditMode) { Modifier.semantics { + onClick(clickActionLabel, null) contentDescription = accessibilityLabel val deleteAction = CustomAccessibilityAction(removeWidgetActionLabel) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt index 620892adc286..b4c1a2e85daf 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt @@ -50,7 +50,6 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp @@ -102,8 +101,6 @@ constructor( val interactionSource = remember { MutableInteractionSource() } val focusRequester = remember { FocusRequester() } - val context = LocalContext.current - LaunchedEffect(Unit) { // Adding a delay to ensure the animation completes before requesting focus delay(250) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt index 665be5392d77..5b328b8cdd9c 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt @@ -52,11 +52,19 @@ internal interface DraggableHandler { * and [onStop] methods. */ internal interface DragController { - /** Drag the current scene by [delta] pixels. */ - fun onDrag(delta: Float) + /** + * Drag the current scene by [delta] pixels. + * + * @return the consumed [delta] + */ + fun onDrag(delta: Float): Float - /** Starts a transition to a target scene. */ - fun onStop(velocity: Float, canChangeScene: Boolean) + /** + * Starts a transition to a target scene. + * + * @return the consumed [velocity] + */ + fun onStop(velocity: Float, canChangeScene: Boolean): Float } internal class DraggableHandlerImpl( @@ -272,8 +280,10 @@ private class DragControllerImpl( * * @return the consumed delta */ - override fun onDrag(delta: Float) { - if (delta == 0f || !isDrivingTransition || swipeTransition.isFinishing) return + override fun onDrag(delta: Float): Float { + if (delta == 0f || !isDrivingTransition || swipeTransition.isFinishing) { + return 0f + } swipeTransition.dragOffset += delta val (fromScene, acceleratedOffset) = @@ -289,7 +299,7 @@ private class DragControllerImpl( if (result == null) { onStop(velocity = delta, canChangeScene = true) - return + return 0f } if ( @@ -314,6 +324,8 @@ private class DragControllerImpl( updateTransition(swipeTransition) } + + return delta } /** @@ -351,10 +363,10 @@ private class DragControllerImpl( } } - override fun onStop(velocity: Float, canChangeScene: Boolean) { + override fun onStop(velocity: Float, canChangeScene: Boolean): Float { // The state was changed since the drag started; don't do anything. if (!isDrivingTransition || swipeTransition.isFinishing) { - return + return 0f } // Important: Make sure that all the code here references the current transition when @@ -440,7 +452,7 @@ private class DragControllerImpl( if (result == null) { // We will not animate swipeTransition.snapToScene(fromScene.key) - return + return 0f } val newSwipeTransition = @@ -462,6 +474,9 @@ private class DragControllerImpl( animateTo(targetScene = fromScene, targetOffset = 0f) } } + + // The onStop animation consumes any remaining velocity. + return velocity } /** @@ -1081,17 +1096,13 @@ internal class NestedScrollHandlerImpl( // TODO(b/297842071) We should handle the overscroll or slow drag if the gesture is // initiated in a nested child. controller.onDrag(delta = offsetAvailable) - - offsetAvailable }, onStop = { velocityAvailable -> val controller = dragController ?: error("Should be called after onStart") - controller.onStop(velocity = velocityAvailable, canChangeScene = canChangeScene) - - dragController = null - // The onDragStopped animation consumes any remaining velocity. - velocityAvailable + controller + .onStop(velocity = velocityAvailable, canChangeScene = canChangeScene) + .also { dragController = null } }, ) } @@ -1106,7 +1117,7 @@ internal class NestedScrollHandlerImpl( internal const val OffsetVisibilityThreshold = 0.5f private object NoOpDragController : DragController { - override fun onDrag(delta: Float) {} + override fun onDrag(delta: Float) = 0f - override fun onStop(velocity: Float, canChangeScene: Boolean) {} + override fun onStop(velocity: Float, canChangeScene: Boolean) = 0f } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt index 7a5a84e2c3f1..c8bbb149a042 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt @@ -212,7 +212,8 @@ class DraggableHandlerTest { draggableHandler: DraggableHandler, startedPosition: Offset = Offset.Zero, overSlop: Float = 0f, - pointersDown: Int = 1 + pointersDown: Int = 1, + expectedConsumed: Boolean = true, ): DragController { val dragController = draggableHandler.onDragStarted( @@ -222,17 +223,23 @@ class DraggableHandlerTest { ) // MultiPointerDraggable will always call onDelta with the initial overSlop right after - dragController.onDragDelta(pixels = overSlop) + dragController.onDragDelta(pixels = overSlop, expectedConsumed = expectedConsumed) return dragController } - fun DragController.onDragDelta(pixels: Float) { - onDrag(delta = pixels) + fun DragController.onDragDelta(pixels: Float, expectedConsumed: Boolean = true) { + val consumed = onDrag(delta = pixels) + assertThat(consumed).isEqualTo(if (expectedConsumed) pixels else 0f) } - fun DragController.onDragStopped(velocity: Float, canChangeScene: Boolean = true) { - onStop(velocity, canChangeScene) + fun DragController.onDragStopped( + velocity: Float, + canChangeScene: Boolean = true, + expectedConsumed: Boolean = true + ) { + val consumed = onStop(velocity, canChangeScene) + assertThat(consumed).isEqualTo(if (expectedConsumed) velocity else 0f) } fun NestedScrollConnection.scroll( @@ -360,10 +367,18 @@ class DraggableHandlerTest { @Test fun onDragStartedWithoutActionsInBothDirections_stayIdle() = runGestureTest { - onDragStarted(horizontalDraggableHandler, overSlop = up(fractionOfScreen = 0.3f)) + onDragStarted( + horizontalDraggableHandler, + overSlop = up(fractionOfScreen = 0.3f), + expectedConsumed = false, + ) assertIdle(currentScene = SceneA) - onDragStarted(horizontalDraggableHandler, overSlop = down(fractionOfScreen = 0.3f)) + onDragStarted( + horizontalDraggableHandler, + overSlop = down(fractionOfScreen = 0.3f), + expectedConsumed = false, + ) assertIdle(currentScene = SceneA) } @@ -489,19 +504,19 @@ class DraggableHandlerTest { // start accelaratedScroll and scroll over to B -> null val dragController2 = onDragStartedImmediately() - dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f)) - dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f)) + dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = false) + dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = false) // here onDragStopped is already triggered, but subsequent onDelta/onDragStopped calls may // still be called. Make sure that they don't crash or change the scene - dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f)) + dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = false) dragController2.onDragStopped(velocity = 0f) advanceUntilIdle() assertIdle(SceneB) // These events can still come in after the animation has settled - dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f)) + dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = false) dragController2.onDragStopped(velocity = 0f) assertIdle(SceneB) } @@ -845,7 +860,7 @@ class DraggableHandlerTest { assertThat(progress).isEqualTo(0.2f) // this should be ignored, we are scrolling now! - dragController.onDragStopped(-velocityThreshold) + dragController.onDragStopped(-velocityThreshold, expectedConsumed = false) assertTransition(currentScene = SceneA) nestedScroll.scroll(available = -offsetY10) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt index ecafb170535d..b98400a70ea4 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt @@ -49,6 +49,21 @@ import org.junit.runner.RunWith class MultiPointerDraggableTest { @get:Rule val rule = createComposeRule() + private class SimpleDragController( + val onDrag: () -> Unit, + val onStop: () -> Unit, + ) : DragController { + override fun onDrag(delta: Float): Float { + onDrag() + return delta + } + + override fun onStop(velocity: Float, canChangeScene: Boolean): Float { + onStop() + return velocity + } + } + @Test fun cancellingPointerCallsOnDragStopped() { val size = 200f @@ -70,15 +85,10 @@ class MultiPointerDraggableTest { startDragImmediately = { false }, onDragStarted = { _, _, _ -> started = true - object : DragController { - override fun onDrag(delta: Float) { - dragged = true - } - - override fun onStop(velocity: Float, canChangeScene: Boolean) { - stopped = true - } - } + SimpleDragController( + onDrag = { dragged = true }, + onStop = { stopped = true }, + ) }, ) ) @@ -142,15 +152,10 @@ class MultiPointerDraggableTest { startDragImmediately = { true }, onDragStarted = { _, _, _ -> started = true - object : DragController { - override fun onDrag(delta: Float) { - dragged = true - } - - override fun onStop(velocity: Float, canChangeScene: Boolean) { - stopped = true - } - } + SimpleDragController( + onDrag = { dragged = true }, + onStop = { stopped = true }, + ) }, ) .pointerInput(Unit) { @@ -218,15 +223,10 @@ class MultiPointerDraggableTest { startDragImmediately = { false }, onDragStarted = { _, _, _ -> started = true - object : DragController { - override fun onDrag(delta: Float) { - dragged = true - } - - override fun onStop(velocity: Float, canChangeScene: Boolean) { - stopped = true - } - } + SimpleDragController( + onDrag = { dragged = true }, + onStop = { stopped = true }, + ) }, ) ) { @@ -341,15 +341,10 @@ class MultiPointerDraggableTest { startDragImmediately = { false }, onDragStarted = { _, _, _ -> started = true - object : DragController { - override fun onDrag(delta: Float) { - dragged = true - } - - override fun onStop(velocity: Float, canChangeScene: Boolean) { - stopped = true - } - } + SimpleDragController( + onDrag = { dragged = true }, + onStop = { stopped = true }, + ) }, ) ) { @@ -447,15 +442,10 @@ class MultiPointerDraggableTest { startDragImmediately = { false }, onDragStarted = { _, _, _ -> verticalStarted = true - object : DragController { - override fun onDrag(delta: Float) { - verticalDragged = true - } - - override fun onStop(velocity: Float, canChangeScene: Boolean) { - verticalStopped = true - } - } + SimpleDragController( + onDrag = { verticalDragged = true }, + onStop = { verticalStopped = true }, + ) }, ) .multiPointerDraggable( @@ -464,15 +454,10 @@ class MultiPointerDraggableTest { startDragImmediately = { false }, onDragStarted = { _, _, _ -> horizontalStarted = true - object : DragController { - override fun onDrag(delta: Float) { - horizontalDragged = true - } - - override fun onStop(velocity: Float, canChangeScene: Boolean) { - horizontalStopped = true - } - } + SimpleDragController( + onDrag = { horizontalDragged = true }, + onStop = { horizontalStopped = true }, + ) }, ) ) @@ -567,11 +552,10 @@ class MultiPointerDraggableTest { }, onDragStarted = { _, _, _ -> started = true - object : DragController { - override fun onDrag(delta: Float) {} - - override fun onStop(velocity: Float, canChangeScene: Boolean) {} - } + SimpleDragController( + onDrag = { /* do nothing */ }, + onStop = { /* do nothing */ }, + ) }, ) ) {} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt index 693fcdabea58..18839e6acb96 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt @@ -22,6 +22,7 @@ import android.testing.TestableLooper.RunWithLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.classifier.falsingManager import com.android.systemui.haptics.vibratorHelper import com.android.systemui.kosmos.testScope @@ -52,6 +53,7 @@ class QSLongPressEffectTest : SysuiTestCase() { private val vibratorHelper = kosmos.vibratorHelper private val qsTile = kosmos.qsTileFactory.createTile("Test Tile") @Mock private lateinit var callback: QSLongPressEffect.Callback + @Mock private lateinit var controller: ActivityTransitionAnimator.Controller private val effectDuration = 400 private val lowTickDuration = 12 @@ -218,8 +220,9 @@ class QSLongPressEffectTest : SysuiTestCase() { // GIVEN that the animation completes longPressEffect.handleAnimationComplete() - // THEN the effect ends in the idle state. + // THEN the effect ends in the idle state and the reversed callback is used. assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE) + verify(callback, times(1)).onEffectFinishedReversing() } @Test @@ -348,6 +351,23 @@ class QSLongPressEffectTest : SysuiTestCase() { assertThat(clickState).isEqualTo(QSLongPressEffect.State.IDLE) } + @Test + fun onLongClickTransitionCancelled_whileInLongClickState_reversesEffect() = + testWhileInState(QSLongPressEffect.State.LONG_CLICKED) { + // GIVEN a transition controller delegate + val delegate = longPressEffect.createTransitionControllerDelegate(controller) + + // WHEN the activity launch animation is cancelled + val newOccludedState = false + delegate.onTransitionAnimationCancelled(newOccludedState) + + // THEN the effect reverses and ends in RUNNING_BACKWARDS_FROM_CANCEL + assertThat(longPressEffect.state) + .isEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_CANCEL) + verify(callback, times(1)).onReverseAnimator(false) + verify(controller).onTransitionAnimationCancelled(newOccludedState) + } + private fun testWithScope(initialize: Boolean = true, test: suspend TestScope.() -> Unit) = with(kosmos) { testScope.runTest { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index a3959d2109c8..42cd5ec69466 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.domain.interactor import android.app.StatusBarManager +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest @@ -25,9 +26,13 @@ import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN import com.android.systemui.Flags import com.android.systemui.Flags.FLAG_COMMUNAL_HUB +import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository +import com.android.systemui.communal.domain.interactor.CommunalSceneTransitionInteractor import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.communal.domain.interactor.communalSceneInteractor +import com.android.systemui.communal.domain.interactor.communalSceneTransitionInteractor import com.android.systemui.communal.domain.interactor.setCommunalAvailable import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.flags.BrokenWithSceneContainer @@ -122,15 +127,22 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest private val fromGlanceableHubTransitionInteractor by lazy { kosmos.fromGlanceableHubTransitionInteractor } + private val communalSceneTransitionInteractor by lazy { + kosmos.communalSceneTransitionInteractor + } private val powerInteractor by lazy { kosmos.powerInteractor } private val communalInteractor by lazy { kosmos.communalInteractor } + private val communalSceneInteractor by lazy { kosmos.communalSceneInteractor } companion object { @JvmStatic @Parameters(name = "{0}") fun getParams(): List<FlagsParameterization> { - return FlagsParameterization.allCombinationsOf().andSceneContainer() + return FlagsParameterization.allCombinationsOf( + FLAG_COMMUNAL_SCENE_KTF_REFACTOR, + ) + .andSceneContainer() } } @@ -163,6 +175,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest fromOccludedTransitionInteractor.start() fromAlternateBouncerTransitionInteractor.start() fromGlanceableHubTransitionInteractor.start() + communalSceneTransitionInteractor.start() } @Test @@ -636,6 +649,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @Test @DisableSceneContainer + @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) fun dozingToGlanceableHub() = testScope.runTest { // GIVEN a prior transition has run to DOZING @@ -770,6 +784,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @Test @BrokenWithSceneContainer(339465026) + @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) fun goneToGlanceableHub() = testScope.runTest { // GIVEN a prior transition has run to GONE @@ -799,6 +814,29 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest } @Test + @BrokenWithSceneContainer(339465026) + @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) + fun goneToGlanceableHub_communalKtfRefactor() = + testScope.runTest { + // GIVEN a prior transition has run to GONE + runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GONE) + + // WHEN the glanceable hub is shown + communalSceneInteractor.changeScene(CommunalScenes.Communal) + runCurrent() + + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.GLANCEABLE_HUB, + from = KeyguardState.GONE, + ownerName = CommunalSceneTransitionInteractor::class.simpleName, + animatorAssertion = { it.isNull() } + ) + + coroutineContext.cancelChildren() + } + + @Test @DisableSceneContainer fun alternateBouncerToPrimaryBouncer() = testScope.runTest { @@ -941,6 +979,11 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @Test fun alternateBouncerToGlanceableHub() = testScope.runTest { + // GIVEN the device is idle on the glanceable hub + communalSceneInteractor.changeScene(CommunalScenes.Communal) + runCurrent() + clearInvocations(transitionRepository) + // GIVEN a prior transition has run to ALTERNATE_BOUNCER bouncerRepository.setAlternateVisible(true) runTransitionAndSetWakefulness( @@ -951,19 +994,11 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest // GIVEN the primary bouncer isn't showing and device not sleeping bouncerRepository.setPrimaryShow(false) - // GIVEN the device is idle on the glanceable hub - val idleTransitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(CommunalScenes.Communal) - ) - communalInteractor.setTransitionState(idleTransitionState) - runCurrent() - // WHEN the alternateBouncer stops showing bouncerRepository.setAlternateVisible(false) advanceTimeBy(200L) - // THEN a transition to LOCKSCREEN should occur + // THEN a transition to GLANCEABLE_HUB should occur assertThat(transitionRepository) .startedTransition( ownerName = FromAlternateBouncerTransitionInteractor::class.simpleName, @@ -1063,18 +1098,17 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @DisableSceneContainer fun primaryBouncerToGlanceableHub() = testScope.runTest { - // GIVEN a prior transition has run to PRIMARY_BOUNCER - bouncerRepository.setPrimaryShow(true) - runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER) - // GIVEN the device is idle on the glanceable hub - val idleTransitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(CommunalScenes.Communal) - ) - communalInteractor.setTransitionState(idleTransitionState) + communalSceneInteractor.changeScene(CommunalScenes.Communal) runCurrent() + // GIVEN a prior transition has run to PRIMARY_BOUNCER + bouncerRepository.setPrimaryShow(true) + runTransitionAndSetWakefulness( + KeyguardState.GLANCEABLE_HUB, + KeyguardState.PRIMARY_BOUNCER + ) + // WHEN the primaryBouncer stops showing bouncerRepository.setPrimaryShow(false) runCurrent() @@ -1095,27 +1129,26 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @DisableSceneContainer fun primaryBouncerToGlanceableHubWhileDreaming() = testScope.runTest { + // GIVEN the device is idle on the glanceable hub + communalSceneInteractor.changeScene(CommunalScenes.Communal) + runCurrent() + // GIVEN a prior transition has run to PRIMARY_BOUNCER bouncerRepository.setPrimaryShow(true) - runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER) + runTransitionAndSetWakefulness( + KeyguardState.GLANCEABLE_HUB, + KeyguardState.PRIMARY_BOUNCER + ) // GIVEN that we are dreaming and occluded keyguardRepository.setDreaming(true) keyguardRepository.setKeyguardOccluded(true) - // GIVEN the device is idle on the glanceable hub - val idleTransitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(CommunalScenes.Communal) - ) - communalInteractor.setTransitionState(idleTransitionState) - runCurrent() - // WHEN the primaryBouncer stops showing bouncerRepository.setPrimaryShow(false) runCurrent() - // THEN a transition to LOCKSCREEN should occur + // THEN a transition to GLANCEABLE_HUB should occur assertThat(transitionRepository) .startedTransition( ownerName = FromPrimaryBouncerTransitionInteractor::class.simpleName, @@ -1219,6 +1252,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @Test @BrokenWithSceneContainer(339465026) + @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) fun occludedToGlanceableHub() = testScope.runTest { // GIVEN a device on lockscreen @@ -1256,6 +1290,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @Test @BrokenWithSceneContainer(339465026) + @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) fun occludedToGlanceableHubWhenInitiallyOnHub() = testScope.runTest { // GIVEN a device on lockscreen and communal is available @@ -1293,6 +1328,37 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest } @Test + @BrokenWithSceneContainer(339465026) + @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) + fun occludedToGlanceableHub_communalKtfRefactor() = + testScope.runTest { + // GIVEN a device on lockscreen and communal is available + keyguardRepository.setKeyguardShowing(true) + kosmos.setCommunalAvailable(true) + runCurrent() + + // GIVEN a prior transition has run to OCCLUDED from GLANCEABLE_HUB + runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.OCCLUDED) + keyguardRepository.setKeyguardOccluded(true) + runCurrent() + + // WHEN occlusion ends + keyguardRepository.setKeyguardOccluded(false) + runCurrent() + + // THEN a transition to GLANCEABLE_HUB should occur + assertThat(transitionRepository) + .startedTransition( + ownerName = CommunalSceneTransitionInteractor::class.simpleName, + from = KeyguardState.OCCLUDED, + to = KeyguardState.GLANCEABLE_HUB, + animatorAssertion = { it.isNull() }, + ) + + coroutineContext.cancelChildren() + } + + @Test fun occludedToAlternateBouncer() = testScope.runTest { // GIVEN a prior transition has run to OCCLUDED @@ -1511,6 +1577,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @Test @DisableSceneContainer + @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) fun dreamingToGlanceableHub() = testScope.runTest { // GIVEN a prior transition has run to DREAMING @@ -1550,6 +1617,47 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest } @Test + @DisableSceneContainer + @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) + fun dreamingToGlanceableHub_communalKtfRefactor() = + testScope.runTest { + // GIVEN a prior transition has run to DREAMING + keyguardRepository.setDreaming(true) + runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING) + runCurrent() + + // WHEN a transition to the glanceable hub starts + val currentScene = CommunalScenes.Blank + val targetScene = CommunalScenes.Communal + + val progress = MutableStateFlow(0f) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = currentScene, + toScene = targetScene, + currentScene = flowOf(targetScene), + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + communalSceneInteractor.setTransitionState(transitionState) + progress.value = .1f + runCurrent() + + assertThat(transitionRepository) + .startedTransition( + ownerName = CommunalSceneTransitionInteractor::class.simpleName, + from = KeyguardState.DREAMING, + to = KeyguardState.GLANCEABLE_HUB, + animatorAssertion = { it.isNull() }, // transition should be manually animated + ) + + coroutineContext.cancelChildren() + } + + @Test @BrokenWithSceneContainer(339465026) fun lockscreenToOccluded() = testScope.runTest { @@ -1679,6 +1787,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @Test @DisableSceneContainer + @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) fun lockscreenToGlanceableHub() = testScope.runTest { // GIVEN a prior transition has run to LOCKSCREEN @@ -1737,6 +1846,48 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @Test @DisableSceneContainer + @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) + fun lockscreenToGlanceableHub_communalKtfRefactor() = + testScope.runTest { + // GIVEN a prior transition has run to LOCKSCREEN + runTransitionAndSetWakefulness(KeyguardState.AOD, KeyguardState.LOCKSCREEN) + runCurrent() + + // WHEN a glanceable hub transition starts + val currentScene = CommunalScenes.Blank + val targetScene = CommunalScenes.Communal + + val progress = MutableStateFlow(0f) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = currentScene, + toScene = targetScene, + currentScene = flowOf(targetScene), + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + communalSceneInteractor.setTransitionState(transitionState) + progress.value = .1f + runCurrent() + + // THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur + assertThat(transitionRepository) + .startedTransition( + ownerName = CommunalSceneTransitionInteractor::class.simpleName, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GLANCEABLE_HUB, + animatorAssertion = { it.isNull() }, // transition should be manually animated + ) + + coroutineContext.cancelChildren() + } + + @Test + @DisableSceneContainer + @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) fun glanceableHubToLockscreen() = testScope.runTest { // GIVEN a prior transition has run to GLANCEABLE_HUB @@ -1792,6 +1943,48 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @Test @DisableSceneContainer + @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) + fun glanceableHubToLockscreen_communalKtfRefactor() = + testScope.runTest { + // GIVEN a prior transition has run to GLANCEABLE_HUB + communalSceneInteractor.changeScene(CommunalScenes.Communal) + runCurrent() + clearInvocations(transitionRepository) + + // WHEN a transition away from glanceable hub starts + val currentScene = CommunalScenes.Communal + val targetScene = CommunalScenes.Blank + + val progress = MutableStateFlow(0f) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = currentScene, + toScene = targetScene, + currentScene = flowOf(targetScene), + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + communalSceneInteractor.setTransitionState(transitionState) + progress.value = .1f + runCurrent() + + assertThat(transitionRepository) + .startedTransition( + ownerName = CommunalSceneTransitionInteractor::class.simpleName, + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.LOCKSCREEN, + animatorAssertion = { it.isNull() }, // transition should be manually animated + ) + + coroutineContext.cancelChildren() + } + + @Test + @DisableSceneContainer + @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) fun glanceableHubToDozing() = testScope.runTest { // GIVEN a prior transition has run to GLANCEABLE_HUB @@ -1814,6 +2007,31 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @Test @DisableSceneContainer + @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) + fun glanceableHubToDozing_communalKtfRefactor() = + testScope.runTest { + // GIVEN a prior transition has run to GLANCEABLE_HUB + communalSceneInteractor.changeScene(CommunalScenes.Communal) + runCurrent() + clearInvocations(transitionRepository) + + // WHEN the device begins to sleep + powerInteractor.setAsleepForTest() + runCurrent() + + assertThat(transitionRepository) + .startedTransition( + ownerName = CommunalSceneTransitionInteractor::class.simpleName, + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.DOZING, + animatorAssertion = { it.isNull() }, + ) + + coroutineContext.cancelChildren() + } + + @Test + @DisableSceneContainer fun glanceableHubToPrimaryBouncer() = testScope.runTest { // GIVEN a prior transition has run to ALTERNATE_BOUNCER @@ -1858,6 +2076,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @Test @BrokenWithSceneContainer(339465026) + @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) fun glanceableHubToOccluded() = testScope.runTest { // GIVEN a prior transition has run to GLANCEABLE_HUB @@ -1888,7 +2107,33 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest } @Test + @BrokenWithSceneContainer(339465026) + @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) + fun glanceableHubToOccluded_communalKtfRefactor() = + testScope.runTest { + // GIVEN a prior transition has run to GLANCEABLE_HUB + communalSceneInteractor.changeScene(CommunalScenes.Communal) + runCurrent() + clearInvocations(transitionRepository) + + // WHEN the keyguard is occluded + keyguardRepository.setKeyguardOccluded(true) + runCurrent() + + assertThat(transitionRepository) + .startedTransition( + ownerName = CommunalSceneTransitionInteractor::class.simpleName, + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.OCCLUDED, + animatorAssertion = { it.isNull() }, + ) + + coroutineContext.cancelChildren() + } + + @Test @DisableSceneContainer + @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) fun glanceableHubToGone() = testScope.runTest { // GIVEN a prior transition has run to GLANCEABLE_HUB @@ -1911,6 +2156,32 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @Test @DisableSceneContainer + @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) + fun glanceableHubToGone_communalKtfRefactor() = + testScope.runTest { + // GIVEN a prior transition has run to GLANCEABLE_HUB + communalSceneInteractor.changeScene(CommunalScenes.Communal) + runCurrent() + clearInvocations(transitionRepository) + + // WHEN keyguard goes away + keyguardRepository.setKeyguardGoingAway(true) + runCurrent() + + assertThat(transitionRepository) + .startedTransition( + ownerName = CommunalSceneTransitionInteractor::class.simpleName, + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.GONE, + animatorAssertion = { it.isNull() }, + ) + + coroutineContext.cancelChildren() + } + + @Test + @DisableSceneContainer + @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) fun glanceableHubToDreaming() = testScope.runTest { // GIVEN that we are dreaming and not dozing @@ -1939,7 +2210,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest isUserInputOngoing = flowOf(false), ) ) - communalInteractor.setTransitionState(transitionState) + communalSceneInteractor.setTransitionState(transitionState) runCurrent() assertThat(transitionRepository) @@ -1953,6 +2224,52 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest coroutineContext.cancelChildren() } + @Test + @DisableSceneContainer + @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) + fun glanceableHubToDreaming_communalKtfRefactor() = + testScope.runTest { + // GIVEN that we are dreaming and not dozing + keyguardRepository.setDreaming(true) + keyguardRepository.setDozeTransitionModel( + DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH) + ) + runCurrent() + + // GIVEN a prior transition has run to GLANCEABLE_HUB + communalSceneInteractor.changeScene(CommunalScenes.Communal) + runCurrent() + clearInvocations(transitionRepository) + + // WHEN a transition away from glanceable hub starts + val currentScene = CommunalScenes.Communal + val targetScene = CommunalScenes.Blank + + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = currentScene, + toScene = targetScene, + currentScene = flowOf(targetScene), + progress = flowOf(0f, 0.1f), + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + communalSceneInteractor.setTransitionState(transitionState) + runCurrent() + + assertThat(transitionRepository) + .startedTransition( + ownerName = CommunalSceneTransitionInteractor::class.simpleName, + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.DREAMING, + animatorAssertion = { it.isNull() }, // transition should be manually animated + ) + + coroutineContext.cancelChildren() + } + private suspend fun TestScope.runTransitionAndSetWakefulness( from: KeyguardState, to: KeyguardState diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt index aa9cbd047bbf..45cfe3673f3d 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt @@ -52,7 +52,7 @@ constructor( @Application private val applicationScope: CoroutineScope, private val communalSceneRepository: CommunalSceneRepository, ) { - val _isLaunchingWidget = MutableStateFlow(false) + private val _isLaunchingWidget = MutableStateFlow(false) /** Whether a widget launch is currently in progress. */ val isLaunchingWidget: StateFlow<Boolean> = _isLaunchingWidget.asStateFlow() diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt index c44eb471d460..491c73d0ae56 100644 --- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt +++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt @@ -16,9 +16,15 @@ package com.android.systemui.haptics.qs +import android.content.ComponentName import android.os.VibrationEffect import android.service.quicksettings.Tile +import android.view.View import androidx.annotation.VisibleForTesting +import com.android.systemui.animation.ActivityTransitionAnimator +import com.android.systemui.animation.DelegateTransitionAnimatorController +import com.android.systemui.animation.DialogCuj +import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.animation.Expandable import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.qs.QSTile @@ -58,6 +64,7 @@ constructor( /** The [QSTile] and [Expandable] used to perform a long-click and click actions */ var qsTile: QSTile? = null var expandable: Expandable? = null + private set /** Haptic effects */ private val durations = @@ -125,8 +132,10 @@ constructor( } fun handleAnimationStart() { - vibrate(longPressHint) - setState(State.RUNNING_FORWARD) + if (state == State.TIMEOUT_WAIT) { + vibrate(longPressHint) + setState(State.RUNNING_FORWARD) + } } /** This function is called both when an animator completes or gets cancelled */ @@ -147,7 +156,10 @@ constructor( setState(getStateForClick()) qsTile?.click(expandable) } - State.RUNNING_BACKWARDS_FROM_CANCEL -> setState(State.IDLE) + State.RUNNING_BACKWARDS_FROM_CANCEL -> { + callback?.onEffectFinishedReversing() + setState(State.IDLE) + } else -> {} } } @@ -222,13 +234,58 @@ constructor( fun resetState() = setState(State.IDLE) + fun createExpandableFromView(view: View) { + expandable = + object : Expandable { + override fun activityTransitionController( + launchCujType: Int?, + cookie: ActivityTransitionAnimator.TransitionCookie?, + component: ComponentName?, + returnCujType: Int?, + ): ActivityTransitionAnimator.Controller? { + val delegatedController = + ActivityTransitionAnimator.Controller.fromView( + view, + launchCujType, + cookie, + component, + returnCujType, + ) + return delegatedController?.let { createTransitionControllerDelegate(it) } + } + + override fun dialogTransitionController( + cuj: DialogCuj?, + ): DialogTransitionAnimator.Controller? = + DialogTransitionAnimator.Controller.fromView(view, cuj) + } + } + + @VisibleForTesting + fun createTransitionControllerDelegate( + controller: ActivityTransitionAnimator.Controller + ): DelegateTransitionAnimatorController { + val delegated = + object : DelegateTransitionAnimatorController(controller) { + override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) { + if (state == State.LONG_CLICKED) { + setState(State.RUNNING_BACKWARDS_FROM_CANCEL) + callback?.onReverseAnimator(false) + } + delegate.onTransitionAnimationCancelled(newKeyguardOccludedState) + } + } + return delegated + } + enum class State { IDLE, /* The effect is idle waiting for touch input */ TIMEOUT_WAIT, /* The effect is waiting for a tap timeout period */ RUNNING_FORWARD, /* The effect is running normally */ /* The effect was interrupted by an ACTION_UP and is now running backwards */ RUNNING_BACKWARDS_FROM_UP, - /* The effect was interrupted by an ACTION_CANCEL and is now running backwards */ + /* The effect was cancelled by an ACTION_CANCEL or a shade collapse and is now running + backwards */ RUNNING_BACKWARDS_FROM_CANCEL, CLICKED, /* The effect has ended with a click */ LONG_CLICKED, /* The effect has ended with a long-click */ @@ -247,7 +304,7 @@ constructor( fun onStartAnimator() /** Reverse the effect animator */ - fun onReverseAnimator() + fun onReverseAnimator(playHaptics: Boolean = true) /** Cancel the effect animator */ fun onCancelAnimator() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt index 59ec87a74980..e5ccc4a14003 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt @@ -119,7 +119,9 @@ constructor( // needed. Also, don't react to wake and unlock events, as we'll be // receiving a call to #dismissAod() shortly when the authentication // completes. - !maybeStartTransitionToOccludedOrInsecureCamera() && + !maybeStartTransitionToOccludedOrInsecureCamera { state, reason -> + startTransitionTo(state, ownerReason = reason) + } && !isWakeAndUnlock(biometricUnlockState.mode) && !primaryBouncerShowing } else { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt index 8f50b03eafec..8ef138eeb16a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt @@ -20,8 +20,11 @@ import android.animation.ValueAnimator import android.annotation.SuppressLint import android.app.DreamManager import com.android.app.animation.Interpolators +import com.android.systemui.Flags.communalSceneKtfRefactor import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor +import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -166,7 +169,7 @@ constructor( } } else if (occluded) { startTransitionTo(KeyguardState.OCCLUDED) - } else if (isIdleOnCommunal) { + } else if (isIdleOnCommunal && !communalSceneKtfRefactor()) { if (SceneContainerFlag.isEnabled) { // TODO(b/336576536): Check if adaptation for scene framework is needed } else { @@ -183,7 +186,7 @@ constructor( if (SceneContainerFlag.isEnabled) { // TODO(b/336576536): Check if adaptation for scene framework is needed } else { - startTransitionTo(KeyguardState.GLANCEABLE_HUB) + transitionToGlanceableHub() } } else { startTransitionTo(KeyguardState.LOCKSCREEN) @@ -218,7 +221,9 @@ constructor( canWakeDirectlyToGone, primaryBouncerShowing) -> if ( - !maybeStartTransitionToOccludedOrInsecureCamera() && + !maybeStartTransitionToOccludedOrInsecureCamera { state, reason -> + startTransitionTo(state, ownerReason = reason) + } && // Handled by dismissFromDozing(). !isWakeAndUnlock(biometricUnlockState.mode) ) { @@ -242,7 +247,7 @@ constructor( ownerReason = "waking from dozing" ) } - } else if (isIdleOnCommunal) { + } else if (isIdleOnCommunal && !communalSceneKtfRefactor()) { if (SceneContainerFlag.isEnabled) { // TODO(b/336576536): Check if adaptation for scene framework is // needed @@ -264,10 +269,7 @@ constructor( // TODO(b/336576536): Check if adaptation for scene framework is // needed } else { - startTransitionTo( - KeyguardState.GLANCEABLE_HUB, - ownerReason = "waking from dozing" - ) + transitionToGlanceableHub() } } else { startTransitionTo( @@ -280,6 +282,18 @@ constructor( } } + private suspend fun transitionToGlanceableHub() { + if (communalSceneKtfRefactor()) { + communalSceneInteractor.changeScene( + CommunalScenes.Communal, + // Immediately show the hub when transitioning from dozing to hub. + CommunalTransitionKeys.Immediately, + ) + } else { + startTransitionTo(KeyguardState.GLANCEABLE_HUB) + } + } + /** Dismisses keyguard from the DOZING state. */ fun dismissFromDozing() { scope.launch { startTransitionTo(KeyguardState.GONE) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt index 453401d2aa7d..4c3a75e765b6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import com.android.app.animation.Interpolators import com.android.app.tracing.coroutines.launch +import com.android.systemui.Flags.communalSceneKtfRefactor import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background @@ -81,7 +82,9 @@ constructor( listenForDreamingToLockscreenOrGone() listenForDreamingToAodOrDozing() listenForTransitionToCamera(scope, keyguardInteractor) - listenForDreamingToGlanceableHub() + if (!communalSceneKtfRefactor()) { + listenForDreamingToGlanceableHub() + } listenForDreamingToPrimaryBouncer() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt index 1a7012abdbe7..d81195060071 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt @@ -19,7 +19,11 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import com.android.app.animation.Interpolators import com.android.app.tracing.coroutines.launch +import com.android.systemui.Flags.communalSceneKtfRefactor +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor +import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -30,6 +34,7 @@ import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf +import com.android.systemui.util.kotlin.BooleanFlowOperators.noneOf import com.android.systemui.util.kotlin.BooleanFlowOperators.not import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds @@ -50,6 +55,7 @@ constructor( private val glanceableHubTransitions: GlanceableHubTransitions, private val communalSettingsInteractor: CommunalSettingsInteractor, keyguardInteractor: KeyguardInteractor, + private val communalSceneInteractor: CommunalSceneInteractor, override val transitionRepository: KeyguardTransitionRepository, override val internalTransitionInteractor: InternalKeyguardTransitionInteractor, transitionInteractor: KeyguardTransitionInteractor, @@ -72,7 +78,9 @@ constructor( if (!communalSettingsInteractor.isCommunalFlagEnabled()) { return } - listenForHubToLockscreenOrDreaming() + if (!communalSceneKtfRefactor()) { + listenForHubToLockscreenOrDreaming() + } listenForHubToDozing() listenForHubToPrimaryBouncer() listenForHubToAlternateBouncer() @@ -120,7 +128,10 @@ constructor( scope.launch("$TAG#listenForHubToPrimaryBouncer") { keyguardInteractor.primaryBouncerShowing .filterRelevantKeyguardStateAnd { primaryBouncerShowing -> primaryBouncerShowing } - .collect { startTransitionTo(KeyguardState.PRIMARY_BOUNCER) } + .collect { + // Bouncer shows on top of the hub, so do not change scenes here. + startTransitionTo(KeyguardState.PRIMARY_BOUNCER) + } } } @@ -130,7 +141,10 @@ constructor( .filterRelevantKeyguardStateAnd { alternateBouncerShowing -> alternateBouncerShowing } - .collect { pair -> startTransitionTo(KeyguardState.ALTERNATE_BOUNCER) } + .collect { pair -> + // Bouncer shows on top of the hub, so do not change scenes here. + startTransitionTo(KeyguardState.ALTERNATE_BOUNCER) + } } } @@ -139,10 +153,18 @@ constructor( powerInteractor.isAsleep .filterRelevantKeyguardStateAnd { isAsleep -> isAsleep } .collect { - startTransitionTo( - toState = KeyguardState.DOZING, - modeOnCanceled = TransitionModeOnCanceled.LAST_VALUE, - ) + if (communalSceneKtfRefactor()) { + communalSceneInteractor.changeScene( + newScene = CommunalScenes.Blank, + transitionKey = CommunalTransitionKeys.Immediately, + keyguardState = KeyguardState.DOZING, + ) + } else { + startTransitionTo( + toState = KeyguardState.DOZING, + modeOnCanceled = TransitionModeOnCanceled.LAST_VALUE, + ) + } } } } @@ -152,7 +174,44 @@ constructor( scope.launch { keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop .filterRelevantKeyguardStateAnd { onTop -> onTop } - .collect { maybeStartTransitionToOccludedOrInsecureCamera() } + .collect { + maybeStartTransitionToOccludedOrInsecureCamera { state, reason -> + if (communalSceneKtfRefactor()) { + communalSceneInteractor.changeScene( + newScene = CommunalScenes.Blank, + transitionKey = CommunalTransitionKeys.SimpleFade, + keyguardState = state, + ) + null + } else { + startTransitionTo(state, ownerReason = reason) + } + } + } + } + } else if (communalSceneKtfRefactor()) { + scope.launch { + allOf( + keyguardInteractor.isKeyguardOccluded, + noneOf( + // Dream is a special-case of occluded, so filter out the dreaming + // case here. + keyguardInteractor.isDreaming, + // When launching activities from widgets on the hub, we have a + // custom occlusion animation. + communalSceneInteractor.isLaunchingWidget, + ), + ) + .filterRelevantKeyguardStateAnd { isOccludedAndNotDreamingNorLaunchingWidget -> + isOccludedAndNotDreamingNorLaunchingWidget + } + .collect { _ -> + communalSceneInteractor.changeScene( + newScene = CommunalScenes.Blank, + transitionKey = CommunalTransitionKeys.SimpleFade, + keyguardState = KeyguardState.OCCLUDED, + ) + } } } else { scope.launch { @@ -160,9 +219,7 @@ constructor( .filterRelevantKeyguardStateAnd { isOccludedAndNotDreaming -> isOccludedAndNotDreaming } - .collect { isOccludedAndNotDreaming -> - startTransitionTo(KeyguardState.OCCLUDED) - } + .collect { _ -> startTransitionTo(KeyguardState.OCCLUDED) } } } } @@ -170,10 +227,33 @@ constructor( private fun listenForHubToGone() { // TODO(b/336576536): Check if adaptation for scene framework is needed if (SceneContainerFlag.isEnabled) return - scope.launch { - keyguardInteractor.isKeyguardGoingAway - .filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway } - .collect { startTransitionTo(KeyguardState.GONE) } + if (communalSceneKtfRefactor()) { + scope.launch { + allOf( + keyguardInteractor.isKeyguardGoingAway, + // TODO(b/327225415): Handle edit mode opening here to avoid going to GONE + // state until after edit mode is ready to be shown. + noneOf( + // When launching activities from widgets on the hub, we wait to change + // scenes until the activity launch is complete. + communalSceneInteractor.isLaunchingWidget, + ), + ) + .filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway } + .collect { + communalSceneInteractor.changeScene( + newScene = CommunalScenes.Blank, + transitionKey = CommunalTransitionKeys.SimpleFade, + keyguardState = KeyguardState.GONE + ) + } + } + } else { + scope.launch { + keyguardInteractor.isKeyguardGoingAway + .filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway } + .collect { startTransitionTo(KeyguardState.GONE) } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index 5c7adf0c5a8a..16c014f451f3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -20,6 +20,7 @@ import android.animation.ValueAnimator import android.util.MathUtils import com.android.app.animation.Interpolators import com.android.app.tracing.coroutines.launch +import com.android.systemui.Flags.communalSceneKtfRefactor import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background @@ -90,7 +91,9 @@ constructor( listenForLockscreenToPrimaryBouncerDragging() listenForLockscreenToAlternateBouncer() listenForLockscreenTransitionToCamera() - listenForLockscreenToGlanceableHub() + if (!communalSceneKtfRefactor()) { + listenForLockscreenToGlanceableHub() + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt index f3ca9df6491c..2f320409f231 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt @@ -18,8 +18,12 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import com.android.app.animation.Interpolators +import com.android.systemui.Flags.communalSceneKtfRefactor import com.android.systemui.Flags.restartDreamOnUnocclude import com.android.systemui.communal.domain.interactor.CommunalInteractor +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor +import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -49,6 +53,7 @@ constructor( keyguardInteractor: KeyguardInteractor, powerInteractor: PowerInteractor, private val communalInteractor: CommunalInteractor, + private val communalSceneInteractor: CommunalSceneInteractor, keyguardOcclusionInteractor: KeyguardOcclusionInteractor, ) : TransitionInteractor( @@ -140,7 +145,14 @@ constructor( } else if (isIdleOnCommunal || showCommunalFromOccluded) { // TODO(b/336576536): Check if adaptation for scene framework is needed if (SceneContainerFlag.isEnabled) return - startTransitionTo(KeyguardState.GLANCEABLE_HUB) + if (communalSceneKtfRefactor()) { + communalSceneInteractor.changeScene( + CommunalScenes.Communal, + CommunalTransitionKeys.SimpleFade + ) + } else { + startTransitionTo(KeyguardState.GLANCEABLE_HUB) + } } else { startTransitionTo(KeyguardState.LOCKSCREEN) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt index 24290881a0fb..6c89ce055470 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt @@ -113,10 +113,9 @@ constructor( (isBouncerShowing, isAwake, isActiveDreamLockscreenHosted, isIdleOnCommunal) -> if ( - !maybeStartTransitionToOccludedOrInsecureCamera() && - !isBouncerShowing && - isAwake && - !isActiveDreamLockscreenHosted + !maybeStartTransitionToOccludedOrInsecureCamera { state, reason -> + startTransitionTo(state, ownerReason = reason) + } && !isBouncerShowing && isAwake && !isActiveDreamLockscreenHosted ) { val toState = if (isIdleOnCommunal) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt index 89c717892d0a..d06ee645652c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt @@ -122,9 +122,14 @@ sealed class TransitionInteractor( * SHOW_WHEN_LOCKED activity, or back to [KeyguardState.GONE], for some power button launch * gesture cases. If so, start the transition. * + * @param startTransition A callback which is triggered to start the transition to the desired + * KeyguardState. Allows caller to hook into the transition start if needed. + * * Returns true if a transition was started, false otherwise. */ - suspend fun maybeStartTransitionToOccludedOrInsecureCamera(): Boolean { + suspend fun maybeStartTransitionToOccludedOrInsecureCamera( + startTransition: suspend (state: KeyguardState, reason: String) -> UUID? + ): Boolean { // The refactor is required for the occlusion interactor to work. KeyguardWmStateRefactor.isUnexpectedlyInLegacyMode() @@ -136,10 +141,7 @@ sealed class TransitionInteractor( if (!maybeHandleInsecurePowerGesture()) { // Otherwise, the double tap gesture occurred while not GONE and not dismissable, // which means we will launch the secure camera, which OCCLUDES the keyguard. - startTransitionTo( - KeyguardState.OCCLUDED, - ownerReason = "Power button gesture on lockscreen" - ) + startTransition(KeyguardState.OCCLUDED, "Power button gesture on lockscreen") } return true @@ -147,10 +149,7 @@ sealed class TransitionInteractor( // A SHOW_WHEN_LOCKED activity is on top of the task stack. Transition to OCCLUDED so // it's visible. // TODO(b/307976454) - Centralize transition to DREAMING here. - startTransitionTo( - KeyguardState.OCCLUDED, - ownerReason = "SHOW_WHEN_LOCKED activity on top" - ) + startTransition(KeyguardState.OCCLUDED, "SHOW_WHEN_LOCKED activity on top") return true } else { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index 6b3dfe1b90ad..dbfe8188b1b5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -107,7 +107,9 @@ constructor( set(value) { if (field == value) return field = value - updateHeight() + if (longPressEffect?.state != QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_CANCEL) { + updateHeight() + } } override var squishinessFraction: Float = 1f @@ -381,14 +383,6 @@ constructor( } private fun updateHeight() { - // TODO(b/332900989): Find a more robust way of resetting the tile if not reset by the - // launch animation. - if (!haveLongPressPropertiesBeenReset && longPressEffect != null) { - // The launch animation of a long-press effect did not reset the long-press effect so - // we must do it here - resetLongPressEffectProperties() - longPressEffect.resetState() - } val actualHeight = if (heightOverride != HeightOverrideable.NO_OVERRIDE) { heightOverride @@ -417,17 +411,17 @@ constructor( } override fun init(tile: QSTile) { - val expandable = Expandable.fromView(this) if (longPressEffect != null) { isHapticFeedbackEnabled = false longPressEffect.qsTile = tile - longPressEffect.expandable = expandable + longPressEffect.createExpandableFromView(this) initLongPressEffectCallback() init( { _: View -> longPressEffect.onTileClick() }, null, // Haptics and long-clicks will be handled by the [QSLongPressEffect] ) } else { + val expandable = Expandable.fromView(this) init( { _: View? -> tile.click(expandable) }, { _: View? -> @@ -475,10 +469,10 @@ constructor( } } - override fun onReverseAnimator() { + override fun onReverseAnimator(playHaptics: Boolean) { longPressEffectAnimator?.let { val pausedProgress = it.animatedFraction - longPressEffect?.playReverseHaptics(pausedProgress) + if (playHaptics) longPressEffect?.playReverseHaptics(pausedProgress) it.reverse() } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java index 49144091cb62..54ae225442c9 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java @@ -49,6 +49,7 @@ import java.util.List; import java.util.Random; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; /** * An AsyncTask that saves an image to the media store in the background. @@ -59,12 +60,73 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { private static final String SCREENSHOT_ID_TEMPLATE = "Screenshot_%s"; private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)"; + /** + * POD used in the AsyncTask which saves an image in the background. + */ + static class SaveImageInBackgroundData { + public Bitmap image; + public Consumer<Uri> finisher; + public ActionsReadyListener mActionsReadyListener; + public QuickShareActionReadyListener mQuickShareActionsReadyListener; + public UserHandle owner; + public int displayId; + + void clearImage() { + image = null; + } + } + + /** + * Structure returned by the SaveImageInBackgroundTask + */ + public static class SavedImageData { + public Uri uri; + public List<Notification.Action> smartActions; + public Notification.Action quickShareAction; + public UserHandle owner; + public String subject; // Title for sharing + public Long imageTime; // Time at which screenshot was saved + + /** + * Used to reset the return data on error + */ + public void reset() { + uri = null; + smartActions = null; + quickShareAction = null; + subject = null; + imageTime = null; + } + } + + /** + * Structure returned by the QueryQuickShareInBackgroundTask + */ + static class QuickShareData { + public Notification.Action quickShareAction; + + /** + * Used to reset the return data on error + */ + public void reset() { + quickShareAction = null; + } + } + + interface ActionsReadyListener { + void onActionsReady(SavedImageData imageData); + } + + interface QuickShareActionReadyListener { + void onActionsReady(QuickShareData quickShareData); + } + private final Context mContext; private FeatureFlags mFlags; private final ScreenshotSmartActions mScreenshotSmartActions; - private final ScreenshotController.SaveImageInBackgroundData mParams; - private final ScreenshotController.SavedImageData mImageData; - private final ScreenshotController.QuickShareData mQuickShareData; + private final SaveImageInBackgroundData mParams; + private final SavedImageData mImageData; + private final QuickShareData mQuickShareData; private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider; private String mScreenshotId; @@ -77,15 +139,15 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { FeatureFlags flags, ImageExporter exporter, ScreenshotSmartActions screenshotSmartActions, - ScreenshotController.SaveImageInBackgroundData data, + SaveImageInBackgroundData data, ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider ) { mContext = context; mFlags = flags; mScreenshotSmartActions = screenshotSmartActions; - mImageData = new ScreenshotController.SavedImageData(); - mQuickShareData = new ScreenshotController.QuickShareData(); + mImageData = new SavedImageData(); + mQuickShareData = new QuickShareData(); mImageExporter = exporter; // Prepare all the output metadata @@ -195,7 +257,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { * Update the listener run when the saving task completes. Used to avoid showing UI for the * first screenshot when a second one is taken. */ - void setActionsReadyListener(ScreenshotController.ActionsReadyListener listener) { + void setActionsReadyListener(ActionsReadyListener listener) { mParams.mActionsReadyListener = listener; } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 773900981759..0a4635e0849b 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -35,10 +35,7 @@ import android.animation.AnimatorListenerAdapter; import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.ActivityOptions; -import android.app.ExitTransitionCoordinator; import android.app.ICompatCameraControlCallback; -import android.app.Notification; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -55,7 +52,6 @@ import android.os.UserManager; import android.provider.Settings; import android.util.DisplayMetrics; import android.util.Log; -import android.util.Pair; import android.view.Display; import android.view.ScrollCaptureResponse; import android.view.View; @@ -67,7 +63,6 @@ import android.view.WindowManager; import android.widget.Toast; import android.window.WindowContext; -import com.android.internal.app.ChooserActivity; import com.android.internal.logging.UiEventLogger; import com.android.internal.policy.PhoneWindow; import com.android.settingslib.applications.InterestingConfigChanges; @@ -89,7 +84,6 @@ import dagger.assisted.AssistedInject; import kotlin.Unit; -import java.util.List; import java.util.UUID; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; @@ -104,67 +98,6 @@ import javax.inject.Provider; public class ScreenshotController implements ScreenshotHandler { private static final String TAG = logTag(ScreenshotController.class); - /** - * POD used in the AsyncTask which saves an image in the background. - */ - static class SaveImageInBackgroundData { - public Bitmap image; - public Consumer<Uri> finisher; - public ScreenshotController.ActionsReadyListener mActionsReadyListener; - public ScreenshotController.QuickShareActionReadyListener mQuickShareActionsReadyListener; - public UserHandle owner; - public int displayId; - - void clearImage() { - image = null; - } - } - - /** - * Structure returned by the SaveImageInBackgroundTask - */ - public static class SavedImageData { - public Uri uri; - public List<Notification.Action> smartActions; - public Notification.Action quickShareAction; - public UserHandle owner; - public String subject; // Title for sharing - public Long imageTime; // Time at which screenshot was saved - - /** - * Used to reset the return data on error - */ - public void reset() { - uri = null; - smartActions = null; - quickShareAction = null; - subject = null; - imageTime = null; - } - } - - /** - * Structure returned by the QueryQuickShareInBackgroundTask - */ - static class QuickShareData { - public Notification.Action quickShareAction; - - /** - * Used to reset the return data on error - */ - public void reset() { - quickShareAction = null; - } - } - - interface ActionsReadyListener { - void onActionsReady(ScreenshotController.SavedImageData imageData); - } - - interface QuickShareActionReadyListener { - void onActionsReady(ScreenshotController.QuickShareData quickShareData); - } - public interface TransitionDestination { /** * Allows the long screenshot activity to call back with a destination location (the bounds @@ -213,7 +146,6 @@ public class ScreenshotController implements ScreenshotHandler { private final ScreenshotNotificationSmartActionsProvider mScreenshotNotificationSmartActionsProvider; private final TimeoutHandler mScreenshotHandler; - private final ActionIntentExecutor mActionIntentExecutor; private final UserManager mUserManager; private final AssistContentRequester mAssistContentRequester; private final ActionExecutor mActionExecutor; @@ -259,7 +191,6 @@ public class ScreenshotController implements ScreenshotHandler { BroadcastDispatcher broadcastDispatcher, ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider, ScreenshotActionsController.Factory screenshotActionsControllerFactory, - ActionIntentExecutor actionIntentExecutor, ActionExecutor.Factory actionExecutorFactory, UserManager userManager, AssistContentRequester assistContentRequester, @@ -289,7 +220,6 @@ public class ScreenshotController implements ScreenshotHandler { final Context displayContext = context.createDisplayContext(display); mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null); mFlags = flags; - mActionIntentExecutor = actionIntentExecutor; mUserManager = userManager; mMessageContainerController = messageContainerController; mAssistContentRequester = assistContentRequester; @@ -765,33 +695,6 @@ public class ScreenshotController implements ScreenshotHandler { mScreenshotAnimation.start(); } - /** - * Supplies the necessary bits for the shared element transition to share sheet. - * Note that once called, the action intent to share must be sent immediately after. - */ - private Pair<ActivityOptions, ExitTransitionCoordinator> createWindowTransition() { - ExitTransitionCoordinator.ExitTransitionCallbacks callbacks = - new ExitTransitionCoordinator.ExitTransitionCallbacks() { - @Override - public boolean isReturnTransitionAllowed() { - return false; - } - - @Override - public void hideSharedElements() { - finishDismiss(); - } - - @Override - public void onFinish() { - } - }; - - return ActivityOptions.startSharedElementAnimation(mWindow, callbacks, null, - Pair.create(mViewProxy.getScreenshotPreview(), - ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME)); - } - /** Reset screenshot view and then call onCompleteRunnable */ private void finishDismiss() { Log.d(TAG, "finishDismiss"); @@ -838,11 +741,11 @@ public class ScreenshotController implements ScreenshotHandler { private void saveScreenshotInWorkerThread( UserHandle owner, @NonNull Consumer<Uri> finisher, - @Nullable ActionsReadyListener actionsReadyListener, - @Nullable QuickShareActionReadyListener + @Nullable SaveImageInBackgroundTask.ActionsReadyListener actionsReadyListener, + @Nullable SaveImageInBackgroundTask.QuickShareActionReadyListener quickShareActionsReadyListener) { - ScreenshotController.SaveImageInBackgroundData - data = new ScreenshotController.SaveImageInBackgroundData(); + SaveImageInBackgroundTask.SaveImageInBackgroundData + data = new SaveImageInBackgroundTask.SaveImageInBackgroundData(); data.image = mScreenBitmap; data.finisher = finisher; data.mActionsReadyListener = actionsReadyListener; @@ -881,7 +784,7 @@ public class ScreenshotController implements ScreenshotHandler { /** * Logs success/failure of the screenshot saving task, and shows an error if it failed. */ - private void logSuccessOnActionsReady(ScreenshotController.SavedImageData imageData) { + private void logSuccessOnActionsReady(SaveImageInBackgroundTask.SavedImageData imageData) { logScreenshotResultStatus(imageData.uri, imageData.owner); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index c9126161c40f..b9d24abc4037 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -51,6 +51,7 @@ import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; +import android.os.UserManager; import android.util.Pair; import android.util.SparseArray; import android.view.KeyEvent; @@ -200,6 +201,7 @@ public class CommandQueue extends IStatusBar.Stub implements * event. */ private int mLastUpdatedImeDisplayId = INVALID_DISPLAY; + private final Context mContext; private final DisplayTracker mDisplayTracker; private final @Nullable CommandRegistry mRegistry; private final @Nullable DumpHandler mDumpHandler; @@ -571,6 +573,7 @@ public class CommandQueue extends IStatusBar.Stub implements DumpHandler dumpHandler, Lazy<PowerInteractor> powerInteractor ) { + mContext = context; mDisplayTracker = displayTracker; mRegistry = registry; mDumpHandler = dumpHandler; @@ -1209,7 +1212,12 @@ public class CommandQueue extends IStatusBar.Stub implements boolean showImeSwitcher) { if (displayId == INVALID_DISPLAY) return; - if (mLastUpdatedImeDisplayId != displayId + boolean isConcurrentMultiUserModeEnabled = UserManager.isVisibleBackgroundUsersEnabled() + && mContext.getResources().getBoolean(android.R.bool.config_perDisplayFocusEnabled) + && android.view.inputmethod.Flags.concurrentInputMethods(); + + if (!isConcurrentMultiUserModeEnabled + && mLastUpdatedImeDisplayId != displayId && mLastUpdatedImeDisplayId != INVALID_DISPLAY) { // Set previous NavBar's IME window status as invisible when IME // window switched to another display for single-session IME case. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index 789a6f4483a3..e08dbb9df7dc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -308,6 +308,9 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(entry.getKey()); if (headsUpEntry != null && headsUpEntry.mRemoteInputActive != remoteInputActive) { headsUpEntry.mRemoteInputActive = remoteInputActive; + if (ExpandHeadsUpOnInlineReply.isEnabled() && remoteInputActive) { + headsUpEntry.mRemoteInputActivatedAtLeastOnce = true; + } if (remoteInputActive) { headsUpEntry.cancelAutoRemovalCallbacks("setRemoteInputActive(true)"); } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java index a0eb989a57bb..65171358b050 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java @@ -41,6 +41,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun; import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor; +import com.android.systemui.statusbar.phone.ExpandHeadsUpOnInlineReply; import com.android.systemui.util.ListenerSet; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.settings.GlobalSettings; @@ -726,6 +727,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { * of AvalancheController that take it as param. */ public class HeadsUpEntry implements Comparable<HeadsUpEntry> { + public boolean mRemoteInputActivatedAtLeastOnce; public boolean mRemoteInputActive; public boolean mUserActionMayIndirectlyRemove; @@ -835,6 +837,15 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { */ public boolean isSticky() { if (mEntry == null) return false; + + if (ExpandHeadsUpOnInlineReply.isEnabled()) { + // we don't consider pinned and expanded huns as sticky after the remote input + // has been activated for them + if (!mRemoteInputActive && mRemoteInputActivatedAtLeastOnce) { + return false; + } + } + return (mEntry.isRowPinned() && mExpanded) || mRemoteInputActive || hasFullScreenIntent(mEntry); diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt index 25dd9fedba7c..24e8b1886438 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt @@ -27,7 +27,6 @@ import android.os.UserHandle import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.screenshot.ScreenshotController.SaveImageInBackgroundData import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq @@ -44,7 +43,7 @@ class SaveImageInBackgroundTaskTest : SysuiTestCase() { private val imageExporter = mock<ImageExporter>() private val smartActions = mock<ScreenshotSmartActions>() private val smartActionsProvider = mock<ScreenshotNotificationSmartActionsProvider>() - private val saveImageData = SaveImageInBackgroundData() + private val saveImageData = SaveImageInBackgroundTask.SaveImageInBackgroundData() private val testScreenshotId: String = "testScreenshotId" private val testBitmap = mock<Bitmap>() private val testUser = UserHandle.getUserHandleForUid(0) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt index 079852a641c2..494f08b88345 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.domain.interactor +import com.android.systemui.communal.domain.interactor.communalSceneInteractor import com.android.systemui.communal.domain.interactor.communalSettingsInteractor import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository import com.android.systemui.kosmos.Kosmos @@ -38,5 +39,6 @@ var Kosmos.fromGlanceableHubTransitionInteractor by transitionInteractor = keyguardTransitionInteractor, powerInteractor = powerInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, + communalSceneInteractor = communalSceneInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt index c2169456eac5..7827655806d8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.communal.domain.interactor.communalSceneInteractor import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope @@ -37,5 +38,6 @@ val Kosmos.fromOccludedTransitionInteractor by powerInteractor = powerInteractor, communalInteractor = communalInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, + communalSceneInteractor = communalSceneInteractor, ) } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java index 3d53deb8d2bb..4fc9d55d3e17 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java @@ -103,9 +103,10 @@ class CompanionDeviceShellCommand extends ShellCommand { String packageName = getNextArgRequired(); String address = getNextArgRequired(); String deviceProfile = getNextArg(); + boolean selfManaged = getNextBooleanArg(); final MacAddress macAddress = MacAddress.fromString(address); mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddress, - deviceProfile, deviceProfile, /* associatedDevice */ null, false, + deviceProfile, deviceProfile, /* associatedDevice */ null, selfManaged, /* callback */ null, /* resultReceiver */ null); } break; @@ -462,6 +463,17 @@ class CompanionDeviceShellCommand extends ShellCommand { } } + private boolean getNextBooleanArg() { + String arg = getNextArg(); + if (arg == null || "false".equalsIgnoreCase(arg)) { + return false; + } else if ("true".equalsIgnoreCase(arg)) { + return Boolean.parseBoolean(arg); + } else { + throw new IllegalArgumentException("Expected a boolean argument but was: " + arg); + } + } + @Override public void onHelp() { PrintWriter pw = getOutPrintWriter(); @@ -470,7 +482,7 @@ class CompanionDeviceShellCommand extends ShellCommand { pw.println(" Print this help text."); pw.println(" list USER_ID"); pw.println(" List all Associations for a user."); - pw.println(" associate USER_ID PACKAGE MAC_ADDRESS [DEVICE_PROFILE]"); + pw.println(" associate USER_ID PACKAGE MAC_ADDRESS [DEVICE_PROFILE] [SELF_MANAGED]"); pw.println(" Create a new Association."); pw.println(" disassociate USER_ID PACKAGE MAC_ADDRESS"); pw.println(" Remove an existing Association."); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 69ee8fc831f4..cf0befaab98d 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -16961,16 +16961,24 @@ public class ActivityManagerService extends IActivityManager.Stub int userId = UserHandle.getCallingUserId(); - if (UserManager.isVisibleBackgroundUsersEnabled() && userId != getCurrentUserId()) { - // The check is added mainly for auto devices. On auto devices, it is possible that - // multiple users are visible simultaneously using visible background users. - // In such cases, it is desired that only the current user (not the visible background - // user) can change the locale and other persistent settings of the device. - Slog.w(TAG, "Only current user is allowed to update persistent configuration if " - + "visible background users are enabled. Current User" + getCurrentUserId() - + ". Calling User: " + userId); - throw new SecurityException("Only current user is allowed to update persistent " - + "configuration."); + if (UserManager.isVisibleBackgroundUsersEnabled()) { + final long origId = Binder.clearCallingIdentity(); + try { + if (userId != getCurrentUserId()) { + // The check is added mainly for auto devices. On auto devices, it is + // possible that multiple users are visible simultaneously using visible + // background users. In such cases, it is desired that only the current user + // (not the visible background user) can change the locale and other persistent + // settings of the device. + Slog.w(TAG, "Only current user is allowed to update persistent configuration " + + "if visible background users are enabled. Current User" + + getCurrentUserId() + ". Calling User: " + userId); + throw new SecurityException("Only current user is allowed to update persistent " + + "configuration."); + } + } finally { + Binder.restoreCallingIdentity(origId); + } } mActivityTaskManager.updatePersistentConfiguration(values, userId); diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 8e8a0378c111..8ec835bae0bf 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -21,6 +21,7 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; import static android.hardware.biometrics.BiometricManager.Authenticators; import static android.hardware.biometrics.BiometricManager.BIOMETRIC_NO_AUTHENTICATION; +import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG; import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_IDLE; @@ -41,6 +42,7 @@ import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricPrompt; +import android.hardware.biometrics.BiometricStateListener; import android.hardware.biometrics.Flags; import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; @@ -54,8 +56,12 @@ import android.hardware.biometrics.ITestSessionCallback; import android.hardware.biometrics.PromptInfo; import android.hardware.biometrics.SensorPropertiesInternal; import android.hardware.camera2.CameraManager; +import android.hardware.face.FaceManager; +import android.hardware.face.FaceSensorPropertiesInternal; +import android.hardware.face.IFaceAuthenticatorsRegisteredCallback; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; +import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback; import android.hardware.security.keymint.HardwareAuthenticatorType; import android.net.Uri; import android.os.Binder; @@ -234,6 +240,8 @@ public class BiometricService extends SystemService { private static final boolean DEFAULT_APP_ENABLED = true; private static final boolean DEFAULT_ALWAYS_REQUIRE_CONFIRMATION = false; private static final boolean DEFAULT_MANDATORY_BIOMETRICS_STATUS = false; + private static final boolean DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS = + true; // Some devices that shipped before S already have face-specific settings. Instead of // migrating, which is complicated, let's just keep using the existing settings. @@ -256,6 +264,8 @@ public class BiometricService extends SystemService { Settings.Secure.getUriFor(Settings.Secure.BIOMETRIC_APP_ENABLED); private final Uri MANDATORY_BIOMETRICS_ENABLED = Settings.Secure.getUriFor(Settings.Secure.MANDATORY_BIOMETRICS); + private final Uri MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED = Settings.Secure.getUriFor( + Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED); private final ContentResolver mContentResolver; private final List<BiometricService.EnabledOnKeyguardCallback> mCallbacks; @@ -264,6 +274,12 @@ public class BiometricService extends SystemService { private final Map<Integer, Boolean> mBiometricEnabledForApps = new HashMap<>(); private final Map<Integer, Boolean> mFaceAlwaysRequireConfirmation = new HashMap<>(); private final Map<Integer, Boolean> mMandatoryBiometricsEnabled = new HashMap<>(); + private final Map<Integer, Boolean> mMandatoryBiometricsRequirementsSatisfied = + new HashMap<>(); + private final Map<Integer, Boolean> mFingerprintEnrolledForUser = + new HashMap<>(); + private final Map<Integer, Boolean> mFaceEnrolledForUser = + new HashMap<>(); /** * Creates a content observer. @@ -288,7 +304,13 @@ public class BiometricService extends SystemService { mMandatoryBiometricsEnabled.put(context.getUserId(), Settings.Secure.getIntForUser( mContentResolver, Settings.Secure.MANDATORY_BIOMETRICS, DEFAULT_MANDATORY_BIOMETRICS_STATUS ? 1 : 0, context.getUserId()) != 0); + mMandatoryBiometricsRequirementsSatisfied.put(context.getUserId(), + Settings.Secure.getIntForUser(mContentResolver, + Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED, + DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS ? 1 : 0, + context.getUserId()) != 0); + addBiometricListenersForMandatoryBiometrics(context); updateContentObserver(); } @@ -322,6 +344,10 @@ public class BiometricService extends SystemService { false /* notifyForDescendants */, this /* observer */, UserHandle.USER_ALL); + mContentResolver.registerContentObserver(MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED, + false /* notifyForDescendants */, + this /* observer */, + UserHandle.USER_ALL); } @Override @@ -370,6 +396,13 @@ public class BiometricService extends SystemService { Settings.Secure.MANDATORY_BIOMETRICS, DEFAULT_MANDATORY_BIOMETRICS_STATUS ? 1 : 0 /* default */, userId) != 0); + } else if (MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED.equals(uri)) { + mMandatoryBiometricsRequirementsSatisfied.put(userId, Settings.Secure.getIntForUser( + mContentResolver, + Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED, + DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS + ? 1 : 0 /* default */, + userId) != 0); } } @@ -411,9 +444,15 @@ public class BiometricService extends SystemService { } } - public boolean getMandatoryBiometricsEnabledForUser(int userId) { + public boolean getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(int userId) { return mMandatoryBiometricsEnabled.getOrDefault(userId, - DEFAULT_MANDATORY_BIOMETRICS_STATUS); + DEFAULT_MANDATORY_BIOMETRICS_STATUS) + && mMandatoryBiometricsRequirementsSatisfied.getOrDefault(userId, + DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS) + && mBiometricEnabledForApps.getOrDefault(userId, DEFAULT_APP_ENABLED) + && getEnabledForApps(userId) + && (mFingerprintEnrolledForUser.getOrDefault(userId, false /* default */) + || mFaceEnrolledForUser.getOrDefault(userId, false /* default */)); } void notifyEnabledOnKeyguardCallbacks(int userId) { @@ -424,6 +463,79 @@ public class BiometricService extends SystemService { userId); } } + + private void addBiometricListenersForMandatoryBiometrics(Context context) { + final FingerprintManager fingerprintManager = context.getSystemService( + FingerprintManager.class); + final FaceManager faceManager = context.getSystemService(FaceManager.class); + if (fingerprintManager != null) { + fingerprintManager.addAuthenticatorsRegisteredCallback( + new IFingerprintAuthenticatorsRegisteredCallback.Stub() { + @Override + public void onAllAuthenticatorsRegistered( + List<FingerprintSensorPropertiesInternal> list) { + if (list == null || list.isEmpty()) { + Slog.d(TAG, "No fingerprint authenticators registered."); + return; + } + final FingerprintSensorPropertiesInternal + fingerprintSensorProperties = list.get(0); + if (fingerprintSensorProperties.sensorStrength + == STRENGTH_STRONG) { + fingerprintManager.registerBiometricStateListener( + new BiometricStateListener() { + @Override + public void onEnrollmentsChanged( + int userId, + int sensorId, + boolean hasEnrollments + ) { + if (sensorId == fingerprintSensorProperties + .sensorId) { + mFingerprintEnrolledForUser.put(userId, + hasEnrollments); + } + } + }); + } + } + }); + } + if (faceManager != null) { + faceManager.addAuthenticatorsRegisteredCallback( + new IFaceAuthenticatorsRegisteredCallback.Stub() { + @Override + public void onAllAuthenticatorsRegistered( + List<FaceSensorPropertiesInternal> list) { + if (list == null || list.isEmpty()) { + Slog.d(TAG, "No face authenticators registered."); + return; + } + final FaceSensorPropertiesInternal + faceSensorPropertiesInternal = list.get(0); + if (faceSensorPropertiesInternal.sensorStrength + == STRENGTH_STRONG) { + faceManager.registerBiometricStateListener( + new BiometricStateListener() { + @Override + public void onEnrollmentsChanged( + int userId, + int sensorId, + boolean hasEnrollments + ) { + if (sensorId + == faceSensorPropertiesInternal + .sensorId) { + mFaceEnrolledForUser.put(userId, + hasEnrollments); + } + } + }); + } + } + }); + } + } } final class EnabledOnKeyguardCallback implements IBinder.DeathRecipient { diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java index b9e6563a7d6a..0bd22f3da67f 100644 --- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java +++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java @@ -112,8 +112,8 @@ class PreAuthInfo { == BiometricManager.Authenticators.MANDATORY_BIOMETRICS; if (dropCredentialFallback(promptInfo.getAuthenticators(), - settingObserver.getMandatoryBiometricsEnabledForUser(userId), - trustManager)) { + settingObserver.getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser( + userId), trustManager)) { promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG); promptInfo.setNegativeButtonText(context.getString(R.string.cancel)); } diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index 73aa14ba016f..78f71877afed 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -684,7 +684,8 @@ public class ClipboardService extends SystemService { if (clipboard == null) { return null; } - showAccessNotificationLocked(pkg, intendingUid, intendingUserId, clipboard); + showAccessNotificationLocked( + pkg, intendingUid, intendingUserId, clipboard, deviceId); notifyTextClassifierLocked(clipboard, pkg, intendingUid); if (clipboard.primaryClip != null) { scheduleAutoClear(userId, intendingUid, intendingDeviceId); @@ -1438,7 +1439,7 @@ public class ClipboardService extends SystemService { */ @GuardedBy("mLock") private void showAccessNotificationLocked(String callingPackage, int uid, @UserIdInt int userId, - Clipboard clipboard) { + Clipboard clipboard, int accessDeviceId) { if (clipboard.primaryClip == null) { return; } @@ -1477,7 +1478,7 @@ public class ClipboardService extends SystemService { return; } - final ArraySet<Context> toastContexts = getToastContexts(clipboard); + final ArraySet<Context> toastContexts = getToastContexts(clipboard, accessDeviceId); Binder.withCleanCallingIdentity(() -> { try { CharSequence callingAppLabel = mPm.getApplicationLabel( @@ -1516,40 +1517,55 @@ public class ClipboardService extends SystemService { * If the clipboard is for a VirtualDevice, we attempt to return the single DisplayContext for * the focused VirtualDisplay for that device, but might need to return the contexts for * multiple displays if the VirtualDevice has several but none of them were focused. + * + * If the clipboard is NOT for a VirtualDevice, but it's being accessed from a VirtualDevice, + * this means that the clipboard is shared between the default and that device. In this case we + * need to show a toast in both places. */ - private ArraySet<Context> getToastContexts(Clipboard clipboard) throws IllegalStateException { + private ArraySet<Context> getToastContexts(Clipboard clipboard, int accessDeviceId) + throws IllegalStateException { ArraySet<Context> contexts = new ArraySet<>(); + if (clipboard.deviceId == DEVICE_ID_DEFAULT || accessDeviceId == DEVICE_ID_DEFAULT) { + // Always show the toast on the default display when the default clipboard is accessed - + // also when the clipboard is shared with a virtual device and accessed from there. + // Same when any clipboard is accessed from the default device. + contexts.add(getContext()); + } - if (mVdmInternal != null && clipboard.deviceId != DEVICE_ID_DEFAULT) { - DisplayManager displayManager = getContext().getSystemService(DisplayManager.class); + if ((accessDeviceId == DEVICE_ID_DEFAULT && clipboard.deviceId == DEVICE_ID_DEFAULT) + || mVdmInternal == null) { + // No virtual devices involved. + return contexts; + } - int topFocusedDisplayId = mWm.getTopFocusedDisplayId(); - ArraySet<Integer> displayIds = mVdmInternal.getDisplayIdsForDevice(clipboard.deviceId); + // At this point the clipboard is either accessed from a virtual device, or it is a virtual + // device clipboard, so show a toast on the relevant virtual display(s). + DisplayManager displayManager = getContext().getSystemService(DisplayManager.class); + ArraySet<Integer> displayIds = mVdmInternal.getDisplayIdsForDevice(accessDeviceId); + int topFocusedDisplayId = mWm.getTopFocusedDisplayId(); - if (displayIds.contains(topFocusedDisplayId)) { - Display display = displayManager.getDisplay(topFocusedDisplayId); - if (display != null) { - contexts.add(getContext().createDisplayContext(display)); - return contexts; - } + if (displayIds.contains(topFocusedDisplayId)) { + Display display = displayManager.getDisplay(topFocusedDisplayId); + if (display != null) { + contexts.add(getContext().createDisplayContext(display)); + return contexts; } + } - for (int i = 0; i < displayIds.size(); i++) { - Display display = displayManager.getDisplay(displayIds.valueAt(i)); - if (display != null) { - contexts.add(getContext().createDisplayContext(display)); - } - } - if (!contexts.isEmpty()) { - return contexts; + for (int i = 0; i < displayIds.size(); i++) { + Display display = displayManager.getDisplay(displayIds.valueAt(i)); + if (display != null) { + contexts.add(getContext().createDisplayContext(display)); } + } + if (contexts.isEmpty()) { Slog.e(TAG, "getToastContexts Couldn't find any VirtualDisplays for VirtualDevice " - + clipboard.deviceId); + + accessDeviceId); // Since we couldn't find any VirtualDisplays to use at all, just fall through to using // the default display below. + contexts.add(getContext()); } - contexts.add(getContext()); return contexts; } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index a9e9dcfa6b2f..cbe202b5adc8 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -1483,17 +1483,15 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mContext.enforceCallingOrSelfPermission( Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); } + if (!mUserManagerInternal.exists(userId)) { + return InputMethodInfoSafeList.empty(); + } synchronized (ImfLock.class) { - final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId, - mCurrentUserId, null); - if (resolvedUserIds.length != 1) { - return InputMethodInfoSafeList.empty(); - } final int callingUid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); try { return InputMethodInfoSafeList.create(getInputMethodListLocked( - resolvedUserIds[0], directBootAwareness, callingUid)); + userId, directBootAwareness, callingUid)); } finally { Binder.restoreCallingIdentity(ident); } @@ -1508,17 +1506,15 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mContext.enforceCallingOrSelfPermission( Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); } + if (!mUserManagerInternal.exists(userId)) { + return InputMethodInfoSafeList.empty(); + } synchronized (ImfLock.class) { - final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId, - mCurrentUserId, null); - if (resolvedUserIds.length != 1) { - return InputMethodInfoSafeList.empty(); - } final int callingUid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); try { return InputMethodInfoSafeList.create( - getEnabledInputMethodListLocked(resolvedUserIds[0], callingUid)); + getEnabledInputMethodListLocked(userId, callingUid)); } finally { Binder.restoreCallingIdentity(ident); } @@ -1534,17 +1530,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mContext.enforceCallingOrSelfPermission( Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); } + if (!mUserManagerInternal.exists(userId)) { + return Collections.emptyList(); + } synchronized (ImfLock.class) { - final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId, - mCurrentUserId, null); - if (resolvedUserIds.length != 1) { - return Collections.emptyList(); - } final int callingUid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); try { - return getInputMethodListLocked( - resolvedUserIds[0], directBootAwareness, callingUid); + return getInputMethodListLocked(userId, directBootAwareness, callingUid); } finally { Binder.restoreCallingIdentity(ident); } @@ -1559,16 +1552,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mContext.enforceCallingOrSelfPermission( Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); } + if (!mUserManagerInternal.exists(userId)) { + return Collections.emptyList(); + } synchronized (ImfLock.class) { - final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId, - mCurrentUserId, null); - if (resolvedUserIds.length != 1) { - return Collections.emptyList(); - } final int callingUid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); try { - return getEnabledInputMethodListLocked(resolvedUserIds[0], callingUid); + return getEnabledInputMethodListLocked(userId, callingUid); } finally { Binder.restoreCallingIdentity(ident); } diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java index 3d0b079c69c8..741513cf3c0b 100644 --- a/services/core/java/com/android/server/locales/LocaleManagerService.java +++ b/services/core/java/com/android/server/locales/LocaleManagerService.java @@ -616,9 +616,10 @@ public class LocaleManagerService extends SystemService { LocaleConfig resLocaleConfig = null; try { resLocaleConfig = LocaleConfig.fromContextIgnoringOverride( - mContext.createPackageContext(appPackageName, 0)); + mContext.createPackageContextAsUser(appPackageName, /* flags= */ 0, + UserHandle.of(userId))); } catch (PackageManager.NameNotFoundException e) { - Slog.e(TAG, "Unknown package name " + appPackageName); + Slog.e(TAG, "Unknown package name " + appPackageName + " for user " + userId); return; } final File file = getXmlFileNameForUser(appPackageName, userId); diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index 7de10452191e..3f4a9bb4d864 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -297,7 +297,10 @@ public class ContextHubService extends IContextHubService.Stub { } public boolean isExpired() { - return mTimestamp + ContextHubTransactionManager.RELIABLE_MESSAGE_TIMEOUT.toNanos() + return mTimestamp + + ContextHubTransactionManager + .RELIABLE_MESSAGE_DUPLICATE_DETECTION_TIMEOUT + .toNanos() < SystemClock.elapsedRealtimeNanos(); } } diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java index e6d330f85dfc..cd69ebaba766 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java @@ -56,6 +56,9 @@ import java.util.concurrent.atomic.AtomicInteger; public static final Duration RELIABLE_MESSAGE_TIMEOUT = Duration.ofSeconds(1); + public static final Duration RELIABLE_MESSAGE_DUPLICATE_DETECTION_TIMEOUT = + RELIABLE_MESSAGE_TIMEOUT.multipliedBy(3); + private static final int MAX_PENDING_REQUESTS = 10000; private static final int RELIABLE_MESSAGE_MAX_NUM_RETRY = 3; diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index 803b125cbabc..621c090d37b8 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -702,7 +702,7 @@ public final class MediaProjectionManagerService extends SystemService } } - private final class BinderService extends IMediaProjectionManager.Stub { + final class BinderService extends IMediaProjectionManager.Stub { BinderService(Context context) { super(PermissionEnforcer.fromContext(context)); @@ -891,6 +891,13 @@ public final class MediaProjectionManagerService extends SystemService @Override public void requestConsentForInvalidProjection(@NonNull IMediaProjection projection) { requestConsentForInvalidProjection_enforcePermission(); + + if (android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions() + && mKeyguardManager.isKeyguardLocked()) { + Slog.v(TAG, "Reusing token: Won't request consent while the keyguard is locked"); + return; + } + synchronized (mLock) { if (!isCurrentProjection(projection)) { Slog.v(TAG, "Reusing token: Won't request consent again for a token that " diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java index fde23b726572..9b644880d737 100644 --- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java +++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java @@ -16,6 +16,7 @@ package com.android.server.policy; +import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.role.RoleManager; import android.content.ActivityNotFoundException; @@ -248,31 +249,7 @@ public class ModifierShortcutManager { + " className=" + className + " shortcutChar=" + shortcutChar); continue; } - ComponentName componentName = new ComponentName(packageName, className); - try { - mPackageManager.getActivityInfo(componentName, - PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE - | PackageManager.MATCH_UNINSTALLED_PACKAGES); - } catch (PackageManager.NameNotFoundException e) { - String[] packages = mPackageManager.canonicalToCurrentPackageNames( - new String[] { packageName }); - componentName = new ComponentName(packages[0], className); - try { - mPackageManager.getActivityInfo(componentName, - PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE - | PackageManager.MATCH_UNINSTALLED_PACKAGES); - } catch (PackageManager.NameNotFoundException e1) { - Log.w(TAG, "Unable to add bookmark: " + packageName - + "/" + className + " not found."); - continue; - } - } - - intent = new Intent(Intent.ACTION_MAIN); - intent.addCategory(Intent.CATEGORY_LAUNCHER); - intent.setComponent(componentName); + intent = resolveComponentNameIntent(packageName, className); } else if (categoryName != null) { if (roleName != null) { Log.w(TAG, "Cannot specify role bookmark when category is present for" @@ -310,6 +287,32 @@ public class ModifierShortcutManager { } } + @Nullable + private Intent resolveComponentNameIntent(String packageName, String className) { + int flags = PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_UNINSTALLED_PACKAGES; + ComponentName componentName = new ComponentName(packageName, className); + try { + mPackageManager.getActivityInfo(componentName, flags); + } catch (PackageManager.NameNotFoundException e) { + String[] packages = mPackageManager.canonicalToCurrentPackageNames( + new String[] { packageName }); + componentName = new ComponentName(packages[0], className); + try { + mPackageManager.getActivityInfo(componentName, flags); + } catch (PackageManager.NameNotFoundException e1) { + Log.w(TAG, "Unable to add bookmark: " + packageName + + "/" + className + " not found."); + return null; + } + } + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + intent.setComponent(componentName); + return intent; + } + void registerShortcutKey(long shortcutCode, IShortcutService shortcutService) throws RemoteException { IShortcutService service = mShortcutKeyServices.get(shortcutCode); diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 8419a608dc41..df973135f5f0 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -2402,7 +2402,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { PowerManager.GO_TO_SLEEP_REASON_TIMEOUT); } - mWindowManagerInternal.registerAppTransitionListener(new AppTransitionListener() { + final var transitionListener = new AppTransitionListener(DEFAULT_DISPLAY) { @Override public int onAppTransitionStartingLocked(long statusBarAnimationStartTime, long statusBarAnimationDuration) { @@ -2436,7 +2436,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { mLockAfterDreamingTransitionFinished = false; } } - }); + }; + mWindowManagerInternal.registerAppTransitionListener(transitionListener); mKeyguardDrawnTimeout = mContext.getResources().getInteger( com.android.internal.R.integer.config_keyguardDrawnTimeout); diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index 1b7bf89d7b44..4052a64aabba 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -16356,6 +16356,7 @@ public class BatteryStatsImpl extends BatteryStats { mBluetoothPowerStatsCollector.collectAndDump(pw); mCameraPowerStatsCollector.collectAndDump(pw); mGnssPowerStatsCollector.collectAndDump(pw); + mCustomEnergyConsumerPowerStatsCollector.collectAndDump(pw); } private final Runnable mWriteAsyncRunnable = () -> { diff --git a/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java index 11919019be4a..0273ba6a6d18 100644 --- a/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java @@ -19,6 +19,7 @@ package com.android.server.power.stats; import android.hardware.power.stats.EnergyConsumerType; import android.os.BatteryConsumer; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -65,4 +66,12 @@ public class CustomEnergyConsumerPowerStatsCollector extends PowerStatsCollector } return success; } + + @Override + public void collectAndDump(PrintWriter pw) { + ensureInitialized(); + for (int i = 0; i < mCollectors.size(); i++) { + mCollectors.get(i).collectAndDump(pw); + } + } } diff --git a/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java index cace94114aa0..ce11fa0edaf7 100644 --- a/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java @@ -176,9 +176,12 @@ public class EnergyConsumerPowerStatsCollector extends PowerStatsCollector { for (EnergyConsumerAttribution attribution : perUid) { int uid = mUidResolver.mapUid(attribution.uid); - long lastEnergy = mLastConsumerEnergyPerUid.get(uid); - long deltaEnergy = attribution.energyUWs - lastEnergy; + long lastEnergy = mLastConsumerEnergyPerUid.get(uid, ENERGY_UNSPECIFIED); mLastConsumerEnergyPerUid.put(uid, attribution.energyUWs); + if (lastEnergy == ENERGY_UNSPECIFIED) { + continue; + } + long deltaEnergy = attribution.energyUWs - lastEnergy; if (deltaEnergy <= 0) { continue; } @@ -189,7 +192,8 @@ public class EnergyConsumerPowerStatsCollector extends PowerStatsCollector { } mLayout.setUidConsumedEnergy(uidStats, 0, - mLayout.getUidConsumedEnergy(uidStats, 0) + deltaEnergy); + mLayout.getUidConsumedEnergy(uidStats, 0) + + uJtoUc(deltaEnergy, averageVoltage)); } } } diff --git a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java index d9f6c1ff1444..f5b00054bea4 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java @@ -196,12 +196,11 @@ public abstract class PowerStatsCollector { } IndentingPrintWriter out = new IndentingPrintWriter(pw); - out.print(getClass().getSimpleName()); if (!isEnabled()) { + out.print(getClass().getSimpleName()); out.println(": disabled"); return; } - out.println(); ArrayList<PowerStats> collected = new ArrayList<>(); Consumer<PowerStats> consumer = collected::add; @@ -215,11 +214,9 @@ public abstract class PowerStatsCollector { removeConsumer(consumer); } - out.increaseIndent(); for (PowerStats stats : collected) { stats.dump(out); } - out.decreaseIndent(); } private void awaitCompletion() { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 400919a88b1f..516fc656ccb4 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -53,7 +53,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.WindowConfiguration.activityTypeToString; -import static android.app.WindowConfiguration.isFloating; import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION; import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE; import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON; @@ -336,7 +335,6 @@ import android.service.contentcapture.ActivityEvent; import android.service.dreams.DreamActivity; import android.service.voice.IVoiceInteractionSession; import android.util.ArraySet; -import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; import android.util.MergedConfiguration; @@ -8648,7 +8646,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds); } - applySizeOverrideIfNeeded(newParentConfiguration, parentWindowingMode, resolvedConfig); + applySizeOverrideIfNeeded( + mDisplayContent, + info.applicationInfo, + newParentConfiguration, + resolvedConfig, + mOptOutEdgeToEdge, + hasFixedRotationTransform(), + getCompatDisplayInsets() != null); mResolveConfigHint.resetTmpOverrides(); logAppCompatState(); @@ -8658,100 +8663,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return Rect.copyOrNull(mResolveConfigHint.mParentAppBoundsOverride); } - /** - * If necessary, override configuration fields related to app bounds. - * This will happen when the app is targeting SDK earlier than 35. - * The insets and configuration has decoupled since SDK level 35, to make the system - * compatible to existing apps, override the configuration with legacy metrics. In legacy - * metrics, fields such as appBounds will exclude some of the system bar areas. - * The override contains all potentially affected fields in Configuration, including - * screenWidthDp, screenHeightDp, smallestScreenWidthDp, and orientation. - * All overrides to those fields should be in this method. - * - * TODO: Consider integrate this with computeConfigByResolveHint() - */ - private void applySizeOverrideIfNeeded(Configuration newParentConfiguration, - int parentWindowingMode, Configuration inOutConfig) { - if (mDisplayContent == null) { - return; - } - final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds(); - int rotation = newParentConfiguration.windowConfiguration.getRotation(); - if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming()) { - rotation = mDisplayContent.getRotation(); - } - if (!mOptOutEdgeToEdge && (!mResolveConfigHint.mUseOverrideInsetsForConfig - || getCompatDisplayInsets() != null - || (isFloating(parentWindowingMode) - // Check the requested windowing mode of activity as well in case it is - // switching between PiP and fullscreen. - && (inOutConfig.windowConfiguration.getWindowingMode() - == WINDOWING_MODE_UNDEFINED - || isFloating(inOutConfig.windowConfiguration.getWindowingMode()))) - || rotation == ROTATION_UNDEFINED)) { - // If the insets configuration decoupled logic is not enabled for the app, or the app - // already has a compat override, or the context doesn't contain enough info to - // calculate the override, skip the override. - return; - } - // Make sure the orientation related fields will be updated by the override insets, because - // fixed rotation has assigned the fields from display's configuration. - if (hasFixedRotationTransform()) { - inOutConfig.windowConfiguration.setAppBounds(null); - inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED; - inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED; - inOutConfig.smallestScreenWidthDp = Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; - inOutConfig.orientation = ORIENTATION_UNDEFINED; - } - - // Override starts here. - final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); - final int dw = rotated ? mDisplayContent.mBaseDisplayHeight - : mDisplayContent.mBaseDisplayWidth; - final int dh = rotated ? mDisplayContent.mBaseDisplayWidth - : mDisplayContent.mBaseDisplayHeight; - final Rect nonDecorInsets = mDisplayContent.getDisplayPolicy() - .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets; - // This should be the only place override the configuration for ActivityRecord. Override - // the value if not calculated yet. - Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); - if (outAppBounds == null || outAppBounds.isEmpty()) { - inOutConfig.windowConfiguration.setAppBounds(parentBounds); - outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); - outAppBounds.inset(nonDecorInsets); - } - float density = inOutConfig.densityDpi; - if (density == Configuration.DENSITY_DPI_UNDEFINED) { - density = newParentConfiguration.densityDpi; - } - density *= DisplayMetrics.DENSITY_DEFAULT_SCALE; - if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) { - inOutConfig.screenWidthDp = (int) (outAppBounds.width() / density + 0.5f); - } - if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { - inOutConfig.screenHeightDp = (int) (outAppBounds.height() / density + 0.5f); - } - if (inOutConfig.smallestScreenWidthDp - == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED - && parentWindowingMode == WINDOWING_MODE_FULLSCREEN) { - // For the case of PIP transition and multi-window environment, the - // smallestScreenWidthDp is handled already. Override only if the app is in - // fullscreen. - final DisplayInfo info = new DisplayInfo(mDisplayContent.getDisplayInfo()); - mDisplayContent.computeSizeRanges(info, rotated, dw, dh, - mDisplayContent.getDisplayMetrics().density, - inOutConfig, true /* overrideConfig */); - } - - // It's possible that screen size will be considered in different orientation with or - // without considering the system bar insets. Override orientation as well. - if (inOutConfig.orientation == ORIENTATION_UNDEFINED) { - inOutConfig.orientation = - (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp) - ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; - } - } - private void computeConfigByResolveHint(@NonNull Configuration resolvedConfig, @NonNull Configuration parentConfig) { task.computeConfigResourceOverrides(resolvedConfig, parentConfig, mResolveConfigHint); diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index 8421765060ce..0f8d68b713a7 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -166,15 +166,14 @@ class BackNavigationController { return null; } - // Move focus to the top embedded window if possible - if (mWindowManagerService.moveFocusToAdjacentEmbeddedWindow(window)) { - window = wmService.getFocusedWindowLocked(); - if (window == null) { - Slog.e(TAG, "New focused window is null, returning null."); - return null; - } + // Updating the window to the most recently used one among the embedded windows + // that are displayed adjacently, unless the IME is visible. + // When the IME is visible, the IME is displayed on top of embedded activities. + // In that case, the back event should still be delivered to focused activity in + // order to dismiss the IME. + if (!window.getDisplayContent().getImeContainer().isVisible()) { + window = mWindowManagerService.getMostRecentUsedEmbeddedWindowForBack(window); } - if (!window.isDrawn()) { ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Focused window didn't have a valid surface drawn."); diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index efd52026cfec..3ebaf03c4a31 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -22,14 +22,23 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.WindowConfiguration.activityTypeToString; +import static android.app.WindowConfiguration.isFloating; import static android.app.WindowConfiguration.windowingModeToString; import static android.app.WindowConfigurationProto.WINDOWING_MODE; import static android.content.ConfigurationProto.WINDOW_CONFIGURATION; +import static android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED; +import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION; +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.content.res.Configuration.ORIENTATION_UNDEFINED; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; import static com.android.server.wm.ConfigurationContainerProto.FULL_CONFIGURATION; import static com.android.server.wm.ConfigurationContainerProto.MERGED_OVERRIDE_CONFIGURATION; @@ -38,11 +47,14 @@ import static com.android.server.wm.ConfigurationContainerProto.OVERRIDE_CONFIGU import android.annotation.CallSuper; import android.annotation.NonNull; import android.app.WindowConfiguration; +import android.content.pm.ApplicationInfo; import android.content.res.Configuration; import android.graphics.Point; import android.graphics.Rect; import android.os.LocaleList; +import android.util.DisplayMetrics; import android.util.proto.ProtoOutputStream; +import android.view.DisplayInfo; import com.android.internal.annotations.VisibleForTesting; @@ -173,6 +185,110 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { mResolvedOverrideConfiguration.setTo(mRequestedOverrideConfiguration); } + /** + * If necessary, override configuration fields related to app bounds. + * This will happen when the app is targeting SDK earlier than 35. + * The insets and configuration has decoupled since SDK level 35, to make the system + * compatible to existing apps, override the configuration with legacy metrics. In legacy + * metrics, fields such as appBounds will exclude some of the system bar areas. + * The override contains all potentially affected fields in Configuration, including + * screenWidthDp, screenHeightDp, smallestScreenWidthDp, and orientation. + * All overrides to those fields should be in this method. + * + * TODO: Consider integrate this with computeConfigByResolveHint() + */ + static void applySizeOverrideIfNeeded(DisplayContent displayContent, ApplicationInfo appInfo, + Configuration newParentConfiguration, Configuration inOutConfig, + boolean optsOutEdgeToEdge, boolean hasFixedRotationTransform, + boolean hasCompatDisplayInsets) { + if (displayContent == null) { + return; + } + final boolean useOverrideInsetsForConfig = + displayContent.mWmService.mFlags.mInsetsDecoupledConfiguration + ? !appInfo.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED) + && !appInfo.isChangeEnabled( + OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION) + : appInfo.isChangeEnabled(OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION); + final int parentWindowingMode = + newParentConfiguration.windowConfiguration.getWindowingMode(); + final boolean isFloating = isFloating(parentWindowingMode) + // Check the requested windowing mode of activity as well in case it is + // switching between PiP and fullscreen. + && (inOutConfig.windowConfiguration.getWindowingMode() == WINDOWING_MODE_UNDEFINED + || isFloating(inOutConfig.windowConfiguration.getWindowingMode())); + int rotation = newParentConfiguration.windowConfiguration.getRotation(); + if (rotation == ROTATION_UNDEFINED && !hasFixedRotationTransform) { + rotation = displayContent.getRotation(); + } + if (!optsOutEdgeToEdge && (!useOverrideInsetsForConfig + || hasCompatDisplayInsets + || isFloating + || rotation == ROTATION_UNDEFINED)) { + // If the insets configuration decoupled logic is not enabled for the app, or the app + // already has a compat override, or the context doesn't contain enough info to + // calculate the override, skip the override. + return; + } + // Make sure the orientation related fields will be updated by the override insets, because + // fixed rotation has assigned the fields from display's configuration. + if (hasFixedRotationTransform) { + inOutConfig.windowConfiguration.setAppBounds(null); + inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED; + inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED; + inOutConfig.smallestScreenWidthDp = Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; + inOutConfig.orientation = ORIENTATION_UNDEFINED; + } + + // Override starts here. + final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); + final int dw = rotated + ? displayContent.mBaseDisplayHeight + : displayContent.mBaseDisplayWidth; + final int dh = rotated + ? displayContent.mBaseDisplayWidth + : displayContent.mBaseDisplayHeight; + // This should be the only place override the configuration for ActivityRecord. Override + // the value if not calculated yet. + Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); + if (outAppBounds == null || outAppBounds.isEmpty()) { + inOutConfig.windowConfiguration.setAppBounds( + newParentConfiguration.windowConfiguration.getBounds()); + outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); + outAppBounds.inset(displayContent.getDisplayPolicy() + .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets); + } + float density = inOutConfig.densityDpi; + if (density == Configuration.DENSITY_DPI_UNDEFINED) { + density = newParentConfiguration.densityDpi; + } + density *= DisplayMetrics.DENSITY_DEFAULT_SCALE; + if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) { + inOutConfig.screenWidthDp = (int) (outAppBounds.width() / density + 0.5f); + } + if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { + inOutConfig.screenHeightDp = (int) (outAppBounds.height() / density + 0.5f); + } + if (inOutConfig.smallestScreenWidthDp == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED + && parentWindowingMode == WINDOWING_MODE_FULLSCREEN) { + // For the case of PIP transition and multi-window environment, the + // smallestScreenWidthDp is handled already. Override only if the app is in + // fullscreen. + final DisplayInfo info = new DisplayInfo(displayContent.getDisplayInfo()); + displayContent.computeSizeRanges(info, rotated, dw, dh, + displayContent.getDisplayMetrics().density, + inOutConfig, true /* overrideConfig */); + } + + // It's possible that screen size will be considered in different orientation with or + // without considering the system bar insets. Override orientation as well. + if (inOutConfig.orientation == ORIENTATION_UNDEFINED) { + inOutConfig.orientation = (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp) + ? ORIENTATION_PORTRAIT + : ORIENTATION_LANDSCAPE; + } + } + /** Returns {@code true} if requested override override configuration is not empty. */ boolean hasRequestedOverrideConfiguration() { return mHasOverrideConfiguration; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 93711497f590..fa603682bc40 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -3895,6 +3895,22 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } /** + * Returns the focused window of the given Activity if the Activity is focused. + */ + WindowState findFocusedWindow(ActivityRecord activityRecord) { + final ActivityRecord tmpApp = mFocusedApp; + mTmpWindow = null; + try { + mFocusedApp = activityRecord; + // mFindFocusedWindow will populate mTmpWindow with the new focused window when found. + activityRecord.forAllWindows(mFindFocusedWindow, true /* traverseTopToBottom */); + } finally { + mFocusedApp = tmpApp; + } + return mTmpWindow; + } + + /** * Update the focused window and make some adjustments if the focus has changed. * * @param mode Indicates the situation we are in. Possible modes are: @@ -6911,6 +6927,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** Whether {@link #mAnimatingRecents} is going to be the top activity. */ private boolean mRecentsWillBeTop; + FixedRotationTransitionListener() { + super(DisplayContent.this.mDisplayId); + } + /** * If the recents activity has a fixed orientation which is different from the current top * activity, it will be rotated before being shown so we avoid a screen rotation animation diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 80362a44a33f..c3339cde6828 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -588,7 +588,7 @@ public class DisplayPolicy { gesturesPointerEventCallbacks); displayContent.registerPointerEventListener(mSystemGestures); } - mAppTransitionListener = new WindowManagerInternal.AppTransitionListener() { + mAppTransitionListener = new WindowManagerInternal.AppTransitionListener(displayId) { private Runnable mAppTransitionPending = () -> { StatusBarManagerInternal statusBar = getStatusBarManagerInternal(); diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java index 63fe94c33061..e50a089a4d5e 100644 --- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java @@ -44,6 +44,7 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLog; import com.android.server.UiThread; +import com.android.window.flags.Flags; /** * Controls camera compatibility treatment that handles orientation mismatch between camera @@ -69,6 +70,9 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp @NonNull private final ActivityRefresher mActivityRefresher; + @Nullable + private Task mCameraTask; + @ScreenOrientation private int mLastReportedOrientation = SCREEN_ORIENTATION_UNSET; @@ -104,7 +108,7 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp * guaranteed to match, the rotation can cause letterboxing. * * <p>If treatment isn't applicable returns {@link SCREEN_ORIENTATION_UNSPECIFIED}. See {@link - * #shouldComputeCameraCompatOrientation} for conditions enabling the treatment. + * #isTreatmentEnabledForDisplay} for conditions enabling the treatment. */ @ScreenOrientation int getOrientation() { @@ -136,9 +140,9 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp // are aligned when they compute orientation of the preview. // This means that even for a landscape-only activity and a device with landscape natural // orientation this would return SCREEN_ORIENTATION_PORTRAIT because an assumption that - // natural orientation = portrait window = portait camera is the main wrong assumption + // natural orientation = portrait window = portrait camera is the main wrong assumption // that apps make when they implement camera previews so landscape windows need be - // rotated in the orientation oposite to the natural one even if it's portrait. + // rotated in the orientation opposite to the natural one even if it's portrait. // TODO(b/261475895): Consider allowing more rotations for "sensor" and "user" versions // of the portrait and landscape orientation requests. final int orientation = (isPortraitActivity && isNaturalDisplayOrientationPortrait) @@ -296,6 +300,7 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp @Override public boolean onCameraOpened(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId) { + mCameraTask = cameraActivity.getTask(); // Checking whether an activity in fullscreen rather than the task as this camera // compat treatment doesn't cover activity embedding. if (cameraActivity.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { @@ -305,7 +310,7 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp } // Checking that the whole app is in multi-window mode as we shouldn't show toast // for the activity embedding case. - if (cameraActivity.getTask().getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW + if (mCameraTask != null && mCameraTask.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW && isTreatmentEnabledForActivity( cameraActivity, /* mustBeFullscreen */ false)) { final PackageManager packageManager = mWmService.mContext.getPackageManager(); @@ -343,10 +348,15 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp @Override public boolean onCameraClosed(@NonNull String cameraId) { - // Top activity in the same task as the camera activity, or `null` if the task is - // closed. - final ActivityRecord topActivity = mDisplayContent.topRunningActivity( - /* considerKeyguardState= */ true); + final ActivityRecord topActivity; + if (Flags.cameraCompatFullscreenPickSameTaskActivity()) { + topActivity = mCameraTask != null ? mCameraTask.getTopActivity( + /* includeFinishing= */ true, /* includeOverlays= */ false) : null; + } else { + topActivity = mDisplayContent.topRunningActivity(/* considerKeyguardState= */ true); + } + + mCameraTask = null; if (topActivity == null) { return true; } @@ -368,8 +378,6 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp mDisplayContent.mDisplayId); // Checking whether an activity in fullscreen rather than the task as this camera compat // treatment doesn't cover activity embedding. - // TODO(b/350495350): Consider checking whether this activity is the camera activity, or - // whether the top activity has the same task as the one which opened camera. if (topActivity.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) { return true; } diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index c26684f60731..cc95518cce39 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -39,7 +39,6 @@ import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType; import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.NonNull; @@ -537,27 +536,11 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { @Override public boolean startMovingTask(IWindow window, float startX, float startY) { - if (DEBUG_TASK_POSITIONING) Slog.d( - TAG_WM, "startMovingTask: {" + startX + "," + startY + "}"); - - final long ident = Binder.clearCallingIdentity(); - try { - return mService.mTaskPositioningController.startMovingTask(window, startX, startY); - } finally { - Binder.restoreCallingIdentity(ident); - } + return false; } @Override public void finishMovingTask(IWindow window) { - if (DEBUG_TASK_POSITIONING) Slog.d(TAG_WM, "finishMovingTask"); - - final long ident = Binder.clearCallingIdentity(); - try { - mService.mTaskPositioningController.finishTaskPositioning(window); - } finally { - Binder.restoreCallingIdentity(ident); - } } @Override diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java deleted file mode 100644 index 972dd2e382cc..000000000000 --- a/services/core/java/com/android/server/wm/TaskPositioner.java +++ /dev/null @@ -1,500 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm; - -import static android.app.ActivityTaskManager.RESIZE_MODE_USER; -import static android.app.ActivityTaskManager.RESIZE_MODE_USER_FORCED; -import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS; -import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; - -import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM; -import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT; -import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE; -import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT; -import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP; -import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING; -import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; -import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; -import static com.android.server.wm.WindowManagerService.dipToPixel; -import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP; -import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP; - -import static java.util.concurrent.CompletableFuture.completedFuture; - -import android.annotation.NonNull; -import android.graphics.Point; -import android.graphics.Rect; -import android.os.Binder; -import android.os.IBinder; -import android.os.InputConfig; -import android.os.RemoteException; -import android.os.Trace; -import android.util.DisplayMetrics; -import android.util.Slog; -import android.view.BatchedInputEventReceiver; -import android.view.InputApplicationHandle; -import android.view.InputChannel; -import android.view.InputDevice; -import android.view.InputEvent; -import android.view.InputEventReceiver; -import android.view.InputWindowHandle; -import android.view.MotionEvent; -import android.view.WindowManager; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.policy.TaskResizingAlgorithm; -import com.android.internal.policy.TaskResizingAlgorithm.CtrlType; -import com.android.internal.protolog.ProtoLog; - -import java.util.concurrent.CompletableFuture; - -class TaskPositioner implements IBinder.DeathRecipient { - private static final boolean DEBUG_ORIENTATION_VIOLATIONS = false; - private static final String TAG_LOCAL = "TaskPositioner"; - private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM; - - private static Factory sFactory; - - public static final float RESIZING_HINT_ALPHA = 0.5f; - - public static final int RESIZING_HINT_DURATION_MS = 0; - - private final WindowManagerService mService; - private InputEventReceiver mInputEventReceiver; - private DisplayContent mDisplayContent; - private Rect mTmpRect = new Rect(); - private int mMinVisibleWidth; - private int mMinVisibleHeight; - - @VisibleForTesting - Task mTask; - WindowState mWindow; - private boolean mResizing; - private boolean mPreserveOrientation; - private boolean mStartOrientationWasLandscape; - private final Rect mWindowOriginalBounds = new Rect(); - private final Rect mWindowDragBounds = new Rect(); - private final Point mMaxVisibleSize = new Point(); - private float mStartDragX; - private float mStartDragY; - @CtrlType - private int mCtrlType = CTRL_NONE; - @VisibleForTesting - boolean mDragEnded; - IBinder mClientCallback; - - InputChannel mClientChannel; - InputApplicationHandle mDragApplicationHandle; - InputWindowHandle mDragWindowHandle; - - /** Use {@link #create(WindowManagerService)} instead. */ - @VisibleForTesting - TaskPositioner(WindowManagerService service) { - mService = service; - } - - private boolean onInputEvent(InputEvent event) { - // All returns need to be in the try block to make sure the finishInputEvent is - // called correctly. - if (!(event instanceof MotionEvent) - || (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) { - return false; - } - final MotionEvent motionEvent = (MotionEvent) event; - if (mDragEnded) { - // The drag has ended but the clean-up message has not been processed by - // window manager. Drop events that occur after this until window manager - // has a chance to clean-up the input handle. - return true; - } - - final float newX = motionEvent.getRawX(); - final float newY = motionEvent.getRawY(); - - switch (motionEvent.getAction()) { - case MotionEvent.ACTION_DOWN: { - if (DEBUG_TASK_POSITIONING) { - Slog.w(TAG, "ACTION_DOWN @ {" + newX + ", " + newY + "}"); - } - } - break; - - case MotionEvent.ACTION_MOVE: { - if (DEBUG_TASK_POSITIONING) { - Slog.w(TAG, "ACTION_MOVE @ {" + newX + ", " + newY + "}"); - } - synchronized (mService.mGlobalLock) { - mDragEnded = notifyMoveLocked(newX, newY); - mTask.getDimBounds(mTmpRect); - } - if (!mTmpRect.equals(mWindowDragBounds)) { - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, - "wm.TaskPositioner.resizeTask"); - mService.mAtmService.resizeTask( - mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER); - Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); - } - } - break; - - case MotionEvent.ACTION_UP: { - if (DEBUG_TASK_POSITIONING) { - Slog.w(TAG, "ACTION_UP @ {" + newX + ", " + newY + "}"); - } - mDragEnded = true; - } - break; - - case MotionEvent.ACTION_CANCEL: { - if (DEBUG_TASK_POSITIONING) { - Slog.w(TAG, "ACTION_CANCEL @ {" + newX + ", " + newY + "}"); - } - mDragEnded = true; - } - break; - } - - if (mDragEnded) { - final boolean wasResizing = mResizing; - synchronized (mService.mGlobalLock) { - endDragLocked(); - mTask.getDimBounds(mTmpRect); - } - if (wasResizing && !mTmpRect.equals(mWindowDragBounds)) { - // We were using fullscreen surface during resizing. Request - // resizeTask() one last time to restore surface to window size. - mService.mAtmService.resizeTask( - mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER_FORCED); - } - - // Post back to WM to handle clean-ups. We still need the input - // event handler for the last finishInputEvent()! - mService.mTaskPositioningController.finishTaskPositioning(); - } - return true; - } - - @VisibleForTesting - Rect getWindowDragBounds() { - return mWindowDragBounds; - } - - /** - * @param displayContent The Display that the window being dragged is on. - * @param win The window which will be dragged. - */ - CompletableFuture<Void> register(DisplayContent displayContent, @NonNull WindowState win) { - if (DEBUG_TASK_POSITIONING) { - Slog.d(TAG, "Registering task positioner"); - } - - if (mClientChannel != null) { - Slog.e(TAG, "Task positioner already registered"); - return completedFuture(null); - } - - mDisplayContent = displayContent; - mClientChannel = mService.mInputManager.createInputChannel(TAG); - - mInputEventReceiver = new BatchedInputEventReceiver.SimpleBatchedInputEventReceiver( - mClientChannel, mService.mAnimationHandler.getLooper(), - mService.mAnimator.getChoreographer(), this::onInputEvent); - - mDragApplicationHandle = new InputApplicationHandle(new Binder(), TAG, - DEFAULT_DISPATCHING_TIMEOUT_MILLIS); - - mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, - displayContent.getDisplayId()); - mDragWindowHandle.name = TAG; - mDragWindowHandle.token = mClientChannel.getToken(); - mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG; - mDragWindowHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS; - mDragWindowHandle.ownerPid = WindowManagerService.MY_PID; - mDragWindowHandle.ownerUid = WindowManagerService.MY_UID; - mDragWindowHandle.scaleFactor = 1.0f; - // When dragging the window around, we do not want to steal focus for the window. - mDragWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE; - - // The drag window cannot receive new touches. - mDragWindowHandle.touchableRegion.setEmpty(); - - // Pause rotations before a drag. - ProtoLog.d(WM_DEBUG_ORIENTATION, "Pausing rotation during re-position"); - mDisplayContent.getDisplayRotation().pause(); - - // Notify InputMonitor to take mDragWindowHandle. - return mService.mTaskPositioningController.showInputSurface(win.getDisplayId()) - .thenRun(() -> { - // The global lock is held by the callers of register but released before the async - // results are waited on. We must acquire the lock in this callback to ensure thread - // safety. - synchronized (mService.mGlobalLock) { - final Rect displayBounds = mTmpRect; - displayContent.getBounds(displayBounds); - final DisplayMetrics displayMetrics = displayContent.getDisplayMetrics(); - mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, displayMetrics); - mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, displayMetrics); - mMaxVisibleSize.set(displayBounds.width(), displayBounds.height()); - - mDragEnded = false; - - try { - mClientCallback = win.mClient.asBinder(); - mClientCallback.linkToDeath(this, 0 /* flags */); - } catch (RemoteException e) { - // The caller has died, so clean up TaskPositioningController. - mService.mTaskPositioningController.finishTaskPositioning(); - return; - } - mWindow = win; - mTask = win.getTask(); - } - }); - } - - void unregister() { - if (DEBUG_TASK_POSITIONING) { - Slog.d(TAG, "Unregistering task positioner"); - } - - if (mClientChannel == null) { - Slog.e(TAG, "Task positioner not registered"); - return; - } - - mService.mTaskPositioningController.hideInputSurface(mDisplayContent.getDisplayId()); - mService.mInputManager.removeInputChannel(mClientChannel.getToken()); - - mInputEventReceiver.dispose(); - mInputEventReceiver = null; - mClientChannel.dispose(); - mClientChannel = null; - - mDragWindowHandle = null; - mDragApplicationHandle = null; - mDragEnded = true; - - // Notify InputMonitor to remove mDragWindowHandle. - mDisplayContent.getInputMonitor().updateInputWindowsLw(true /*force*/); - - // Resume rotations after a drag. - ProtoLog.d(WM_DEBUG_ORIENTATION, "Resuming rotation after re-position"); - mDisplayContent.getDisplayRotation().resume(); - mDisplayContent = null; - if (mClientCallback != null) { - mClientCallback.unlinkToDeath(this, 0 /* flags */); - } - mWindow = null; - } - - /** - * Starts moving or resizing the task. This method should be only called from - * {@link TaskPositioningController#startPositioningLocked} or unit tests. - */ - void startDrag(boolean resize, boolean preserveOrientation, float startX, float startY) { - if (DEBUG_TASK_POSITIONING) { - Slog.d(TAG, "startDrag: win=" + mWindow + ", resize=" + resize - + ", preserveOrientation=" + preserveOrientation + ", {" + startX + ", " - + startY + "}"); - } - // Use the bounds of the task which accounts for - // multiple app windows. Don't use any bounds from win itself as it - // may not be the same size as the task. - final Rect startBounds = mTmpRect; - mTask.getBounds(startBounds); - - mCtrlType = CTRL_NONE; - mStartDragX = startX; - mStartDragY = startY; - mPreserveOrientation = preserveOrientation; - - if (resize) { - if (startX < startBounds.left) { - mCtrlType |= CTRL_LEFT; - } - if (startX > startBounds.right) { - mCtrlType |= CTRL_RIGHT; - } - if (startY < startBounds.top) { - mCtrlType |= CTRL_TOP; - } - if (startY > startBounds.bottom) { - mCtrlType |= CTRL_BOTTOM; - } - mResizing = mCtrlType != CTRL_NONE; - } - - // In case of !isDockedInEffect we are using the union of all task bounds. These might be - // made up out of multiple windows which are only partially overlapping. When that happens, - // the orientation from the window of interest to the entire stack might diverge. However - // for now we treat them as the same. - mStartOrientationWasLandscape = startBounds.width() >= startBounds.height(); - mWindowOriginalBounds.set(startBounds); - - // Notify the app that resizing has started, even though we haven't received any new - // bounds yet. This will guarantee that the app starts the backdrop renderer before - // configuration changes which could cause an activity restart. - if (mResizing) { - notifyMoveLocked(startX, startY); - - // The WindowPositionerEventReceiver callbacks are delivered on the same handler so this - // initial resize is always guaranteed to happen before subsequent drag resizes. - mService.mH.post(() -> { - mService.mAtmService.resizeTask( - mTask.mTaskId, startBounds, RESIZE_MODE_USER_FORCED); - }); - } - - // Make sure we always have valid drag bounds even if the drag ends before any move events - // have been handled. - mWindowDragBounds.set(startBounds); - } - - private void endDragLocked() { - mResizing = false; - mTask.setDragResizing(false); - } - - /** Returns true if the move operation should be ended. */ - @VisibleForTesting - boolean notifyMoveLocked(float x, float y) { - if (DEBUG_TASK_POSITIONING) { - Slog.d(TAG, "notifyMoveLocked: {" + x + "," + y + "}"); - } - - if (mCtrlType != CTRL_NONE) { - resizeDrag(x, y); - mTask.setDragResizing(true); - return false; - } - - // This is a moving or scrolling operation. - // Only allow to move in stable area so the target window won't be covered by system bar. - // Though {@link Task#resolveOverrideConfiguration} should also avoid the case. - mDisplayContent.getStableRect(mTmpRect); - // The task may be put in a limited display area. - mTmpRect.intersect(mTask.getRootTask().getParent().getBounds()); - - int nX = (int) x; - int nY = (int) y; - if (!mTmpRect.contains(nX, nY)) { - // For a moving operation we allow the pointer to go out of the stack bounds, but - // use the clamped pointer position for the drag bounds computation. - nX = Math.min(Math.max(nX, mTmpRect.left), mTmpRect.right); - nY = Math.min(Math.max(nY, mTmpRect.top), mTmpRect.bottom); - } - - updateWindowDragBounds(nX, nY, mTmpRect); - return false; - } - - /** - * The user is drag - resizing the window. - * - * @param x The x coordinate of the current drag coordinate. - * @param y the y coordinate of the current drag coordinate. - */ - @VisibleForTesting - void resizeDrag(float x, float y) { - updateDraggedBounds(TaskResizingAlgorithm.resizeDrag(x, y, mStartDragX, mStartDragY, - mWindowOriginalBounds, mCtrlType, mMinVisibleWidth, mMinVisibleHeight, - mMaxVisibleSize, mPreserveOrientation, mStartOrientationWasLandscape)); - } - - private void updateDraggedBounds(Rect newBounds) { - mWindowDragBounds.set(newBounds); - - checkBoundsForOrientationViolations(mWindowDragBounds); - } - - /** - * Validate bounds against orientation violations (if DEBUG_ORIENTATION_VIOLATIONS is set). - * - * @param bounds The bounds to be checked. - */ - private void checkBoundsForOrientationViolations(Rect bounds) { - // When using debug check that we are not violating the given constraints. - if (DEBUG_ORIENTATION_VIOLATIONS) { - if (mStartOrientationWasLandscape != (bounds.width() >= bounds.height())) { - Slog.e(TAG, "Orientation violation detected! should be " - + (mStartOrientationWasLandscape ? "landscape" : "portrait") - + " but is the other"); - } else { - Slog.v(TAG, "new bounds size: " + bounds.width() + " x " + bounds.height()); - } - if (mMinVisibleWidth > bounds.width() || mMinVisibleHeight > bounds.height()) { - Slog.v(TAG, "Minimum requirement violated: Width(min, is)=(" + mMinVisibleWidth - + ", " + bounds.width() + ") Height(min,is)=(" - + mMinVisibleHeight + ", " + bounds.height() + ")"); - } - if (mMaxVisibleSize.x < bounds.width() || mMaxVisibleSize.y < bounds.height()) { - Slog.v(TAG, "Maximum requirement violated: Width(min, is)=(" + mMaxVisibleSize.x - + ", " + bounds.width() + ") Height(min,is)=(" - + mMaxVisibleSize.y + ", " + bounds.height() + ")"); - } - } - } - - private void updateWindowDragBounds(int x, int y, Rect rootTaskBounds) { - final int offsetX = Math.round(x - mStartDragX); - final int offsetY = Math.round(y - mStartDragY); - mWindowDragBounds.set(mWindowOriginalBounds); - // Horizontally, at least mMinVisibleWidth pixels of the window should remain visible. - final int maxLeft = rootTaskBounds.right - mMinVisibleWidth; - final int minLeft = rootTaskBounds.left + mMinVisibleWidth - mWindowOriginalBounds.width(); - - // Vertically, the top mMinVisibleHeight of the window should remain visible. - // (This assumes that the window caption bar is at the top of the window). - final int minTop = rootTaskBounds.top; - final int maxTop = rootTaskBounds.bottom - mMinVisibleHeight; - - mWindowDragBounds.offsetTo( - Math.min(Math.max(mWindowOriginalBounds.left + offsetX, minLeft), maxLeft), - Math.min(Math.max(mWindowOriginalBounds.top + offsetY, minTop), maxTop)); - - if (DEBUG_TASK_POSITIONING) Slog.d(TAG, - "updateWindowDragBounds: " + mWindowDragBounds); - } - - public String toShortString() { - return TAG; - } - - static void setFactory(Factory factory) { - sFactory = factory; - } - - static TaskPositioner create(WindowManagerService service) { - if (sFactory == null) { - sFactory = new Factory() {}; - } - - return sFactory.create(service); - } - - @Override - public void binderDied() { - mService.mTaskPositioningController.finishTaskPositioning(); - } - - interface Factory { - default TaskPositioner create(WindowManagerService service) { - return new TaskPositioner(service); - } - } -} diff --git a/services/core/java/com/android/server/wm/TaskPositioningController.java b/services/core/java/com/android/server/wm/TaskPositioningController.java deleted file mode 100644 index 6f548ab01d74..000000000000 --- a/services/core/java/com/android/server/wm/TaskPositioningController.java +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm; - -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING; -import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; - -import static java.util.concurrent.CompletableFuture.completedFuture; - -import android.annotation.Nullable; -import android.graphics.Point; -import android.graphics.Rect; -import android.util.Slog; -import android.view.Display; -import android.view.IWindow; -import android.view.InputWindowHandle; -import android.view.SurfaceControl; - -import java.util.concurrent.CompletableFuture; - -/** - * Controller for task positioning by drag. - */ -class TaskPositioningController { - private final WindowManagerService mService; - private SurfaceControl mInputSurface; - private DisplayContent mPositioningDisplay; - - private @Nullable TaskPositioner mTaskPositioner; - - private final Rect mTmpClipRect = new Rect(); - - boolean isPositioningLocked() { - return mTaskPositioner != null; - } - - final SurfaceControl.Transaction mTransaction; - - InputWindowHandle getDragWindowHandleLocked() { - return mTaskPositioner != null ? mTaskPositioner.mDragWindowHandle : null; - } - - TaskPositioningController(WindowManagerService service) { - mService = service; - mTransaction = service.mTransactionFactory.get(); - } - - void hideInputSurface(int displayId) { - if (mPositioningDisplay != null && mPositioningDisplay.getDisplayId() == displayId - && mInputSurface != null) { - mTransaction.hide(mInputSurface).apply(); - } - } - - /** - * @return a future that completes after window info is sent. - */ - CompletableFuture<Void> showInputSurface(int displayId) { - if (mPositioningDisplay == null || mPositioningDisplay.getDisplayId() != displayId) { - return completedFuture(null); - } - final DisplayContent dc = mService.mRoot.getDisplayContent(displayId); - if (mInputSurface == null) { - mInputSurface = mService.makeSurfaceBuilder(dc.getSession()) - .setContainerLayer() - .setName("Drag and Drop Input Consumer") - .setCallsite("TaskPositioningController.showInputSurface") - .setParent(dc.getOverlayLayer()) - .build(); - } - - final InputWindowHandle h = getDragWindowHandleLocked(); - if (h == null) { - Slog.w(TAG_WM, "Drag is in progress but there is no " - + "drag window handle."); - return completedFuture(null); - } - - final Display display = dc.getDisplay(); - final Point p = new Point(); - display.getRealSize(p); - mTmpClipRect.set(0, 0, p.x, p.y); - - CompletableFuture<Void> result = new CompletableFuture<>(); - mTransaction.show(mInputSurface) - .setInputWindowInfo(mInputSurface, h) - .setLayer(mInputSurface, Integer.MAX_VALUE) - .setPosition(mInputSurface, 0, 0) - .setCrop(mInputSurface, mTmpClipRect) - .addWindowInfosReportedListener(() -> result.complete(null)) - .apply(); - return result; - } - - boolean startMovingTask(IWindow window, float startX, float startY) { - WindowState win = null; - CompletableFuture<Boolean> startPositioningLockedFuture; - synchronized (mService.mGlobalLock) { - win = mService.windowForClientLocked(null, window, false); - startPositioningLockedFuture = - startPositioningLocked( - win, false /*resize*/, false /*preserveOrientation*/, startX, startY); - } - - try { - if (!startPositioningLockedFuture.get()) { - return false; - } - } catch (Exception exception) { - Slog.e(TAG_WM, "Exception thrown while waiting for startPositionLocked future", - exception); - return false; - } - - synchronized (mService.mGlobalLock) { - mService.mAtmService.setFocusedTask(win.getTask().mTaskId); - } - return true; - } - - void handleTapOutsideTask(DisplayContent displayContent, int x, int y) { - mService.mH.post(() -> { - Task task; - CompletableFuture<Boolean> startPositioningLockedFuture; - synchronized (mService.mGlobalLock) { - task = displayContent.findTaskForResizePoint(x, y); - if (task == null || !task.isResizeable()) { - // The task is not resizable, so don't do anything when the user drags the - // the resize handles. - return; - } - startPositioningLockedFuture = - startPositioningLocked(task.getTopVisibleAppMainWindow(), true /*resize*/, - task.preserveOrientationOnResize(), x, y); - } - - try { - if (!startPositioningLockedFuture.get()) { - return; - } - } catch (Exception exception) { - Slog.e(TAG_WM, "Exception thrown while waiting for startPositionLocked future", - exception); - return; - } - - synchronized (mService.mGlobalLock) { - mService.mAtmService.setFocusedTask(task.mTaskId); - } - }); - } - - private CompletableFuture<Boolean> startPositioningLocked(WindowState win, boolean resize, - boolean preserveOrientation, float startX, float startY) { - if (DEBUG_TASK_POSITIONING) - Slog.d(TAG_WM, "startPositioningLocked: " - + "win=" + win + ", resize=" + resize + ", preserveOrientation=" - + preserveOrientation + ", {" + startX + ", " + startY + "}"); - - if (win == null || win.mActivityRecord == null) { - Slog.w(TAG_WM, "startPositioningLocked: Bad window " + win); - return completedFuture(false); - } - if (win.mInputChannel == null) { - Slog.wtf(TAG_WM, "startPositioningLocked: " + win + " has no input channel, " - + " probably being removed"); - return completedFuture(false); - } - - final DisplayContent displayContent = win.getDisplayContent(); - if (displayContent == null) { - Slog.w(TAG_WM, "startPositioningLocked: Invalid display content " + win); - return completedFuture(false); - } - mPositioningDisplay = displayContent; - - mTaskPositioner = TaskPositioner.create(mService); - return mTaskPositioner.register(displayContent, win).thenApply(unused -> { - // The global lock is held by the callers of startPositioningLocked but released before - // the async results are waited on. We must acquire the lock in this callback to ensure - // thread safety. - synchronized (mService.mGlobalLock) { - // We need to grab the touch focus so that the touch events during the - // resizing/scrolling are not sent to the app. 'win' is the main window - // of the app, it may not have focus since there might be other windows - // on top (eg. a dialog window). - WindowState transferTouchFromWin = win; - if (displayContent.mCurrentFocus != null && displayContent.mCurrentFocus != win - && displayContent.mCurrentFocus.mActivityRecord == win.mActivityRecord) { - transferTouchFromWin = displayContent.mCurrentFocus; - } - if (!mService.mInputManager.transferTouchGesture( - transferTouchFromWin.mInputChannel.getToken(), - mTaskPositioner.mClientChannel.getToken())) { - Slog.e(TAG_WM, "startPositioningLocked: Unable to transfer touch focus"); - cleanUpTaskPositioner(); - return false; - } - - mTaskPositioner.startDrag(resize, preserveOrientation, startX, startY); - return true; - } - }); - } - - public void finishTaskPositioning(IWindow window) { - if (mTaskPositioner != null && mTaskPositioner.mClientCallback == window.asBinder()) { - finishTaskPositioning(); - } - } - - void finishTaskPositioning() { - // TaskPositioner attaches the InputEventReceiver to the animation thread. We need to - // dispose the receiver on the same thread to avoid race conditions. - mService.mAnimationHandler.post(() -> { - if (DEBUG_TASK_POSITIONING) Slog.d(TAG_WM, "finishPositioning"); - - synchronized (mService.mGlobalLock) { - cleanUpTaskPositioner(); - mPositioningDisplay = null; - } - }); - } - - private void cleanUpTaskPositioner() { - final TaskPositioner positioner = mTaskPositioner; - if (positioner == null) { - return; - } - - // We need to assign task positioner to null first to indicate that we're finishing task - // positioning. - mTaskPositioner = null; - positioner.unregister(); - } -} diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index f6a68d58ea27..65bc9a226f55 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -745,6 +745,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (mController.isAnimating()) { dc.enableHighPerfTransition(true); } + mController.dispatchLegacyAppTransitionPending(dc.mDisplayId); } /** @@ -1618,7 +1619,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { mController.mTransitionTracer.logAbortedTransition(this); // Syncengine abort will call through to onTransactionReady() mSyncEngine.abort(mSyncId); - mController.dispatchLegacyAppTransitionCancelled(); + mController.dispatchLegacyAppTransitionCancelled(mTargetDisplays); invokeTransitionEndedListeners(); } @@ -1766,7 +1767,19 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } for (int i = 0; i < mTargets.size(); ++i) { - final DisplayArea da = mTargets.get(i).mContainer.asDisplayArea(); + final WindowContainer<?> wc = mTargets.get(i).mContainer; + final WallpaperWindowToken wp = wc.asWallpaperToken(); + if (wp != null) { + // If on a rotation leash, the wallpaper token surface needs to be shown explicitly + // because shell only gets the leash and the wallpaper token surface is not allowed + // to be changed by non-transition logic until the transition is finished. + if (Flags.ensureWallpaperInTransitions() && wp.isVisibleRequested() + && wp.getFixedRotationLeash() != null) { + transaction.show(wp.mSurfaceControl); + } + continue; + } + final DisplayArea<?> da = wc.asDisplayArea(); if (da == null) continue; if (da.isVisibleRequested()) { mController.mValidateDisplayVis.remove(da); @@ -2168,14 +2181,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { && !wallpaperIsOwnTarget(wallpaper)) { wallpaper.setVisibleRequested(false); } - if (showWallpaper && Flags.ensureWallpaperInTransitions() - && wallpaper.isVisibleRequested() - && getLeashSurface(wallpaper, t) != wallpaper.getSurfaceControl()) { - // If on a rotation leash, we need to explicitly show the wallpaper surface - // because shell only gets the leash and we don't allow non-transition logic - // to touch the surfaces until the transition is over. - t.show(wallpaper.getSurfaceControl()); - } } } } diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index f4ff404c2bff..1df251cf3225 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -42,6 +42,7 @@ import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; +import android.view.Display; import android.view.WindowManager; import android.window.ITransitionMetricsReporter; import android.window.ITransitionPlayer; @@ -326,7 +327,6 @@ class TransitionController { mCollectingTransition.startCollecting(timeoutMs); ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Start collecting in Transition: %s", mCollectingTransition); - dispatchLegacyAppTransitionPending(); } void registerTransitionPlayer(@Nullable ITransitionPlayer player, @@ -1347,31 +1347,54 @@ class TransitionController { mLegacyListeners.remove(listener); } - void dispatchLegacyAppTransitionPending() { + private static boolean shouldDispatchLegacyListener( + WindowManagerInternal.AppTransitionListener listener, int displayId) { + // INVALID_DISPLAY means that it is a global listener. + return listener.mDisplayId == Display.INVALID_DISPLAY || listener.mDisplayId == displayId; + } + + void dispatchLegacyAppTransitionPending(int displayId) { for (int i = 0; i < mLegacyListeners.size(); ++i) { - mLegacyListeners.get(i).onAppTransitionPendingLocked(); + final WindowManagerInternal.AppTransitionListener listener = mLegacyListeners.get(i); + if (shouldDispatchLegacyListener(listener, displayId)) { + listener.onAppTransitionPendingLocked(); + } } } void dispatchLegacyAppTransitionStarting(TransitionInfo info, long statusBarTransitionDelay) { + final long now = SystemClock.uptimeMillis(); for (int i = 0; i < mLegacyListeners.size(); ++i) { - // TODO(shell-transitions): handle (un)occlude transition. - mLegacyListeners.get(i).onAppTransitionStartingLocked( - SystemClock.uptimeMillis() + statusBarTransitionDelay, - AnimationAdapter.STATUS_BAR_TRANSITION_DURATION); + final WindowManagerInternal.AppTransitionListener listener = mLegacyListeners.get(i); + for (int j = 0; j < info.getRootCount(); ++j) { + final int displayId = info.getRoot(j).getDisplayId(); + if (shouldDispatchLegacyListener(listener, displayId)) { + listener.onAppTransitionStartingLocked( + now + statusBarTransitionDelay, + AnimationAdapter.STATUS_BAR_TRANSITION_DURATION); + } + } } } void dispatchLegacyAppTransitionFinished(ActivityRecord ar) { for (int i = 0; i < mLegacyListeners.size(); ++i) { - mLegacyListeners.get(i).onAppTransitionFinishedLocked(ar.token); + final WindowManagerInternal.AppTransitionListener listener = mLegacyListeners.get(i); + if (shouldDispatchLegacyListener(listener, ar.getDisplayId())) { + listener.onAppTransitionFinishedLocked(ar.token); + } } } - void dispatchLegacyAppTransitionCancelled() { - for (int i = 0; i < mLegacyListeners.size(); ++i) { - mLegacyListeners.get(i).onAppTransitionCancelledLocked( - false /* keyguardGoingAwayCancelled */); + void dispatchLegacyAppTransitionCancelled(ArrayList<DisplayContent> targetDisplays) { + for (int i = 0; i < targetDisplays.size(); ++i) { + final int displayId = targetDisplays.get(i).mDisplayId; + for (int j = 0; j < mLegacyListeners.size(); ++j) { + final var listener = mLegacyListeners.get(j); + if (shouldDispatchLegacyListener(listener, displayId)) { + listener.onAppTransitionCancelledLocked(false /* keyguardGoingAwayCancelled */); + } + } } } diff --git a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java index 42b556f77ab6..61253602c066 100644 --- a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java +++ b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java @@ -47,7 +47,6 @@ public class WindowManagerDebugConfig { static final boolean DEBUG_LAYOUT_REPEATS = false; static final boolean DEBUG_WINDOW_TRACE = false; static final boolean DEBUG_TASK_MOVEMENT = false; - static final boolean DEBUG_TASK_POSITIONING = false; static final boolean DEBUG_ROOT_TASK = false; static final boolean DEBUG_DISPLAY = false; static final boolean DEBUG_POWER = false; diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 2ea1cf88447a..132e1ee99914 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -244,6 +244,22 @@ public abstract class WindowManagerInternal { public static abstract class AppTransitionListener { /** + * The display this listener is interested in. If it is INVALID_DISPLAY, then which display + * should be notified depends on the dispatcher. + */ + public final int mDisplayId; + + /** Let transition controller decide which display should receive the callbacks. */ + public AppTransitionListener() { + this(Display.INVALID_DISPLAY); + } + + /** It will listen the transition on the given display. */ + public AppTransitionListener(int displayId) { + mDisplayId = displayId; + } + + /** * Called when an app transition is being setup and about to be executed. */ public void onAppTransitionPendingLocked() {} diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index acd8b3f1dbc3..f65eea0797bf 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -54,7 +54,6 @@ import static android.service.dreams.Flags.dreamHandlesConfirmKeys; import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; -import static android.view.flags.Flags.sensitiveContentAppProtection; import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY; import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; @@ -97,6 +96,7 @@ import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED; import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN; +import static android.view.flags.Flags.sensitiveContentAppProtection; import static android.window.WindowProviderService.isWindowProviderService; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; @@ -1070,7 +1070,6 @@ public class WindowManagerService extends IWindowManager.Stub /** Whether or not a layout can cause a wake up when theater mode is enabled. */ boolean mAllowTheaterModeWakeFromLayout; - final TaskPositioningController mTaskPositioningController; final DragDropController mDragDropController; /** For frozen screen animations. */ @@ -1428,7 +1427,6 @@ public class WindowManagerService extends IWindowManager.Stub mAllowTheaterModeWakeFromLayout = context.getResources().getBoolean( com.android.internal.R.bool.config_allowTheaterModeWakeFromWindowLayout); - mTaskPositioningController = new TaskPositioningController(this); mDragDropController = new DragDropController(this, mH.getLooper()); mHighRefreshRateDenylist = HighRefreshRateDenylist.create(context.getResources()); @@ -9379,40 +9377,82 @@ public class WindowManagerService extends IWindowManager.Stub } /** - * Move focus to the adjacent embedded activity if the adjacent activity is more recently - * created or has a window more recently added. + * Returns the Activity that has the most recently created window in the adjacent activities + * if any. */ - boolean moveFocusToAdjacentEmbeddedWindow(@NonNull WindowState focusedWindow) { - final TaskFragment taskFragment = focusedWindow.getTaskFragment(); + @NonNull + ActivityRecord getMostRecentActivityInAdjacent(@NonNull ActivityRecord focusedActivity) { + final TaskFragment taskFragment = focusedActivity.getTaskFragment(); if (taskFragment == null) { - // Skip if not an Activity window. - return false; + // Return if activity no attached. + return focusedActivity; } if (!Flags.embeddedActivityBackNavFlag()) { - // Skip if flag is not enabled. - return false; + // Return if flag is not enabled. + return focusedActivity; } - if (!focusedWindow.mActivityRecord.isEmbedded()) { - // Skip if the focused activity is not embedded - return false; + if (!focusedActivity.isEmbedded()) { + // Return if the focused activity is not embedded. + return focusedActivity; } final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment(); final ActivityRecord adjacentTopActivity = adjacentTaskFragment != null ? adjacentTaskFragment.topRunningActivity() : null; if (adjacentTopActivity == null) { - return false; + // Return if no adjacent activity. + return focusedActivity; } if (adjacentTopActivity.getLastWindowCreateTime() - < focusedWindow.mActivityRecord.getLastWindowCreateTime()) { - // Skip if the current focus activity has more recently active window. + < focusedActivity.getLastWindowCreateTime()) { + // Return if the current focus activity has more recently active window. + return focusedActivity; + } + + return adjacentTopActivity; + } + + @NonNull + WindowState getMostRecentUsedEmbeddedWindowForBack(@NonNull WindowState focusedWindow) { + final ActivityRecord focusedActivity = focusedWindow.getActivityRecord(); + if (focusedActivity == null) { + // Not an Activity. + return focusedWindow; + } + + final ActivityRecord mostRecentActivityInAdjacent = getMostRecentActivityInAdjacent( + focusedActivity); + if (mostRecentActivityInAdjacent == focusedActivity) { + // Already be the most recent window. + return focusedWindow; + } + + // Looks for a candidate focused window on the adjacent Activity for the back event. + final WindowState candidate = + mostRecentActivityInAdjacent.getDisplayContent().findFocusedWindow( + mostRecentActivityInAdjacent); + return candidate != null ? candidate : focusedWindow; + } + + /** + * Move focus to the adjacent embedded activity if the adjacent activity is more recently + * created or has a window more recently added. + * <p> + * Returns {@code true} if the focused window is changed. Otherwise, returns {@code false}. + */ + boolean moveFocusToAdjacentEmbeddedWindow(@NonNull WindowState focusedWindow) { + final ActivityRecord activity = focusedWindow.getActivityRecord(); + if (activity == null) { return false; } - moveFocusToActivity(adjacentTopActivity); + final ActivityRecord mostRecentActivityInAdjacent = getMostRecentActivityInAdjacent( + activity); + + moveFocusToActivity(mostRecentActivityInAdjacent); return !focusedWindow.isFocused(); } diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 12c50739f66b..984caf1c692b 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -1674,6 +1674,22 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio // Otherwise if other places send wpc.getConfiguration() to client, the configuration may // be ignored due to the seq is older. resolvedConfig.seq = newParentConfig.seq; + + if (mConfigActivityRecord != null) { + // Let the activity decide whether to apply the size override. + return; + } + final DisplayContent displayContent = mAtm.mWindowManager != null + ? mAtm.mWindowManager.getDefaultDisplayContentLocked() + : null; + applySizeOverrideIfNeeded( + displayContent, + mInfo, + newParentConfig, + resolvedConfig, + false /* optsOutEdgeToEdge */, + false /* hasFixedRotationTransform */, + false /* hasCompatDisplayInsets */); } void dispatchConfiguration(@NonNull Configuration config) { diff --git a/services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java b/services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java index 4211764085b1..3559e620a350 100644 --- a/services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java +++ b/services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java @@ -47,16 +47,13 @@ public enum DesktopModeFlagsUtil { Flags::enableDesktopWindowingWallpaperActivity, /* shouldOverrideByDevOption= */ true); private static final String TAG = "DesktopModeFlagsUtil"; - private static final String SYSTEM_PROPERTY_OVERRIDE_KEY = - "sys.wmshell.desktopmode.dev_toggle_override"; - // Function called to obtain aconfig flag value. private final Supplier<Boolean> mFlagFunction; // Whether the flag state should be affected by developer option. private final boolean mShouldOverrideByDevOption; // Local cache for toggle override, which is initialized once on its first access. It needs to - // be refreshed only on reboots as overridden state takes effect on reboots. + // be refreshed only on reboots as overridden state is expected to take effect on reboots. private static ToggleOverride sCachedToggleOverride; DesktopModeFlagsUtil(Supplier<Boolean> flagFunction, boolean shouldOverrideByDevOption) { @@ -67,9 +64,6 @@ public enum DesktopModeFlagsUtil { /** * Determines state of flag based on the actual flag and desktop mode developer option * overrides. - * - * Note: this method makes sure that a constant developer toggle overrides is read until - * reboot. */ public boolean isEnabled(Context context) { if (!Flags.showDesktopWindowingDevOption() @@ -102,49 +96,15 @@ public enum DesktopModeFlagsUtil { } /** - * Returns {@link ToggleOverride} from a non-persistent system property if present. Otherwise - * initializes the system property by reading Settings.Global. + * Returns {@link ToggleOverride} from Settings.Global set by toggle. */ private ToggleOverride getToggleOverrideFromSystem(Context context) { - // A non-persistent System Property is used to store override to ensure it remains - // constant till reboot. - String overrideProperty = System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, null); - ToggleOverride overrideFromSystemProperties = convertToToggleOverride(overrideProperty); - - // If valid system property, return it - if (overrideFromSystemProperties != null) { - return overrideFromSystemProperties; - } - - // Fallback when System Property is not present (just after reboot) or not valid (user - // manually changed the value): Read from Settings.Global int settingValue = Settings.Global.getInt( context.getContentResolver(), Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, OVERRIDE_UNSET.getSetting() ); - ToggleOverride overrideFromSettingsGlobal = - ToggleOverride.fromSetting(settingValue, OVERRIDE_UNSET); - // Initialize System Property - System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, String.valueOf(settingValue)); - return overrideFromSettingsGlobal; - } - - /** - * Converts {@code intString} into {@link ToggleOverride}. Return {@code null} if - * {@code intString} does not correspond to a {@link ToggleOverride}. - */ - private static @Nullable ToggleOverride convertToToggleOverride( - @Nullable String intString - ) { - if (intString == null) return null; - try { - int intValue = Integer.parseInt(intString); - return ToggleOverride.fromSetting(intValue, null); - } catch (NumberFormatException e) { - Log.w(TAG, "Unknown toggleOverride int " + intString); - return null; - } + return ToggleOverride.fromSetting(settingValue, OVERRIDE_UNSET); } /** Override state of desktop mode developer option toggle. */ diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index db4b171152a7..9e8811f419a2 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -153,6 +153,7 @@ import com.android.server.contentsuggestions.ContentSuggestionsManagerService; import com.android.server.contextualsearch.ContextualSearchManagerService; import com.android.server.coverage.CoverageService; import com.android.server.cpu.CpuMonitorService; +import com.android.server.crashrecovery.CrashRecoveryModule; import com.android.server.credentials.CredentialManagerService; import com.android.server.criticalevents.CriticalEventLog; import com.android.server.devicepolicy.DevicePolicyManagerService; @@ -381,8 +382,6 @@ public final class SystemServer implements Dumpable { + "OnDevicePersonalizationSystemService$Lifecycle"; private static final String UPDATABLE_DEVICE_CONFIG_SERVICE_CLASS = "com.android.server.deviceconfig.DeviceConfigInit$Lifecycle"; - private static final String CRASHRECOVERY_MODULE_LIFECYCLE_CLASS = - "com.android.server.crashrecovery.CrashRecoveryModule$Lifecycle"; /* @@ -2939,7 +2938,7 @@ public final class SystemServer implements Dumpable { if (Flags.refactorCrashrecovery()) { t.traceBegin("StartCrashRecoveryModule"); - mSystemServiceManager.startService(CRASHRECOVERY_MODULE_LIFECYCLE_CLASS); + mSystemServiceManager.startService(CrashRecoveryModule.Lifecycle.class); t.traceEnd(); } else { if (Flags.recoverabilityDetection()) { diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java index 3ed6ad78343b..acdbbdee7d67 100644 --- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java +++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java @@ -398,13 +398,13 @@ public final class ProfcollectForwardingService extends SystemService { if (randomNum >= traceFrequency) { return; } - // For a small percentage a traces, we collect the initialization behavior. - boolean traceInitialization = ThreadLocalRandom.current().nextInt(10) < 1; - int traceDelay = traceInitialization ? 0 : 1000; - String traceTag = traceInitialization ? "camera_init" : "camera"; + final int traceDelay = 1000; + final int traceDuration = 5000; + final String traceTag = "camera"; BackgroundThread.get().getThreadHandler().postDelayed(() -> { try { - mIProfcollect.trace_process(traceTag, "android.hardware.camera.provider"); + mIProfcollect.trace_process(traceTag, "android.hardware.camera.provider", + traceDuration); } catch (RemoteException e) { Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage()); } diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamAccessibilityTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamAccessibilityTest.java index 99968d5117c7..9da695a4effb 100644 --- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamAccessibilityTest.java +++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamAccessibilityTest.java @@ -19,10 +19,12 @@ package com.android.server.dreams; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + import android.content.Context; import android.content.res.Resources; @@ -44,9 +46,6 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.ArrayList; -import java.util.Collections; - @SmallTest @RunWith(AndroidJUnit4.class) public class DreamAccessibilityTest { @@ -73,7 +72,8 @@ public class DreamAccessibilityTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); - mDreamAccessibility = new DreamAccessibility(mContext, mView); + Runnable mDismissCallback = () -> {}; + mDreamAccessibility = new DreamAccessibility(mContext, mView, mDismissCallback); when(mContext.getResources()).thenReturn(mResources); when(mResources.getString(R.string.dream_accessibility_action_click)) @@ -84,80 +84,55 @@ public class DreamAccessibilityTest { */ @Test public void testConfigureAccessibilityActions() { - when(mAccessibilityNodeInfo.getActionList()).thenReturn(new ArrayList<>()); + when(mView.getAccessibilityDelegate()).thenReturn(null); - mDreamAccessibility.updateAccessibilityConfiguration(false); + mDreamAccessibility.updateAccessibilityConfiguration(); verify(mView).setAccessibilityDelegate(mAccessibilityDelegateArgumentCaptor.capture()); - View.AccessibilityDelegate capturedDelegate = - mAccessibilityDelegateArgumentCaptor.getValue(); + View.AccessibilityDelegate capturedDelegate = mAccessibilityDelegateArgumentCaptor + .getValue(); capturedDelegate.onInitializeAccessibilityNodeInfo(mView, mAccessibilityNodeInfo); verify(mAccessibilityNodeInfo).addAction(argThat(action -> - action.getId() == AccessibilityNodeInfo.ACTION_CLICK + action.getId() == AccessibilityNodeInfo.ACTION_DISMISS && TextUtils.equals(action.getLabel(), CUSTOM_ACTION))); } /** - * Test to verify the configuration of accessibility actions within a view delegate, - * specifically checking the removal of an existing click action and addition - * of a new custom action. + * Test to verify no accessibility configuration is added if one exist. */ @Test - public void testConfigureAccessibilityActions_RemovesExistingClickAction() { - AccessibilityNodeInfo.AccessibilityAction existingAction = - new AccessibilityNodeInfo.AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK, - EXISTING_ACTION); - when(mAccessibilityNodeInfo.getActionList()) - .thenReturn(Collections.singletonList(existingAction)); - - mDreamAccessibility.updateAccessibilityConfiguration(false); - - verify(mView).setAccessibilityDelegate(mAccessibilityDelegateArgumentCaptor.capture()); - View.AccessibilityDelegate capturedDelegate = - mAccessibilityDelegateArgumentCaptor.getValue(); - - capturedDelegate.onInitializeAccessibilityNodeInfo(mView, mAccessibilityNodeInfo); + public void testNotAddingDuplicateAccessibilityConfiguration() { + View.AccessibilityDelegate existingDelegate = mock(View.AccessibilityDelegate.class); + when(mView.getAccessibilityDelegate()).thenReturn(existingDelegate); - verify(mAccessibilityNodeInfo).removeAction(existingAction); - verify(mAccessibilityNodeInfo).addAction(argThat(action -> - action.getId() == AccessibilityNodeInfo.ACTION_CLICK - && TextUtils.equals(action.getLabel(), CUSTOM_ACTION))); + mDreamAccessibility.updateAccessibilityConfiguration(); + verify(mView, never()).setAccessibilityDelegate(any()); } /** - * Test to verify the removal of a custom accessibility action within a view delegate. + * Test to verify dismiss callback is called */ @Test - public void testRemoveCustomAccessibilityAction() { + public void testPerformAccessibilityAction() { + Runnable mockDismissCallback = mock(Runnable.class); + DreamAccessibility dreamAccessibility = new DreamAccessibility(mContext, + mView, mockDismissCallback); - AccessibilityNodeInfo.AccessibilityAction existingAction = - new AccessibilityNodeInfo.AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK, - EXISTING_ACTION); - when(mAccessibilityNodeInfo.getActionList()) - .thenReturn(Collections.singletonList(existingAction)); + dreamAccessibility.updateAccessibilityConfiguration(); - mDreamAccessibility.updateAccessibilityConfiguration(false); verify(mView).setAccessibilityDelegate(mAccessibilityDelegateArgumentCaptor.capture()); - View.AccessibilityDelegate capturedDelegate = - mAccessibilityDelegateArgumentCaptor.getValue(); - when(mView.getAccessibilityDelegate()).thenReturn(capturedDelegate); - clearInvocations(mView); + View.AccessibilityDelegate capturedDelegate = mAccessibilityDelegateArgumentCaptor + .getValue(); - mDreamAccessibility.updateAccessibilityConfiguration(true); - verify(mView).setAccessibilityDelegate(null); - } + boolean result = capturedDelegate.performAccessibilityAction(mView, + AccessibilityNodeInfo.ACTION_DISMISS, null); - /** - * Test to verify the removal of custom accessibility action is not called if delegate is not - * set by the dreamService. - */ - @Test - public void testRemoveCustomAccessibility_DoesNotRemoveDelegateNotSetByDreamAccessibility() { - mDreamAccessibility.updateAccessibilityConfiguration(true); - verify(mView, never()).setAccessibilityDelegate(any()); + assertTrue(result); + verify(mockDismissCallback).run(); } + } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java index 644ae4717eb1..005ceee703a8 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java @@ -130,7 +130,7 @@ public class CpuPowerStatsCollectorValidationTest { boolean inCpuSection = false; for (int i = 0; i < lines.length; i++) { if (!inCpuSection) { - if (lines[i].startsWith("CpuPowerStatsCollector")) { + if (lines[i].startsWith("cpu (1)")) { inCpuSection = true; } } else if (lines[i].startsWith(" ")) { diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerStatsTest.java index 7bec13f653e7..1621d47d62b5 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerStatsTest.java @@ -149,9 +149,9 @@ public class CustomEnergyConsumerPowerStatsTest { .isEqualTo(20000); assertThat(ps2.uidStats.size()).isEqualTo(2); assertThat(POWER_STATS_LAYOUT.getUidConsumedEnergy(ps2.uidStats.get(APP_UID1), 0)) - .isEqualTo(14000); + .isEqualTo(4000); assertThat(POWER_STATS_LAYOUT.getUidConsumedEnergy(ps2.uidStats.get(APP_UID2), 0)) - .isEqualTo(21000); + .isEqualTo(6000); } @Test @@ -209,8 +209,8 @@ public class CustomEnergyConsumerPowerStatsTest { assertThat(POWER_STATS_LAYOUT.getDevicePowerEstimate(deviceStats)) .isWithin(PRECISION).of(expectedPower * 0.75); - // UID1: estimated power = 14,000 uC = 0.00388 mAh - expectedPower = 0.00388; + // UID1: estimated power = 4,000 uC = 0.00111 mAh + expectedPower = 0.00111; ps2.getUidStats(uidStats, APP_UID1, states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND)); assertThat(POWER_STATS_LAYOUT.getUidPowerEstimate(uidStats)) @@ -221,8 +221,8 @@ public class CustomEnergyConsumerPowerStatsTest { assertThat(POWER_STATS_LAYOUT.getUidPowerEstimate(uidStats)) .isWithin(PRECISION).of(expectedPower * 0.75); - // UID2: estimated power = 21,000 uC = 0.00583 mAh - expectedPower = 0.00583; + // UID2: estimated power = 6,000 uC = 0.00166 mAh + expectedPower = 0.00167; ps2.getUidStats(uidStats, APP_UID2, states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED)); assertThat(POWER_STATS_LAYOUT.getUidPowerEstimate(uidStats)) diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java index 0f385325e525..a4222ff5650b 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -1518,7 +1518,8 @@ public class BiometricServiceTest { mBiometricService.onStart(); when(mTrustManager.isInSignificantPlace()).thenReturn(false); - when(mBiometricService.mSettingObserver.getMandatoryBiometricsEnabledForUser(anyInt())) + when(mBiometricService.mSettingObserver + .getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(anyInt())) .thenReturn(true); setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); @@ -1540,7 +1541,8 @@ public class BiometricServiceTest { mBiometricService.onStart(); when(mTrustManager.isInSignificantPlace()).thenReturn(false); - when(mBiometricService.mSettingObserver.getMandatoryBiometricsEnabledForUser(anyInt())) + when(mBiometricService.mSettingObserver + .getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(anyInt())) .thenReturn(true); setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); @@ -1564,7 +1566,8 @@ public class BiometricServiceTest { mBiometricService.onStart(); when(mTrustManager.isInSignificantPlace()).thenReturn(false); - when(mBiometricService.mSettingObserver.getMandatoryBiometricsEnabledForUser(anyInt())) + when(mBiometricService.mSettingObserver + .getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(anyInt())) .thenReturn(true); setupAuthForOnly(TYPE_CREDENTIAL, Authenticators.DEVICE_CREDENTIAL); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java index b831ef5c30bc..240da9fe46bd 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java @@ -90,7 +90,8 @@ public class PreAuthInfoTest { when(mDevicePolicyManager.getKeyguardDisabledFeatures(any(), anyInt())) .thenReturn(KEYGUARD_DISABLE_FEATURES_NONE); when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true); - when(mSettingObserver.getMandatoryBiometricsEnabledForUser(anyInt())).thenReturn(true); + when(mSettingObserver.getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser( + anyInt())).thenReturn(true); when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true); when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true); when(mFaceAuthenticator.getLockoutModeForUser(anyInt())) diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java index 316b5faf2a68..689b241f0faa 100644 --- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java @@ -39,6 +39,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -364,6 +365,39 @@ public class MediaProjectionManagerServiceTest { @EnableFlags(android.companion.virtualdevice.flags .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test + public void testReuseProjection_keyguardNotLocked_startConsentDialog() + throws NameNotFoundException { + MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); + projection.start(mIMediaProjectionCallback); + + doNothing().when(mContext).startActivityAsUser(any(), any()); + doReturn(false).when(mKeyguardManager).isKeyguardLocked(); + + MediaProjectionManagerService.BinderService mediaProjectionBinderService = + mService.new BinderService(mContext); + mediaProjectionBinderService.requestConsentForInvalidProjection(projection); + + verify(mContext).startActivityAsUser(any(), any()); + } + + @EnableFlags(android.companion.virtualdevice.flags + .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) + @Test + public void testReuseProjection_keyguardLocked_noConsentDialog() throws NameNotFoundException { + MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); + projection.start(mIMediaProjectionCallback); + + doReturn(true).when(mKeyguardManager).isKeyguardLocked(); + MediaProjectionManagerService.BinderService mediaProjectionBinderService = + mService.new BinderService(mContext); + mediaProjectionBinderService.requestConsentForInvalidProjection(projection); + + verify(mContext, never()).startActivityAsUser(any(), any()); + } + + @EnableFlags(android.companion.virtualdevice.flags + .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) + @Test public void testKeyguardLocked_stopsActiveProjection() throws Exception { MediaProjectionManagerService service = new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector); diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java index 9dac23f075e6..d7004e72bc52 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java @@ -1746,10 +1746,6 @@ public class VibrationThreadTest { assertTrue("Tested duration=" + duration4, duration4 < 2000); // Effect5: played normally after effect4, which may or may not have played. - - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId5)); - verifyCallbacksTriggered(vibrationId5, Vibration.Status.FINISHED); - assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)), fakeVibrator.getEffectSegments(vibrationId5)); } diff --git a/services/tests/wmtests/res/xml/bookmarks.xml b/services/tests/wmtests/res/xml/bookmarks.xml index 88419e9c441b..1549b2de78c3 100644 --- a/services/tests/wmtests/res/xml/bookmarks.xml +++ b/services/tests/wmtests/res/xml/bookmarks.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2024 The Android Open Source Project +<!-- Copyright 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. @@ -14,6 +14,8 @@ limitations under the License. --> <bookmarks> + <!-- the key combinations for the following shortcuts must be in sync + with the key combinations sent by the test in ModifierShortcutTests.java --> <bookmark role="android.app.role.BROWSER" shortcut="b" /> @@ -38,4 +40,37 @@ <bookmark category="android.intent.category.APP_CALCULATOR" shortcut="u" /> + + <!-- The following shortcuts will not be invoked by tests but are here to + provide test coverage of parsing the different types of shortcut. --> + <bookmark + package="com.test" + class="com.test.BookmarkTest" + shortcut="a" /> + <bookmark + package="com.test2" + class="com.test.BookmarkTest" + shortcut="d" /> + + <bookmark + role="android.app.role.BROWSER" + shortcut="b" + shift="true" /> + <bookmark + category="android.intent.category.APP_CONTACTS" + shortcut="c" + shift="true" /> + <bookmark + package="com.test" + class="com.test.BookmarkTest" + shortcut="a" + shift="true" /> + + <!-- It's intended that this package/class will NOT resolve so we test the resolution + failure case. --> + <bookmark + package="com.test3" + class="com.test.BookmarkTest" + shortcut="f" /> + </bookmarks> diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java index 8c375d413950..5533ff909c50 100644 --- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 The Android Open Source Project + * Copyright 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. @@ -19,15 +19,22 @@ package com.android.server.policy; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static org.junit.Assert.assertEquals; +import static org.mockito.AdditionalMatchers.aryEq; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyObject; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.os.Handler; @@ -58,27 +65,56 @@ public class ModifierShortcutManagerTests { private Handler mHandler; private Context mContext; private Resources mResources; + private PackageManager mPackageManager; @Before public void setUp() { mHandler = new Handler(Looper.getMainLooper()); mContext = spy(getInstrumentation().getTargetContext()); mResources = spy(mContext.getResources()); + mPackageManager = spy(mContext.getPackageManager()); XmlResourceParser testBookmarks = mResources.getXml( com.android.frameworks.wmtests.R.xml.bookmarks); when(mContext.getResources()).thenReturn(mResources); + when(mContext.getPackageManager()).thenReturn(mPackageManager); when(mResources.getXml(R.xml.bookmarks)).thenReturn(testBookmarks); + try { + // Keep packageName / className in sync with + // services/tests/wmtests/res/xml/bookmarks.xml + ActivityInfo testActivityInfo = new ActivityInfo(); + testActivityInfo.applicationInfo = new ApplicationInfo(); + testActivityInfo.packageName = + testActivityInfo.applicationInfo.packageName = "com.test"; + + doReturn(testActivityInfo).when(mPackageManager).getActivityInfo( + eq(new ComponentName("com.test", "com.test.BookmarkTest")), anyInt()); + doThrow(new PackageManager.NameNotFoundException("com.test3")).when(mPackageManager) + .getActivityInfo(eq(new ComponentName("com.test3", "com.test.BookmarkTest")), + anyInt()); + } catch (PackageManager.NameNotFoundException ignored) { } + doReturn(new String[] { "com.test" }).when(mPackageManager) + .canonicalToCurrentPackageNames(aryEq(new String[] { "com.test2" })); + mModifierShortcutManager = new ModifierShortcutManager(mContext, mHandler); } @Test public void test_getApplicationLaunchKeyboardShortcuts() { + // Expected values here determined by the number of shortcuts defined in + // services/tests/wmtests/res/xml/bookmarks.xml + + // Total valid shortcuts. KeyboardShortcutGroup group = mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(-1); - assertEquals(8, group.getItems().size()); + assertEquals(13, group.getItems().size()); + + // Total valid shift shortcuts. + assertEquals(3, group.getItems().stream() + .filter(s -> s.getModifiers() == (KeyEvent.META_SHIFT_ON | KeyEvent.META_META_ON)) + .count()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java new file mode 100644 index 000000000000..ddd6d562abb1 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java @@ -0,0 +1,316 @@ +/* + * 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.server.wm; + +import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + +import static org.junit.Assert.assertEquals; + +import android.compat.testing.PlatformCompatChangeRule; +import android.platform.test.annotations.Presubmit; + +import androidx.annotation.NonNull; + +import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; +import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runner.RunWith; + +import java.util.function.Consumer; + +/** + * Test class for {@link AppCompatAspectRatioOverrides}. + * <p> + * Build/Install/Run: + * atest WmTests:AppCompatAspectRatioOverridesTest + */ +@Presubmit +@RunWith(WindowTestRunner.class) +public class AppCompatAspectRatioOverridesTest extends WindowTestsBase { + + @Rule + public TestRule compatChangeRule = new PlatformCompatChangeRule(); + + @Test + public void testShouldApplyUserFullscreenOverride_trueProperty_returnsFalse() { + runTestScenario((robot)-> { + robot.prop().enable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE); + robot.conf().enableUserAppAspectRatioFullscreen(/* enabled */ false); + + robot.activity().createActivityWithComponent(); + + robot.checkShouldApplyUserFullscreenOverride(/* expected */ false); + }); + } + + @Test + public void testShouldApplyUserFullscreenOverride_falseFullscreenProperty_returnsFalse() { + runTestScenario((robot)-> { + robot.conf().enableUserAppAspectRatioFullscreen(/* enabled */ true); + robot.activity().setIgnoreOrientationRequest(/* enabled */ true); + robot.prop().disable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE); + robot.activity().createActivityWithComponent(); + robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_FULLSCREEN); + + robot.checkShouldApplyUserFullscreenOverride(/* expected */ false); + }); + } + + @Test + public void testShouldApplyUserFullscreenOverride_falseSettingsProperty_returnsFalse() { + runTestScenario((robot)-> { + robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true); + robot.activity().setIgnoreOrientationRequest(/* enabled */ true); + robot.prop().disable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_FULLSCREEN); + robot.checkShouldApplyUserFullscreenOverride(/* expected */ false); + }); + } + + + @Test + public void testShouldApplyUserFullscreenOverride_returnsTrue() { + runTestScenario((robot)-> { + robot.conf().enableUserAppAspectRatioFullscreen(/* enabled */ true); + robot.activity().setIgnoreOrientationRequest(/* enabled */ true); + robot.activity().createActivityWithComponent(); + robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_FULLSCREEN); + + robot.checkShouldApplyUserFullscreenOverride(/* expected */ true); + }); + } + + @Test + public void testShouldEnableUserAspectRatioSettings_falseProperty_returnsFalse() { + runTestScenario((robot)-> { + robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true); + robot.activity().setIgnoreOrientationRequest(/* enabled */ true); + robot.prop().disable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2); + + robot.checkShouldEnableUserAspectRatioSettings(/* expected */ false); + }); + } + + @Test + public void testShouldEnableUserAspectRatioSettings_trueProperty_returnsTrue() { + runTestScenario((robot)-> { + robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true); + robot.activity().setIgnoreOrientationRequest(/* enabled */ true); + robot.prop().enable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2); + + robot.checkShouldEnableUserAspectRatioSettings(/* expected */ true); + }); + } + + @Test + public void testShouldEnableUserAspectRatioSettings_ignoreOrientation_returnsFalse() { + runTestScenario((robot)-> { + robot.conf().enableUserAppAspectRatioSettings(/* enabled */ false); + robot.activity().setIgnoreOrientationRequest(/* enabled */ true); + robot.prop().enable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2); + + robot.checkShouldEnableUserAspectRatioSettings(/* expected */ false); + }); + } + + @Test + public void testShouldApplyUserMinAspectRatioOverride_falseProperty_returnsFalse() { + runTestScenario((robot)-> { + robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true); + robot.activity().setIgnoreOrientationRequest(/* enabled */ true); + robot.prop().disable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2); + + robot.checkShouldEnableUserAspectRatioSettings(/* expected */ false); + }); + } + + @Test + public void testShouldApplyUserMinAspectRatioOverride_trueProperty_returnsFalse() { + runTestScenario((robot)-> { + robot.conf().enableUserAppAspectRatioSettings(/* enabled */ false); + robot.prop().enable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + + robot.checkShouldEnableUserAspectRatioSettings(/* enabled */ false); + }); + } + + @Test + public void testShouldApplyUserMinAspectRatioOverride_disabledIgnoreOrientationRequest() { + runTestScenario((robot)-> { + robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true); + robot.activity().setIgnoreOrientationRequest(/* enabled */ false); + robot.activity().createActivityWithComponent(); + robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2); + + robot.checkShouldApplyUserMinAspectRatioOverride(/* expected */ false); + }); + } + + @Test + public void testShouldApplyUserMinAspectRatioOverride_returnsTrue() { + runTestScenario((robot)-> { + robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true); + robot.activity().setIgnoreOrientationRequest(/* enabled */ true); + robot.activity().createActivityWithComponent(); + robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2); + + robot.checkShouldApplyUserMinAspectRatioOverride(/* expected */ true); + }); + } + + @Test + public void testShouldApplyUserMinAspectRatioOverride_ignoreOrientation_returnsFalse() { + runTestScenario((robot)-> { + robot.conf().enableUserAppAspectRatioSettings(/* enabled */ false); + robot.activity().setIgnoreOrientationRequest(/* enabled */ true); + robot.activity().createActivityWithComponent(); + robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2); + + robot.checkShouldApplyUserMinAspectRatioOverride(/* expected */ false); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO}) + public void testShouldOverrideMinAspectRatio_overrideEnabled_returnsTrue() { + runTestScenario((robot)-> { + robot.activity().createActivityWithComponent(); + + robot.checkShouldOverrideMinAspectRatio(/* expected */ true); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO}) + public void testShouldOverrideMinAspectRatio_propertyTrue_overrideEnabled_returnsTrue() { + runTestScenario((robot)-> { + robot.prop().enable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + + robot.checkShouldOverrideMinAspectRatio(/* expected */ true); + }); + } + + @Test + @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO}) + public void testShouldOverrideMinAspectRatio_propertyTrue_overrideDisabled_returnsFalse() { + runTestScenario((robot)-> { + robot.prop().enable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + + robot.checkShouldOverrideMinAspectRatio(/* expected */ false); + }); + } + + @Test + @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO}) + public void testShouldOverrideMinAspectRatio_overrideDisabled_returnsFalse() { + runTestScenario((robot)-> { + robot.activity().createActivityWithComponent(); + + robot.checkShouldOverrideMinAspectRatio(/* expected */ false); + }); + } + + + @Test + @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO}) + public void testshouldOverrideMinAspectRatio_propertyFalse_overrideEnabled_returnsFalse() { + runTestScenario((robot)-> { + robot.prop().disable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + + robot.checkShouldOverrideMinAspectRatio(/* expected */ false); + }); + } + + @Test + @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO}) + public void testshouldOverrideMinAspectRatio_propertyFalse_noOverride_returnsFalse() { + runTestScenario((robot)-> { + robot.prop().disable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + + robot.checkShouldOverrideMinAspectRatio(/* expected */ false); + }); + } + + /** + * Runs a test scenario providing a Robot. + */ + void runTestScenario(@NonNull Consumer<AspectRatioOverridesRobotTest> consumer) { + spyOn(mWm.mAppCompatConfiguration); + final AspectRatioOverridesRobotTest robot = + new AspectRatioOverridesRobotTest(mWm, mAtm, mSupervisor); + consumer.accept(robot); + } + + private static class AspectRatioOverridesRobotTest extends AppCompatRobotBase { + + AspectRatioOverridesRobotTest(@NonNull WindowManagerService wm, + @NonNull ActivityTaskManagerService atm, + @NonNull ActivityTaskSupervisor supervisor) { + super(wm, atm, supervisor); + } + + void checkShouldApplyUserFullscreenOverride(boolean expected) { + assertEquals(expected, getTopActivityAppCompatAspectRatioOverrides() + .shouldApplyUserFullscreenOverride()); + } + + void checkShouldEnableUserAspectRatioSettings(boolean expected) { + assertEquals(expected, getTopActivityAppCompatAspectRatioOverrides() + .shouldEnableUserAspectRatioSettings()); + } + + void checkShouldApplyUserMinAspectRatioOverride(boolean expected) { + assertEquals(expected, getTopActivityAppCompatAspectRatioOverrides() + .shouldApplyUserMinAspectRatioOverride()); + } + + void checkShouldOverrideMinAspectRatio(boolean expected) { + assertEquals(expected, getTopActivityAppCompatAspectRatioOverrides() + .shouldOverrideMinAspectRatio()); + } + + @NonNull + private AppCompatAspectRatioOverrides getTopActivityAppCompatAspectRatioOverrides() { + return activity().top().mAppCompatController.getAppCompatAspectRatioOverrides(); + } + } + +} diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java index d8c7fb3594c1..de99f546ab07 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java @@ -25,6 +25,7 @@ import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAME import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION; import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH; import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM; @@ -36,6 +37,7 @@ import android.platform.test.annotations.Presubmit; import androidx.annotation.NonNull; +import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; import org.junit.Rule; @@ -286,6 +288,88 @@ public class AppCompatCameraOverridesTest extends WindowTestsBase { }); } + + @Test + @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA}) + public void shouldOverrideMinAspectRatioForCamera_overrideEnabled_returnsTrue() { + runTestScenario((robot) -> { + robot.activity().createActivityWithComponent(); + robot.activity().activateCameraInPolicy(/* isCameraActive */ true); + + robot.checkShouldOverrideMinAspectRatioForCamera(/* expected */ true); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA}) + public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideEnabled_returnsTrue() { + runTestScenario((robot) -> { + robot.prop().enable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + robot.activity().activateCameraInPolicy(/* isCameraActive */ true); + + robot.checkShouldOverrideMinAspectRatioForCamera(/* expected */ true); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA}) + public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideEnabled_returnsFalse() { + runTestScenario((robot) -> { + robot.prop().enable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + robot.activity().activateCameraInPolicy(/* isCameraActive */ false); + + robot.checkShouldOverrideMinAspectRatioForCamera(/* expected */ false); + }); + } + + @Test + @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA}) + public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideDisabled_returnsFalse() { + runTestScenario((robot) -> { + robot.prop().enable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + robot.activity().activateCameraInPolicy(/* isCameraActive */ true); + + robot.checkShouldOverrideMinAspectRatioForCamera(/* expected */ false); + }); + } + + @Test + @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA}) + public void shouldOverrideMinAspectRatioForCamera_overrideDisabled_returnsFalse() { + runTestScenario((robot) -> { + robot.activity().createActivityWithComponent(); + robot.activity().activateCameraInPolicy(/* isCameraActive */ true); + + robot.checkShouldOverrideMinAspectRatioForCamera(/* expected */ false); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA}) + public void shouldOverrideMinAspectRatioForCamera_propertyFalse_overrideEnabled_returnsFalse() { + runTestScenario((robot) -> { + robot.prop().disable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + + robot.checkShouldOverrideMinAspectRatioForCamera(/* expected */ false); + }); + } + + @Test + @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA}) + public void shouldOverrideMinAspectRatioForCamera_propertyFalse_noOverride_returnsFalse() { + runTestScenario((robot) -> { + robot.prop().disable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + robot.activity().activateCameraInPolicy(/* isCameraActive */ true); + + robot.checkShouldOverrideMinAspectRatioForCamera(/* expected */ false); + }); + } + /** * Runs a test scenario providing a Robot. */ @@ -323,6 +407,11 @@ public class AppCompatCameraOverridesTest extends WindowTestsBase { .shouldApplyFreeformTreatmentForCameraCompat(), expected); } + void checkShouldOverrideMinAspectRatioForCamera(boolean expected) { + Assert.assertEquals(getAppCompatCameraOverrides() + .shouldOverrideMinAspectRatioForCamera(), expected); + } + void checkIsCameraActive(boolean active) { Assert.assertEquals(getAppCompatCameraOverrides().isCameraActive(), active); } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatComponentPropRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatComponentPropRobot.java index d568eecfd1c5..361177f480a6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatComponentPropRobot.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatComponentPropRobot.java @@ -32,27 +32,27 @@ import androidx.annotation.NonNull; */ class AppCompatComponentPropRobot { @NonNull - private final WindowManagerService mWm; + private final PackageManager mPackageManager; AppCompatComponentPropRobot(@NonNull WindowManagerService wm) { - mWm = wm; + mPackageManager = wm.mContext.getPackageManager(); + spyOn(mPackageManager); } void enable(@NonNull String propertyName) { - setPropertyValue(propertyName, /* enabled */ true); + setPropertyValue(propertyName, "", "", /* enabled */ true); } void disable(@NonNull String propertyName) { - setPropertyValue(propertyName, /* enabled */ false); + setPropertyValue(propertyName, "", "", /* enabled */ false); } - private void setPropertyValue(@NonNull String propertyName, boolean enabled) { + private void setPropertyValue(@NonNull String propertyName, @NonNull String packageName, + @NonNull String className, boolean enabled) { final PackageManager.Property property = new PackageManager.Property(propertyName, - /* value */ enabled, /* packageName */ "", /* className */ ""); - final PackageManager pm = mWm.mContext.getPackageManager(); - spyOn(pm); + /* value */ enabled, packageName, className); try { - doReturn(property).when(pm).getProperty(eq(propertyName), anyString()); + doReturn(property).when(mPackageManager).getProperty(eq(propertyName), anyString()); } catch (PackageManager.NameNotFoundException e) { fail(e.getLocalizedMessage()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java index cb3cf6bd2a5c..0a1b16bfc3e9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java @@ -66,6 +66,4 @@ class AppCompatConfigurationRobot { doReturn(enabled).when(mAppCompatConfiguration) .isCameraCompatSplitScreenAspectRatioEnabled(); } - - } diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java index 63c14b90958f..afa22bc5eae8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -67,6 +67,7 @@ import android.window.IOnBackInvokedCallback; import android.window.OnBackInvokedCallback; import android.window.OnBackInvokedCallbackInfo; import android.window.OnBackInvokedDispatcher; +import android.window.TaskFragmentOrganizer; import android.window.TaskSnapshot; import android.window.WindowOnBackInvokedDispatcher; @@ -670,25 +671,29 @@ public class BackNavigationControllerTests extends WindowTestsBase { } @Test - public void testAdjacentFocusInActivityEmbedding() { + public void testBackOnMostRecentWindowInActivityEmbedding() { mSetFlagsRule.enableFlags(Flags.FLAG_EMBEDDED_ACTIVITY_BACK_NAV_FLAG); final Task task = createTask(mDefaultDisplay); - final TaskFragment primaryTf = createTaskFragmentWithActivity(task); - final TaskFragment secondaryTf = createTaskFragmentWithActivity(task); + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + final TaskFragment primaryTf = createTaskFragmentWithEmbeddedActivity(task, organizer); + final TaskFragment secondaryTf = createTaskFragmentWithEmbeddedActivity(task, organizer); final ActivityRecord primaryActivity = primaryTf.getTopMostActivity(); final ActivityRecord secondaryActivity = secondaryTf.getTopMostActivity(); primaryTf.setAdjacentTaskFragment(secondaryTf); secondaryTf.setAdjacentTaskFragment(primaryTf); - final WindowState windowState = mock(WindowState.class); - windowState.mActivityRecord = primaryActivity; - doReturn(windowState).when(mWm).getFocusedWindowLocked(); - doReturn(primaryTf).when(windowState).getTaskFragment(); + final WindowState primaryWindow = mock(WindowState.class); + final WindowState secondaryWindow = mock(WindowState.class); + doReturn(primaryActivity).when(primaryWindow).getActivityRecord(); + doReturn(secondaryActivity).when(secondaryWindow).getActivityRecord(); doReturn(1L).when(primaryActivity).getLastWindowCreateTime(); doReturn(2L).when(secondaryActivity).getLastWindowCreateTime(); + doReturn(mDisplayContent).when(primaryActivity).getDisplayContent(); + doReturn(secondaryWindow).when(mDisplayContent).findFocusedWindow(eq(secondaryActivity)); - startBackNavigation(); - verify(mWm).moveFocusToActivity(eq(secondaryActivity)); + final WindowState mostRecentUsedWindow = + mWm.getMostRecentUsedEmbeddedWindowForBack(primaryWindow); + assertThat(mostRecentUsedWindow).isEqualTo(secondaryWindow); } /** diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java index d318f0047c08..44c7057b2294 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -19,19 +19,12 @@ package com.android.server.wm; import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP; import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP; import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS; -import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO; -import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA; import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION; -import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2; -import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE; -import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES; -import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE; -import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE; import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -119,8 +112,6 @@ public class LetterboxUiControllerTest extends WindowTestsBase { mController = new LetterboxUiController(mWm, mActivity); } - - @Test public void testGetCropBoundsIfNeeded_handleCropForTransparentActivityBasedOnOpaqueBounds() { final InsetsSource taskbar = new InsetsSource(/*id=*/ 0, @@ -320,164 +311,6 @@ public class LetterboxUiControllerTest extends WindowTestsBase { return mainWindow; } - // shouldApplyUser...Override - @Test - public void testShouldApplyUserFullscreenOverride_trueProperty_returnsFalse() throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE, - /* value */ true); - - doReturn(false).when(mAppCompatConfiguration).isUserAppAspectRatioFullscreenEnabled(); - mActivity = setUpActivityWithComponent(); - - assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldApplyUserFullscreenOverride()); - } - - @Test - public void testShouldApplyUserFullscreenOverride_falseFullscreenProperty_returnsFalse() - throws Exception { - prepareActivityThatShouldApplyUserFullscreenOverride(); - mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE, - /* value */ false); - - mActivity = setUpActivityWithComponent(); - - assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldApplyUserFullscreenOverride()); - } - - @Test - public void testShouldApplyUserFullscreenOverride_falseSettingsProperty_returnsFalse() - throws Exception { - prepareActivityThatShouldApplyUserFullscreenOverride(); - mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false); - - mActivity = setUpActivityWithComponent(); - - assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldApplyUserFullscreenOverride()); - } - - @Test - public void testShouldApplyUserFullscreenOverride_returnsTrue() { - prepareActivityThatShouldApplyUserFullscreenOverride(); - - assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldApplyUserFullscreenOverride()); - } - - @Test - public void testShouldEnableUserAspectRatioSettings_falseProperty_returnsFalse() - throws Exception { - prepareActivityThatShouldApplyUserMinAspectRatioOverride(); - mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false); - - mActivity = setUpActivityWithComponent(); - mController = new LetterboxUiController(mWm, mActivity); - - assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldEnableUserAspectRatioSettings()); - } - - @Test - public void testShouldEnableUserAspectRatioSettings_trueProperty_returnsTrue() - throws Exception { - - mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true); - mActivity = setUpActivityWithComponent(); - prepareActivityThatShouldApplyUserMinAspectRatioOverride(); - - mController = new LetterboxUiController(mWm, mActivity); - - assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldEnableUserAspectRatioSettings()); - } - - @Test - public void testShouldEnableUserAspectRatioSettings_noIgnoreOrientation_returnsFalse() - throws Exception { - prepareActivityForShouldApplyUserMinAspectRatioOverride(/* orientationRequest */ false); - mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true); - - mController = new LetterboxUiController(mWm, mActivity); - - assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldEnableUserAspectRatioSettings()); - } - - @Test - public void testShouldApplyUserMinAspectRatioOverride_falseProperty_returnsFalse() - throws Exception { - prepareActivityThatShouldApplyUserMinAspectRatioOverride(); - mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false); - - mActivity = setUpActivityWithComponent(); - - assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldEnableUserAspectRatioSettings()); - } - - @Test - public void testShouldApplyUserMinAspectRatioOverride_trueProperty_returnsFalse() - throws Exception { - doReturn(false).when(mAppCompatConfiguration).isUserAppAspectRatioSettingsEnabled(); - mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true); - - mActivity = setUpActivityWithComponent(); - - assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldApplyUserMinAspectRatioOverride()); - } - - @Test - public void testShouldApplyUserMinAspectRatioOverride_disabledIgnoreOrientationRequest() { - prepareActivityThatShouldApplyUserMinAspectRatioOverride(); - mDisplayContent.setIgnoreOrientationRequest(false); - - assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldApplyUserMinAspectRatioOverride()); - } - - @Test - public void testShouldApplyUserMinAspectRatioOverride_returnsTrue() { - prepareActivityThatShouldApplyUserMinAspectRatioOverride(); - - assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldApplyUserMinAspectRatioOverride()); - } - - @Test - public void testShouldApplyUserMinAspectRatioOverride_noIgnoreOrientation_returnsFalse() { - prepareActivityForShouldApplyUserMinAspectRatioOverride(/* orientationRequest */ false); - - assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldApplyUserMinAspectRatioOverride()); - } - - private void prepareActivityForShouldApplyUserMinAspectRatioOverride( - boolean orientationRequest) { - spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()); - doReturn(orientationRequest).when( - mAppCompatConfiguration).isUserAppAspectRatioSettingsEnabled(); - mDisplayContent.setIgnoreOrientationRequest(true); - doReturn(USER_MIN_ASPECT_RATIO_3_2) - .when(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()) - .getUserMinAspectRatioOverrideCode(); - } - - private void prepareActivityThatShouldApplyUserMinAspectRatioOverride() { - prepareActivityForShouldApplyUserMinAspectRatioOverride(/* orientationRequest */ true); - } - - private void prepareActivityThatShouldApplyUserFullscreenOverride() { - spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()); - doReturn(true).when(mAppCompatConfiguration).isUserAppAspectRatioFullscreenEnabled(); - mDisplayContent.setIgnoreOrientationRequest(true); - doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN) - .when(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()) - .getUserMinAspectRatioOverrideCode(); - } - // shouldUseDisplayLandscapeNaturalOrientation @Test @@ -595,156 +428,6 @@ public class LetterboxUiControllerTest extends WindowTestsBase { } @Test - @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO}) - public void testshouldOverrideMinAspectRatio_overrideEnabled_returnsTrue() { - mActivity = setUpActivityWithComponent(); - - assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldOverrideMinAspectRatio()); - } - - @Test - @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO}) - public void testshouldOverrideMinAspectRatio_propertyTrue_overrideEnabled_returnsTrue() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true); - mActivity = setUpActivityWithComponent(); - - assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldOverrideMinAspectRatio()); - } - - @Test - @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO}) - public void testshouldOverrideMinAspectRatio_propertyTrue_overrideDisabled_returnsFalse() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true); - mActivity = setUpActivityWithComponent(); - - assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldOverrideMinAspectRatio()); - } - - @Test - @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO}) - public void testshouldOverrideMinAspectRatio_overrideDisabled_returnsFalse() { - mActivity = setUpActivityWithComponent(); - - assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldOverrideMinAspectRatio()); - } - - @Test - @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO}) - public void testshouldOverrideMinAspectRatio_propertyFalse_overrideEnabled_returnsFalse() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false); - - mActivity = setUpActivityWithComponent(); - - assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldOverrideMinAspectRatio()); - } - - @Test - @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO}) - public void testshouldOverrideMinAspectRatio_propertyFalse_noOverride_returnsFalse() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false); - mActivity = setUpActivityWithComponent(); - - assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldOverrideMinAspectRatio()); - } - - @Test - @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA}) - public void shouldOverrideMinAspectRatioForCamera_overrideEnabled_returnsTrue() { - mActivity = setUpActivityWithComponent(); - doReturn(true).when(mActivity.mAppCompatController - .getAppCompatCameraOverrides()).isCameraActive(); - - assertTrue(mActivity.mAppCompatController.getAppCompatCameraOverrides() - .shouldOverrideMinAspectRatioForCamera()); - } - - @Test - @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA}) - public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideEnabled_returnsTrue() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true); - mActivity = setUpActivityWithComponent(); - doReturn(true).when(mActivity.mAppCompatController - .getAppCompatCameraOverrides()).isCameraActive(); - - assertTrue(mActivity.mAppCompatController.getAppCompatCameraOverrides() - .shouldOverrideMinAspectRatioForCamera()); - } - - @Test - @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA}) - public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideEnabled_returnsFalse() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true); - mActivity = setUpActivityWithComponent(); - doReturn(false).when(mActivity.mAppCompatController - .getAppCompatCameraOverrides()).isCameraActive(); - - assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides() - .shouldOverrideMinAspectRatioForCamera()); - } - - @Test - @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA}) - public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideDisabled_returnsFalse() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true); - mActivity = setUpActivityWithComponent(); - doReturn(true).when(mActivity.mAppCompatController - .getAppCompatCameraOverrides()).isCameraActive(); - - assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides() - .shouldOverrideMinAspectRatioForCamera()); - } - - @Test - @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA}) - public void shouldOverrideMinAspectRatioForCamera_overrideDisabled_returnsFalse() { - mActivity = setUpActivityWithComponent(); - doReturn(true).when(mActivity.mAppCompatController - .getAppCompatCameraOverrides()).isCameraActive(); - - assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides() - .shouldOverrideMinAspectRatioForCamera()); - } - - @Test - @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA}) - public void shouldOverrideMinAspectRatioForCamera_propertyFalse_overrideEnabled_returnsFalse() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false); - mActivity = setUpActivityWithComponent(); - - assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides() - .shouldOverrideMinAspectRatioForCamera()); - } - - @Test - @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA}) - public void shouldOverrideMinAspectRatioForCamera_propertyFalse_noOverride_returnsFalse() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false); - - mActivity = setUpActivityWithComponent(); - - doReturn(true).when(mActivity.mAppCompatController - .getAppCompatCameraOverrides()).isCameraActive(); - - assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides() - .shouldOverrideMinAspectRatioForCamera()); - } - - @Test @EnableCompatChanges({FORCE_RESIZE_APP}) public void testshouldOverrideForceResizeApp_overrideEnabled_returnsTrue() { mController = new LetterboxUiController(mWm, mActivity); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java deleted file mode 100644 index d5356774ffdb..000000000000 --- a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java +++ /dev/null @@ -1,526 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm; - -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; -import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; - -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; -import static com.android.internal.policy.TaskResizingAlgorithm.MIN_ASPECT; -import static com.android.server.wm.WindowManagerService.dipToPixel; -import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP; -import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - -import android.graphics.Rect; -import android.platform.test.annotations.Presubmit; -import android.util.DisplayMetrics; -import android.util.Log; - -import androidx.test.filters.SmallTest; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Tests for the {@link TaskPositioner} class. - * - * Build/Install/Run: - * atest WmTests:TaskPositionerTests - */ -@SmallTest -@Presubmit -@RunWith(WindowTestRunner.class) -public class TaskPositionerTests extends WindowTestsBase { - - private static final boolean DEBUGGING = false; - private static final String TAG = "TaskPositionerTest"; - - private static final int MOUSE_DELTA_X = 5; - private static final int MOUSE_DELTA_Y = 5; - - private int mMinVisibleWidth; - private int mMinVisibleHeight; - private TaskPositioner mPositioner; - - @Before - public void setUp() { - TaskPositioner.setFactory(null); - - final DisplayMetrics dm = mDisplayContent.getDisplayMetrics(); - - // This should be the same calculation as the TaskPositioner uses. - mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, dm); - mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, dm); - removeGlobalMinSizeRestriction(); - - final ActivityRecord activity = new ActivityBuilder(mAtm) - .setCreateTask(true) - .build(); - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "window"); - mPositioner = new TaskPositioner(mWm); - mPositioner.register(mDisplayContent, win); - - win.getRootTask().setWindowingMode(WINDOWING_MODE_FREEFORM); - } - - @After - public void tearDown() { - TaskPositioner.setFactory(null); - } - - @Test - public void testOverrideFactory() { - final boolean[] created = new boolean[1]; - created[0] = false; - TaskPositioner.setFactory(new TaskPositioner.Factory() { - @Override - public TaskPositioner create(WindowManagerService service) { - created[0] = true; - return null; - } - }); - - assertNull(TaskPositioner.create(mWm)); - assertTrue(created[0]); - } - - /** This tests that the window can move in all directions. */ - @Test - public void testMoveWindow() { - final Rect displayBounds = mDisplayContent.getBounds(); - final int windowSize = Math.min(displayBounds.width(), displayBounds.height()) / 2; - final int left = displayBounds.centerX() - windowSize / 2; - final int top = displayBounds.centerY() - windowSize / 2; - final Rect r = new Rect(left, top, left + windowSize, top + windowSize); - mPositioner.mTask.setBounds(r); - mPositioner.startDrag(false /* resizing */, false /* preserveOrientation */, left, top); - - // Move upper left. - mPositioner.notifyMoveLocked(left - MOUSE_DELTA_X, top - MOUSE_DELTA_Y); - r.offset(-MOUSE_DELTA_X, -MOUSE_DELTA_Y); - assertBoundsEquals(r, mPositioner.getWindowDragBounds()); - - // Move bottom right. - mPositioner.notifyMoveLocked(left, top); - r.offset(MOUSE_DELTA_X, MOUSE_DELTA_Y); - assertBoundsEquals(r, mPositioner.getWindowDragBounds()); - } - - /** - * This tests that free resizing will allow to change the orientation as well - * as does some basic tests (e.g. dragging in Y only will keep X stable). - */ - @Test - public void testBasicFreeWindowResizing() { - final Rect r = new Rect(100, 220, 700, 520); - final int midY = (r.top + r.bottom) / 2; - mPositioner.mTask.setBounds(r, true); - - // Start a drag resize starting upper left. - mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */, - r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y); - assertBoundsEquals(r, mPositioner.getWindowDragBounds()); - - // Drag to a good landscape size. - mPositioner.resizeDrag(0.0f, 0.0f); - assertBoundsEquals(new Rect(MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to a good portrait size. - mPositioner.resizeDrag(400.0f, 0.0f); - assertBoundsEquals(new Rect(400 + MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to a too small size for the width. - mPositioner.resizeDrag(2000.0f, r.top); - assertBoundsEquals( - new Rect(r.right - mMinVisibleWidth, r.top + MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to a too small size for the height. - mPositioner.resizeDrag(r.left, 2000.0f); - assertBoundsEquals( - new Rect(r.left + MOUSE_DELTA_X, r.bottom - mMinVisibleHeight, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Start a drag resize left and see that only the left coord changes.. - mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */, - r.left - MOUSE_DELTA_X, midY); - - // Drag to the left. - mPositioner.resizeDrag(0.0f, midY); - assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the right. - mPositioner.resizeDrag(200.0f, midY); - assertBoundsEquals(new Rect(200 + MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the top - mPositioner.resizeDrag(r.left, 0.0f); - assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the bottom - mPositioner.resizeDrag(r.left, 1000.0f); - assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - } - - /** - * This tests that by dragging any edge, the fixed / opposite edge(s) remains anchored. - */ - @Test - public void testFreeWindowResizingTestAllEdges() { - final Rect r = new Rect(100, 220, 700, 520); - final int midX = (r.left + r.right) / 2; - final int midY = (r.top + r.bottom) / 2; - mPositioner.mTask.setBounds(r, true); - - // Drag upper left. - mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */, - r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y); - mPositioner.resizeDrag(0.0f, 0.0f); - assertNotEquals(r.left, mPositioner.getWindowDragBounds().left); - assertEquals(r.right, mPositioner.getWindowDragBounds().right); - assertNotEquals(r.top, mPositioner.getWindowDragBounds().top); - assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom); - - // Drag upper. - mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */, midX, - r.top - MOUSE_DELTA_Y); - mPositioner.resizeDrag(0.0f, 0.0f); - assertEquals(r.left, mPositioner.getWindowDragBounds().left); - assertEquals(r.right, mPositioner.getWindowDragBounds().right); - assertNotEquals(r.top, mPositioner.getWindowDragBounds().top); - assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom); - - // Drag upper right. - mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */, - r.right + MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y); - mPositioner.resizeDrag(r.right + 100, 0.0f); - assertEquals(r.left, mPositioner.getWindowDragBounds().left); - assertNotEquals(r.right, mPositioner.getWindowDragBounds().right); - assertNotEquals(r.top, mPositioner.getWindowDragBounds().top); - assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom); - - // Drag right. - mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */, - r.right + MOUSE_DELTA_X, midY); - mPositioner.resizeDrag(r.right + 100, 0.0f); - assertEquals(r.left, mPositioner.getWindowDragBounds().left); - assertNotEquals(r.right, mPositioner.getWindowDragBounds().right); - assertEquals(r.top, mPositioner.getWindowDragBounds().top); - assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom); - - // Drag bottom right. - mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */, - r.right + MOUSE_DELTA_X, r.bottom + MOUSE_DELTA_Y); - mPositioner.resizeDrag(r.right + 100, r.bottom + 100); - assertEquals(r.left, mPositioner.getWindowDragBounds().left); - assertNotEquals(r.right, mPositioner.getWindowDragBounds().right); - assertEquals(r.top, mPositioner.getWindowDragBounds().top); - assertNotEquals(r.bottom, mPositioner.getWindowDragBounds().bottom); - - // Drag bottom. - mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */, midX, - r.bottom + MOUSE_DELTA_Y); - mPositioner.resizeDrag(r.right + 100, r.bottom + 100); - assertEquals(r.left, mPositioner.getWindowDragBounds().left); - assertEquals(r.right, mPositioner.getWindowDragBounds().right); - assertEquals(r.top, mPositioner.getWindowDragBounds().top); - assertNotEquals(r.bottom, mPositioner.getWindowDragBounds().bottom); - - // Drag bottom left. - mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */, - r.left - MOUSE_DELTA_X, r.bottom + MOUSE_DELTA_Y); - mPositioner.resizeDrag(0.0f, r.bottom + 100); - assertNotEquals(r.left, mPositioner.getWindowDragBounds().left); - assertEquals(r.right, mPositioner.getWindowDragBounds().right); - assertEquals(r.top, mPositioner.getWindowDragBounds().top); - assertNotEquals(r.bottom, mPositioner.getWindowDragBounds().bottom); - - // Drag left. - mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */, - r.left - MOUSE_DELTA_X, midY); - mPositioner.resizeDrag(0.0f, r.bottom + 100); - assertNotEquals(r.left, mPositioner.getWindowDragBounds().left); - assertEquals(r.right, mPositioner.getWindowDragBounds().right); - assertEquals(r.top, mPositioner.getWindowDragBounds().top); - assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom); - } - - /** - * This tests that a constrained landscape window will keep the aspect and do the - * right things upon resizing when dragged from the top left corner. - */ - @Test - public void testLandscapePreservedWindowResizingDragTopLeft() { - final Rect r = new Rect(100, 220, 700, 520); - mPositioner.mTask.setBounds(r, true); - - mPositioner.startDrag(true /* resizing */, true /* preserveOrientation */, - r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y); - assertBoundsEquals(r, mPositioner.getWindowDragBounds()); - - // Drag to a good landscape size. - mPositioner.resizeDrag(0.0f, 0.0f); - assertBoundsEquals(new Rect(MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to a good portrait size. - mPositioner.resizeDrag(400.0f, 0.0f); - int width = Math.round((float) (r.bottom - MOUSE_DELTA_Y) * MIN_ASPECT); - assertBoundsEquals(new Rect(r.right - width, MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to a too small size for the width. - mPositioner.resizeDrag(2000.0f, r.top); - final int w = mMinVisibleWidth; - final int h = Math.round(w / MIN_ASPECT); - assertBoundsEquals(new Rect(r.right - w, r.bottom - h, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to a too small size for the height. - mPositioner.resizeDrag(r.left, 2000.0f); - assertBoundsEquals( - new Rect(r.left + MOUSE_DELTA_X, r.bottom - mMinVisibleHeight, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - } - - /** - * This tests that a constrained landscape window will keep the aspect and do the - * right things upon resizing when dragged from the left corner. - */ - @Test - public void testLandscapePreservedWindowResizingDragLeft() { - final Rect r = new Rect(100, 220, 700, 520); - final int midY = (r.top + r.bottom) / 2; - mPositioner.mTask.setBounds(r, true); - - mPositioner.startDrag(true /* resizing */, true /* preserveOrientation */, - r.left - MOUSE_DELTA_X, midY); - - // Drag to the left. - mPositioner.resizeDrag(0.0f, midY); - assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the right. - mPositioner.resizeDrag(200.0f, midY); - assertBoundsEquals(new Rect(200 + MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag all the way to the right and see the height also shrinking. - mPositioner.resizeDrag(2000.0f, midY); - final int w = mMinVisibleWidth; - final int h = Math.round((float) w / MIN_ASPECT); - assertBoundsEquals(new Rect(r.right - w, r.top, r.right, r.top + h), - mPositioner.getWindowDragBounds()); - - // Drag to the top. - mPositioner.resizeDrag(r.left, 0.0f); - assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the bottom. - mPositioner.resizeDrag(r.left, 1000.0f); - assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - } - - /** - * This tests that a constrained landscape window will keep the aspect and do the - * right things upon resizing when dragged from the top corner. - */ - @Test - public void testLandscapePreservedWindowResizingDragTop() { - final Rect r = new Rect(100, 220, 700, 520); - final int midX = (r.left + r.right) / 2; - mPositioner.mTask.setBounds(r, true); - - mPositioner.startDrag(true /*resizing*/, true /*preserveOrientation*/, midX, - r.top - MOUSE_DELTA_Y); - - // Drag to the left (no change). - mPositioner.resizeDrag(0.0f, r.top); - assertBoundsEquals(new Rect(r.left, r.top + MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the right (no change). - mPositioner.resizeDrag(2000.0f, r.top); - assertBoundsEquals(new Rect(r.left , r.top + MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the top. - mPositioner.resizeDrag(300.0f, 0.0f); - int h = r.bottom - MOUSE_DELTA_Y; - int w = Math.max(r.right - r.left, Math.round(h * MIN_ASPECT)); - assertBoundsEquals(new Rect(r.left, MOUSE_DELTA_Y, r.left + w, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the bottom. - mPositioner.resizeDrag(r.left, 1000.0f); - h = mMinVisibleHeight; - assertBoundsEquals(new Rect(r.left, r.bottom - h, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - } - - /** - * This tests that a constrained portrait window will keep the aspect and do the - * right things upon resizing when dragged from the top left corner. - */ - @Test - public void testPortraitPreservedWindowResizingDragTopLeft() { - final Rect r = new Rect(330, 100, 630, 600); - mPositioner.mTask.setBounds(r, true); - - mPositioner.startDrag(true /*resizing*/, true /*preserveOrientation*/, - r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y); - assertBoundsEquals(r, mPositioner.getWindowDragBounds()); - - // Drag to a good landscape size. - mPositioner.resizeDrag(0.0f, 0.0f); - int height = Math.round((float) (r.right - MOUSE_DELTA_X) * MIN_ASPECT); - assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.bottom - height, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to a good portrait size. - mPositioner.resizeDrag(400.0f, 0.0f); - assertBoundsEquals(new Rect(400 + MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to a too small size for the height and the the width shrinking. - mPositioner.resizeDrag(r.left + MOUSE_DELTA_X, 2000.0f); - final int w = Math.max(mMinVisibleWidth, Math.round(mMinVisibleHeight / MIN_ASPECT)); - final int h = Math.max(mMinVisibleHeight, Math.round(w * MIN_ASPECT)); - assertBoundsEquals( - new Rect(r.right - w, r.bottom - h, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - } - - /** - * This tests that a constrained portrait window will keep the aspect and do the - * right things upon resizing when dragged from the left corner. - */ - @Test - public void testPortraitPreservedWindowResizingDragLeft() { - final Rect r = new Rect(330, 100, 630, 600); - final int midY = (r.top + r.bottom) / 2; - mPositioner.mTask.setBounds(r, true); - - mPositioner.startDrag(true /* resizing */, true /* preserveOrientation */, - r.left - MOUSE_DELTA_X, midY); - - // Drag to the left. - mPositioner.resizeDrag(0.0f, midY); - int w = r.right - MOUSE_DELTA_X; - int h = Math.round(w * MIN_ASPECT); - assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.top + h), - mPositioner.getWindowDragBounds()); - - // Drag to the right. - mPositioner.resizeDrag(450.0f, midY); - assertBoundsEquals(new Rect(450 + MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag all the way to the right. - mPositioner.resizeDrag(2000.0f, midY); - w = mMinVisibleWidth; - h = Math.max(Math.round((float) w * MIN_ASPECT), r.height()); - assertBoundsEquals(new Rect(r.right - w, r.top, r.right, r.top + h), - mPositioner.getWindowDragBounds()); - - // Drag to the top. - mPositioner.resizeDrag(r.left, 0.0f); - assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the bottom. - mPositioner.resizeDrag(r.left, 1000.0f); - assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - } - - /** - * This tests that a constrained portrait window will keep the aspect and do the - * right things upon resizing when dragged from the top corner. - */ - @Test - public void testPortraitPreservedWindowResizingDragTop() { - final Rect r = new Rect(330, 100, 630, 600); - final int midX = (r.left + r.right) / 2; - mPositioner.mTask.setBounds(r, true); - - mPositioner.startDrag(true /* resizing */, true /* preserveOrientation */, midX, - r.top - MOUSE_DELTA_Y); - - // Drag to the left (no change). - mPositioner.resizeDrag(0.0f, r.top); - assertBoundsEquals(new Rect(r.left, r.top + MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the right (no change). - mPositioner.resizeDrag(2000.0f, r.top); - assertBoundsEquals(new Rect(r.left , r.top + MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the top. - mPositioner.resizeDrag(300.0f, 0.0f); - int h = r.bottom - MOUSE_DELTA_Y; - int w = Math.min(r.width(), Math.round(h / MIN_ASPECT)); - assertBoundsEquals(new Rect(r.left, MOUSE_DELTA_Y, r.left + w, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the bottom. - mPositioner.resizeDrag(r.left, 1000.0f); - h = Math.max(mMinVisibleHeight, Math.round(mMinVisibleWidth * MIN_ASPECT)); - w = Math.round(h / MIN_ASPECT); - assertBoundsEquals(new Rect(r.left, r.bottom - h, r.left + w, r.bottom), - mPositioner.getWindowDragBounds()); - } - - private static void assertBoundsEquals(Rect expected, Rect actual) { - if (DEBUGGING) { - if (!expected.equals(actual)) { - Log.e(TAG, "rect(" + actual.toString() + ") != isRect(" + actual.toString() - + ") " + Log.getStackTraceString(new Throwable())); - } - } - assertEquals(expected, actual); - } - - @Test - public void testFinishingMovingWhenBinderDied() { - spyOn(mWm.mTaskPositioningController); - - mPositioner.startDrag(false, false, 0 /* startX */, 0 /* startY */); - verify(mWm.mTaskPositioningController, never()).finishTaskPositioning(); - mPositioner.binderDied(); - verify(mWm.mTaskPositioningController).finishTaskPositioning(); - } -} diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java deleted file mode 100644 index bfc13d3d2ef2..000000000000 --- a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm; - -import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; -import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; -import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; - -import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import android.platform.test.annotations.Presubmit; -import android.view.InputChannel; - -import androidx.test.filters.FlakyTest; -import androidx.test.filters.SmallTest; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Tests for the {@link TaskPositioningController} class. - * - * Build/Install/Run: - * atest WmTests:TaskPositioningControllerTests - */ -@SmallTest -@Presubmit -@RunWith(WindowTestRunner.class) -public class TaskPositioningControllerTests extends WindowTestsBase { - private static final int TIMEOUT_MS = 1000; - - private TaskPositioningController mTarget; - private WindowState mWindow; - - @Before - public void setUp() throws Exception { - assertNotNull(mWm.mTaskPositioningController); - mTarget = mWm.mTaskPositioningController; - - when(mWm.mInputManager.transferTouchGesture(any(), any())).thenReturn(true); - - mWindow = createWindow(null, TYPE_BASE_APPLICATION, "window"); - mWindow.getTask().setResizeMode(RESIZE_MODE_RESIZEABLE); - mWindow.mInputChannel = new InputChannel(); - mWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow); - doReturn(mock(InputMonitor.class)).when(mDisplayContent).getInputMonitor(); - } - - @FlakyTest(bugId = 291067614) - @Test - public void testStartAndFinishPositioning() { - assertFalse(mTarget.isPositioningLocked()); - assertNull(mTarget.getDragWindowHandleLocked()); - - assertTrue(mTarget.startMovingTask(mWindow.mClient, 0, 0)); - - assertTrue(mTarget.isPositioningLocked()); - assertNotNull(mTarget.getDragWindowHandleLocked()); - - mTarget.finishTaskPositioning(); - // Wait until the looper processes finishTaskPositioning. - assertTrue(waitHandlerIdle(mWm.mAnimationHandler, TIMEOUT_MS)); - - assertFalse(mTarget.isPositioningLocked()); - assertNull(mTarget.getDragWindowHandleLocked()); - } - - @Test - public void testFinishPositioningWhenAppRequested() { - assertFalse(mTarget.isPositioningLocked()); - assertNull(mTarget.getDragWindowHandleLocked()); - - assertTrue(mTarget.startMovingTask(mWindow.mClient, 0, 0)); - - assertTrue(mTarget.isPositioningLocked()); - assertNotNull(mTarget.getDragWindowHandleLocked()); - - mTarget.finishTaskPositioning(mWindow.mClient); - // Wait until the looper processes finishTaskPositioning. - assertTrue(waitHandlerIdle(mWm.mAnimationHandler, TIMEOUT_MS)); - - assertFalse(mTarget.isPositioningLocked()); - assertNull(mTarget.getDragWindowHandleLocked()); - } - - @Test - public void testHandleTapOutsideTask() { - assertFalse(mTarget.isPositioningLocked()); - assertNull(mTarget.getDragWindowHandleLocked()); - - final DisplayContent content = mock(DisplayContent.class); - doReturn(mWindow.getTask()).when(content).findTaskForResizePoint(anyInt(), anyInt()); - assertNotNull(mWindow.getTask().getTopVisibleAppMainWindow()); - - mTarget.handleTapOutsideTask(content, 0, 0); - // Wait until the looper processes handleTapOutsideTask. - assertTrue(waitHandlerIdle(mWm.mH, TIMEOUT_MS)); - - assertTrue(mTarget.isPositioningLocked()); - assertNotNull(mTarget.getDragWindowHandleLocked()); - - mTarget.finishTaskPositioning(); - // Wait until the looper processes finishTaskPositioning. - assertTrue(waitHandlerIdle(mWm.mAnimationHandler, TIMEOUT_MS)); - - assertFalse(mTarget.isPositioningLocked()); - assertNull(mTarget.getDragWindowHandleLocked()); - } - - @Test - public void testHandleTapOutsideNonResizableTask() { - assertFalse(mTarget.isPositioningLocked()); - assertNull(mTarget.getDragWindowHandleLocked()); - - final DisplayContent content = mock(DisplayContent.class); - doReturn(mWindow.getTask()).when(content).findTaskForResizePoint(anyInt(), anyInt()); - assertNotNull(mWindow.getTask().getTopVisibleAppMainWindow()); - - mWindow.getTask().setResizeMode(RESIZE_MODE_UNRESIZEABLE); - - mTarget.handleTapOutsideTask(content, 0, 0); - // Wait until the looper processes handleTapOutsideTask. - assertTrue(waitHandlerIdle(mWm.mH, TIMEOUT_MS)); - - assertFalse(mTarget.isPositioningLocked()); - } - -} diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 7d01b79fe866..720457e24370 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -1573,7 +1573,8 @@ public class TransitionTests extends WindowTestsBase { enteringAnimReports.clear(); doCallRealMethod().when(mWm.mRoot).ensureActivitiesVisible(any(), anyBoolean()); final boolean[] wasInFinishingTransition = { false }; - controller.registerLegacyListener(new WindowManagerInternal.AppTransitionListener() { + controller.registerLegacyListener(new WindowManagerInternal.AppTransitionListener( + mDisplayContent.mDisplayId) { @Override public void onAppTransitionFinishedLocked(IBinder token) { final ActivityRecord r = ActivityRecord.forToken(token); @@ -1582,6 +1583,14 @@ public class TransitionTests extends WindowTestsBase { } } }); + final boolean[] calledListenerOnOtherDisplay = { false }; + controller.registerLegacyListener(new WindowManagerInternal.AppTransitionListener( + mDisplayContent.mDisplayId + 1234) { + @Override + public void onAppTransitionFinishedLocked(IBinder token) { + calledListenerOnOtherDisplay[0] = true; + } + }); assertTrue(activity1.isVisible()); doReturn(false).when(task1).isTranslucent(null); doReturn(false).when(task1).isTranslucentForTransition(); @@ -1592,6 +1601,7 @@ public class TransitionTests extends WindowTestsBase { controller.finishTransition(closeTransition); assertTrue(wasInFinishingTransition[0]); + assertFalse(calledListenerOnOtherDisplay[0]); assertNull(controller.mFinishingTransition); assertTrue(activity2.isVisible()); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java index e6648dad4bbe..0cb22ad47355 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED; import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; @@ -86,6 +87,7 @@ public class WindowProcessControllerTests extends WindowTestsBase { ApplicationInfo info = mock(ApplicationInfo.class); info.packageName = "test.package.name"; + doReturn(true).when(info).isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED); mWpc = new WindowProcessController( mAtm, info, null, 0, -1, null, mMockListener); mWpc.setThread(mock(IApplicationThread.class)); diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java index e5f2f89ccead..eda78cb40c5d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java @@ -63,9 +63,6 @@ public class DesktopModeFlagsUtilTest extends WindowTestsBase { resetCache(); } - private static final String SYSTEM_PROPERTY_OVERRIDE_KEY = - "sys.wmshell.desktopmode.dev_toggle_override"; - @Test @DisableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) @@ -190,110 +187,6 @@ public class DesktopModeFlagsUtilTest extends WindowTestsBase { } @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isEnabled_noProperty_overrideOn_featureFlagOff_returnsTrueAndPropertyOn() { - System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY); - setOverride(OVERRIDE_ON.getSetting()); - - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue(); - // Store System Property if not present - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(String.valueOf(OVERRIDE_ON.getSetting())); - } - - @Test - @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - public void isEnabled_noProperty_overrideUnset_featureFlagOn_returnsTrueAndPropertyUnset() { - System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY); - setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting()); - - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue(); - // Store System Property if not present - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(String.valueOf( - DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting())); - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isEnabled_noProperty_overrideUnset_featureFlagOff_returnsFalseAndPropertyUnset() { - System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY); - setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting()); - - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse(); - // Store System Property if not present - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(String.valueOf( - DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting())); - } - - @Test - @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - public void isEnabled_propertyNotInt_overrideOff_featureFlagOn_returnsFalseAndPropertyOff() { - System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, "abc"); - setOverride(OVERRIDE_OFF.getSetting()); - - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse(); - // Store System Property if currently invalid - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(String.valueOf(OVERRIDE_OFF.getSetting())); - } - - @Test - @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - public void isEnabled_propertyInvalid_overrideOff_featureFlagOn_returnsFalseAndPropertyOff() { - System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, "-2"); - setOverride(OVERRIDE_OFF.getSetting()); - - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse(); - // Store System Property if currently invalid - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(String.valueOf(OVERRIDE_OFF.getSetting())); - } - - @Test - @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - public void isEnabled_propertyOff_overrideOn_featureFlagOn_returnsFalseAndnoPropertyUpdate() { - System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, String.valueOf( - OVERRIDE_OFF.getSetting())); - setOverride(OVERRIDE_ON.getSetting()); - - // Have a consistent override until reboot - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse(); - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(String.valueOf(OVERRIDE_OFF.getSetting())); - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isEnabled_propertyOn_overrideOff_featureFlagOff_returnsTrueAndnoPropertyUpdate() { - System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, String.valueOf(OVERRIDE_ON.getSetting())); - setOverride(OVERRIDE_OFF.getSetting()); - - // Have a consistent override until reboot - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue(); - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(String.valueOf(OVERRIDE_ON.getSetting())); - } - - @Test - @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - public void isEnabled_propertyUnset_overrideOff_featureFlagOn_returnsTrueAndnoPropertyUpdate() { - System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, - String.valueOf(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting())); - setOverride(OVERRIDE_OFF.getSetting()); - - // Have a consistent override until reboot - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue(); - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(String.valueOf( - DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting())); - } - - @Test @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY}) public void isEnabled_dwFlagOn_overrideUnset_featureFlagOn_returnsTrue() { @@ -452,8 +345,5 @@ public class DesktopModeFlagsUtilTest extends WindowTestsBase { "sCachedToggleOverride"); cachedToggleOverride.setAccessible(true); cachedToggleOverride.set(null, null); - - // Clear override cache stored in System property - System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY); } } diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java index b6672a0e2f4b..fad94d45c85d 100644 --- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java +++ b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java @@ -698,7 +698,7 @@ public class PerfettoProtoLogImplTest { traceMonitor.start(); mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, - "My test null string: %s", null); + "My test null string: %s", (Object) null); } finally { traceMonitor.stop(mWriter); } |