diff options
122 files changed, 2067 insertions, 691 deletions
diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java index 13c7946a033f..1488e14cfb8f 100644 --- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java +++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java @@ -76,6 +76,8 @@ public final class Telecom extends BaseCommand { private static final String COMMAND_CLEANUP_ORPHAN_PHONE_ACCOUNTS = "cleanup-orphan-phone-accounts"; private static final String COMMAND_RESET_CAR_MODE = "reset-car-mode"; + private static final String COMMAND_IS_NON_IN_CALL_SERVICE_BOUND = + "is-non-ui-in-call-service-bound"; /** * Change the system dialer package name if a package name was specified, @@ -169,6 +171,8 @@ public final class Telecom extends BaseCommand { + " over Telephony.\n" + "telecom log-mark <MESSAGE>: emits a message into the telecom logs. Useful for " + "testers to indicate where in the logs various test steps take place.\n" + + "telecom is-non-ui-in-call-service-bound <PACKAGE>: queries a particular " + + "non-ui-InCallService in InCallController to determine if it is bound \n" ); } @@ -270,6 +274,9 @@ public final class Telecom extends BaseCommand { case COMMAND_GET_MAX_PHONES: runGetMaxPhones(); break; + case COMMAND_IS_NON_IN_CALL_SERVICE_BOUND: + runIsNonUiInCallServiceBound(); + break; case COMMAND_SET_TEST_EMERGENCY_PHONE_ACCOUNT_PACKAGE_FILTER: runSetEmergencyPhoneAccountPackageFilter(); break; @@ -428,6 +435,18 @@ public final class Telecom extends BaseCommand { } /** + * prints out whether a particular non-ui InCallServices is bound in a call + */ + public void runIsNonUiInCallServiceBound() throws RemoteException { + if (TextUtils.isEmpty(mArgs.peekNextArg())) { + System.out.println("No Argument passed. Please pass a <PACKAGE_NAME> to lookup."); + } else { + System.out.println( + String.valueOf(mTelecomService.isNonUiInCallServiceBound(nextArg()))); + } + } + + /** * Prints the mSIM config to the console. * "DSDS" for a phone in DSDS mode * "" (empty string) for a phone in SS mode diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 050667b166bc..525f9ec793ef 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -3526,6 +3526,7 @@ package android.view { public final class InputDevice implements android.os.Parcelable { method @RequiresPermission("android.permission.DISABLE_INPUT_DEVICE") public void disable(); method @RequiresPermission("android.permission.DISABLE_INPUT_DEVICE") public void enable(); + method public int getAssociatedDisplayId(); method @NonNull public android.hardware.input.InputDeviceIdentifier getIdentifier(); method @Nullable public String getKeyboardLanguageTag(); method @Nullable public String getKeyboardLayoutType(); diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING index 394f9e74247b..06e1b5bb5fb8 100644 --- a/core/java/android/app/TEST_MAPPING +++ b/core/java/android/app/TEST_MAPPING @@ -244,7 +244,7 @@ "(/|^)DeviceFeature[^/]*", "(/|^)Hdmi[^/]*" ] }, -{ + { "name": "CtsWindowManagerDeviceActivity", "options": [ { diff --git a/core/java/android/database/sqlite/package.html b/core/java/android/database/sqlite/package.html index 71b8f3f8e17a..2c366466f9df 100644 --- a/core/java/android/database/sqlite/package.html +++ b/core/java/android/database/sqlite/package.html @@ -20,6 +20,7 @@ with adb shell, for example, <code>adb -e shell sqlite3</code>. <p>The version of SQLite depends on the version of Android. See the following table: <table style="width:auto;"> <tr><th>Android API</th><th>SQLite Version</th></tr> + <tr><td>LATEST</td><td>3.42.0</td></tr> <tr><td>API 34</td><td>3.39</td></tr> <tr><td>API 33</td><td>3.32</td></tr> <tr><td>API 32</td><td>3.32</td></tr> diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index e6bdfe1b95c4..299e7f112dd6 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -115,6 +115,7 @@ public class GraphicsEnvironment { private static final String ANGLE_GL_DRIVER_CHOICE_DEFAULT = "default"; private static final String ANGLE_GL_DRIVER_CHOICE_ANGLE = "angle"; private static final String ANGLE_GL_DRIVER_CHOICE_NATIVE = "native"; + private static final String SYSTEM_ANGLE_STRING = "system"; private static final String PROPERTY_RO_ANGLE_SUPPORTED = "ro.gfx.angle.supported"; @@ -195,15 +196,16 @@ public class GraphicsEnvironment { } /** - * Query to determine if ANGLE should be used + * Query to determine the ANGLE driver choice. */ - private boolean shouldUseAngle(Context context, Bundle coreSettings, String packageName) { + private String queryAngleChoice(Context context, Bundle coreSettings, + String packageName) { if (TextUtils.isEmpty(packageName)) { Log.v(TAG, "No package name specified; use the system driver"); - return false; + return ANGLE_GL_DRIVER_CHOICE_DEFAULT; } - return shouldUseAngleInternal(context, coreSettings, packageName); + return queryAngleChoiceInternal(context, coreSettings, packageName); } private int getVulkanVersion(PackageManager pm) { @@ -424,10 +426,11 @@ public class GraphicsEnvironment { * forces a choice; * 3) Use ANGLE if isAngleEnabledByGameMode() returns true; */ - private boolean shouldUseAngleInternal(Context context, Bundle bundle, String packageName) { + private String queryAngleChoiceInternal(Context context, Bundle bundle, + String packageName) { // Make sure we have a good package name if (TextUtils.isEmpty(packageName)) { - return false; + return ANGLE_GL_DRIVER_CHOICE_DEFAULT; } // Check the semi-global switch (i.e. once system has booted enough) for whether ANGLE @@ -442,7 +445,7 @@ public class GraphicsEnvironment { } if (allUseAngle == ANGLE_GL_DRIVER_ALL_ANGLE_ON) { Log.v(TAG, "Turn on ANGLE for all applications."); - return true; + return ANGLE_GL_DRIVER_CHOICE_ANGLE; } // Get the per-application settings lists @@ -465,7 +468,8 @@ public class GraphicsEnvironment { + optInPackages.size() + ", " + "number of values: " + optInValues.size()); - return mEnabledByGameMode; + return mEnabledByGameMode ? ANGLE_GL_DRIVER_CHOICE_ANGLE + : ANGLE_GL_DRIVER_CHOICE_DEFAULT; } // See if this application is listed in the per-application settings list @@ -473,7 +477,8 @@ public class GraphicsEnvironment { if (pkgIndex < 0) { Log.v(TAG, packageName + " is not listed in per-application setting"); - return mEnabledByGameMode; + return mEnabledByGameMode ? ANGLE_GL_DRIVER_CHOICE_ANGLE + : ANGLE_GL_DRIVER_CHOICE_DEFAULT; } mAngleOptInIndex = pkgIndex; @@ -484,13 +489,14 @@ public class GraphicsEnvironment { "ANGLE Developer option for '" + packageName + "' " + "set to: '" + optInValue + "'"); if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE)) { - return true; + return ANGLE_GL_DRIVER_CHOICE_ANGLE; } else if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE)) { - return false; + return ANGLE_GL_DRIVER_CHOICE_NATIVE; } else { // The user either chose default or an invalid value; go with the default driver or what // the game mode indicates - return mEnabledByGameMode; + return mEnabledByGameMode ? ANGLE_GL_DRIVER_CHOICE_ANGLE + : ANGLE_GL_DRIVER_CHOICE_DEFAULT; } } @@ -557,8 +563,12 @@ public class GraphicsEnvironment { */ private boolean setupAngle(Context context, Bundle bundle, PackageManager packageManager, String packageName) { - - if (!shouldUseAngle(context, bundle, packageName)) { + final String angleChoice = queryAngleChoice(context, bundle, packageName); + if (angleChoice.equals(ANGLE_GL_DRIVER_CHOICE_DEFAULT)) { + return false; + } + if (angleChoice.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE)) { + nativeSetAngleInfo("", true, packageName, null); return false; } @@ -627,10 +637,10 @@ public class GraphicsEnvironment { Log.d(TAG, "ANGLE package libs: " + paths); } - // If we make it to here, ANGLE will be used. Call setAngleInfo() with the package name, - // and features to use. + // If we make it to here, ANGLE apk will be used. Call nativeSetAngleInfo() with the + // application package name and ANGLE features to use. final String[] features = getAngleEglFeatures(context, bundle); - setAngleInfo(paths, false, packageName, features); + nativeSetAngleInfo(paths, false, packageName, features); return true; } @@ -652,10 +662,10 @@ public class GraphicsEnvironment { return false; } - // If we make it to here, ANGLE will be used. Call setAngleInfo() with the package name, - // and features to use. + // If we make it to here, system ANGLE will be used. Call nativeSetAngleInfo() with + // the application package name and ANGLE features to use. final String[] features = getAngleEglFeatures(context, bundle); - setAngleInfo("", true, packageName, features); + nativeSetAngleInfo(SYSTEM_ANGLE_STRING, false, packageName, features); return true; } @@ -936,8 +946,8 @@ public class GraphicsEnvironment { private static native void setDriverPathAndSphalLibraries(String path, String sphalLibraries); private static native void setGpuStats(String driverPackageName, String driverVersionName, long driverVersionCode, long driverBuildTime, String appPackageName, int vulkanVersion); - private static native void setAngleInfo(String path, boolean useSystemAngle, String packageName, - String[] features); + private static native void nativeSetAngleInfo(String path, boolean useNativeDriver, + String packageName, String[] features); private static native boolean setInjectLayersPrSetDumpable(); private static native void nativeToggleAngleAsSystemDriver(boolean enabled); diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java index 8b23f19f1067..f81dc5a58593 100644 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -1319,6 +1319,7 @@ public final class InputDevice implements Parcelable { } /** @hide */ + @TestApi public int getAssociatedDisplayId() { return mAssociatedDisplayId; } diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 1d5826807f36..17afd5576973 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -69,6 +69,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.internal.inputmethod.ImeTracing; import com.android.internal.inputmethod.SoftInputShowHideReason; +import com.android.internal.util.function.TriFunction; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -77,7 +78,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; -import java.util.function.BiFunction; /** * Implements {@link WindowInsetsController} on the client. @@ -627,7 +627,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private final InsetsState mLastDispatchedState = new InsetsState(); private final Rect mFrame = new Rect(); - private final BiFunction<InsetsController, InsetsSource, InsetsSourceConsumer> mConsumerCreator; + private final TriFunction<InsetsController, Integer, Integer, InsetsSourceConsumer> + mConsumerCreator; private final SparseArray<InsetsSourceConsumer> mSourceConsumers = new SparseArray<>(); private final InsetsSourceConsumer mImeSourceConsumer; private final Host mHost; @@ -695,13 +696,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation // Don't change the indexes of the sources while traversing. Remove it later. mPendingRemoveIndexes.add(index1); - - // Remove the consumer as well except the IME one. IME consumer should always - // be there since we need to communicate with InputMethodManager no matter we - // have the source or not. - if (source1.getType() != ime()) { - mSourceConsumers.remove(source1.getId()); - } } @Override @@ -756,12 +750,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation }; public InsetsController(Host host) { - this(host, (controller, source) -> { - if (source.getType() == ime()) { - return new ImeInsetsSourceConsumer(source.getId(), controller.mState, + this(host, (controller, id, type) -> { + if (type == ime()) { + return new ImeInsetsSourceConsumer(id, controller.mState, Transaction::new, controller); } else { - return new InsetsSourceConsumer(source.getId(), source.getType(), controller.mState, + return new InsetsSourceConsumer(id, type, controller.mState, Transaction::new, controller); } }, host.getHandler()); @@ -769,7 +763,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @VisibleForTesting public InsetsController(Host host, - BiFunction<InsetsController, InsetsSource, InsetsSourceConsumer> consumerCreator, + TriFunction<InsetsController, Integer, Integer, InsetsSourceConsumer> consumerCreator, Handler handler) { mHost = host; mConsumerCreator = consumerCreator; @@ -821,7 +815,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation }; // Make mImeSourceConsumer always non-null. - mImeSourceConsumer = getSourceConsumer(new InsetsSource(ID_IME, ime())); + mImeSourceConsumer = getSourceConsumer(ID_IME, ime()); } @VisibleForTesting @@ -898,7 +892,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation cancelledUserAnimationTypes[0] |= type; } } - getSourceConsumer(source).updateSource(source, animationType); + final InsetsSourceConsumer consumer = mSourceConsumers.get(source.getId()); + if (consumer != null) { + consumer.updateSource(source, animationType); + } else { + mState.addSource(source); + } existingTypes |= type; if (source.isVisible()) { visibleTypes |= type; @@ -1002,8 +1001,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @InsetsType int controllableTypes = 0; int consumedControlCount = 0; - final int[] showTypes = new int[1]; - final int[] hideTypes = new int[1]; + final @InsetsType int[] showTypes = new int[1]; + final @InsetsType int[] hideTypes = new int[1]; // Ensure to update all existing source consumers for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { @@ -1019,15 +1018,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation consumer.setControl(control, showTypes, hideTypes); } + // Ensure to create source consumers if not available yet. if (consumedControlCount != mTmpControlArray.size()) { - // Whoops! The server sent us some controls without sending corresponding sources. for (int i = mTmpControlArray.size() - 1; i >= 0; i--) { final InsetsSourceControl control = mTmpControlArray.valueAt(i); - final InsetsSourceConsumer consumer = mSourceConsumers.get(control.getId()); - if (consumer == null) { - control.release(SurfaceControl::release); - Log.e(TAG, control + " has no consumer."); - } + getSourceConsumer(control.getId(), control.getType()) + .setControl(control, showTypes, hideTypes); } } @@ -1592,6 +1588,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (type == ime()) { abortPendingImeControlRequest(); } + if (consumer.getType() != ime()) { + // IME consumer should always be there since we need to communicate with + // InputMethodManager no matter we have the control or not. + mSourceConsumers.remove(consumer.getId()); + } } private void cancelAnimation(InsetsAnimationControlRunner control, boolean invokeCallback) { @@ -1645,21 +1646,20 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } @VisibleForTesting - public @NonNull InsetsSourceConsumer getSourceConsumer(InsetsSource source) { - final int sourceId = source.getId(); - InsetsSourceConsumer consumer = mSourceConsumers.get(sourceId); + public @NonNull InsetsSourceConsumer getSourceConsumer(int id, int type) { + InsetsSourceConsumer consumer = mSourceConsumers.get(id); if (consumer != null) { return consumer; } - if (source.getType() == ime() && mImeSourceConsumer != null) { + if (type == ime() && mImeSourceConsumer != null) { // WindowInsets.Type.ime() should be only provided by one source. mSourceConsumers.remove(mImeSourceConsumer.getId()); consumer = mImeSourceConsumer; - consumer.setId(sourceId); + consumer.setId(id); } else { - consumer = mConsumerCreator.apply(this, source); + consumer = mConsumerCreator.apply(this, id, type); } - mSourceConsumers.put(sourceId, consumer); + mSourceConsumers.put(id, consumer); return consumer; } @@ -1668,8 +1668,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation return mImeSourceConsumer; } - @VisibleForTesting - public void notifyVisibilityChanged() { + void notifyVisibilityChanged() { mHost.notifyInsetsChanged(); } diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index 467d720196b1..34b288460a67 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -149,9 +149,12 @@ public class InsetsSourceConsumer { // Check if we need to restore server visibility. final InsetsSource localSource = mState.peekSource(mId); final InsetsSource serverSource = mController.getLastDispatchedState().peekSource(mId); - if (localSource != null && serverSource != null - && localSource.isVisible() != serverSource.isVisible()) { - localSource.setVisible(serverSource.isVisible()); + final boolean localVisible = localSource != null && localSource.isVisible(); + final boolean serverVisible = serverSource != null && serverSource.isVisible(); + if (localSource != null) { + localSource.setVisible(serverVisible); + } + if (localVisible != serverVisible) { mController.notifyVisibilityChanged(); } } else { diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index 3c1ad06a5334..2a88cf074f96 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -181,7 +181,7 @@ public class ViewConfiguration { private static final int TOUCH_SLOP = 8; /** Distance a stylus touch can wander before we think the user is handwriting in dips. */ - private static final int HANDWRITING_SLOP = 4; + private static final int HANDWRITING_SLOP = 2; /** * Defines the minimum size of the touch target for a scrollbar in dips diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 792b38bebefe..2405d4092a0d 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -4305,6 +4305,14 @@ public final class AutofillManager { } @Override + public void requestHideFillUiWhenDestroyed(int sessionId, AutofillId id) { + final AutofillManager afm = mAfm.get(); + if (afm != null) { + afm.post(() -> afm.requestHideFillUi(id, true)); + } + } + + @Override public void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) { final AutofillManager afm = mAfm.get(); if (afm != null) { diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl index 2e5967cc32d1..6e13097fe319 100644 --- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl +++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl @@ -81,6 +81,11 @@ oneway interface IAutoFillManagerClient { void requestHideFillUi(int sessionId, in AutofillId id); /** + * Requests hiding the fill UI when it's destroyed + */ + void requestHideFillUiWhenDestroyed(int sessionId, in AutofillId id); + + /** * Notifies no fill UI will be shown, and also mark the state as finished if necessary (if * sessionFinishedState != 0). */ diff --git a/core/java/android/window/WindowTokenClientController.java b/core/java/android/window/WindowTokenClientController.java index 2f05f830fe09..44847073646d 100644 --- a/core/java/android/window/WindowTokenClientController.java +++ b/core/java/android/window/WindowTokenClientController.java @@ -73,6 +73,12 @@ public class WindowTokenClientController { } } + /** Creates a new instance for test only. */ + @VisibleForTesting + public static WindowTokenClientController createInstanceForTesting() { + return new WindowTokenClientController(); + } + private WindowTokenClientController() {} /** diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp index afc3cbd15f88..8fc30d1c248d 100644 --- a/core/jni/android_os_GraphicsEnvironment.cpp +++ b/core/jni/android_os_GraphicsEnvironment.cpp @@ -50,7 +50,7 @@ void setGpuStats_native(JNIEnv* env, jobject clazz, jstring driverPackageName, appPackageNameChars.c_str(), vulkanVersion); } -void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useSystemAngle, +void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useNativeDriver, jstring packageName, jobjectArray featuresObj) { ScopedUtfChars pathChars(env, path); ScopedUtfChars packageNameChars(env, packageName); @@ -73,7 +73,7 @@ void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useS } } - android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), useSystemAngle, + android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), useNativeDriver, packageNameChars.c_str(), features); } @@ -118,7 +118,7 @@ const JNINativeMethod g_methods[] = { reinterpret_cast<void*>(setGpuStats_native)}, {"setInjectLayersPrSetDumpable", "()Z", reinterpret_cast<void*>(setInjectLayersPrSetDumpable_native)}, - {"setAngleInfo", "(Ljava/lang/String;ZLjava/lang/String;[Ljava/lang/String;)V", + {"nativeSetAngleInfo", "(Ljava/lang/String;ZLjava/lang/String;[Ljava/lang/String;)V", reinterpret_cast<void*>(setAngleInfo_native)}, {"setLayerPaths", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V", reinterpret_cast<void*>(setLayerPaths_native)}, diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index 6913bf9e3cfa..8da6d74de36d 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -106,6 +106,7 @@ public class ActivityThreadTest { false /* launchActivity */); private WindowTokenClientController mOriginalWindowTokenClientController; + private Configuration mOriginalAppConfig; private ArrayList<VirtualDisplay> mCreatedVirtualDisplays; @@ -114,6 +115,8 @@ public class ActivityThreadTest { // Keep track of the original controller, so that it can be used to restore in tearDown() // when there is override in some test cases. mOriginalWindowTokenClientController = WindowTokenClientController.getInstance(); + mOriginalAppConfig = new Configuration(ActivityThread.currentActivityThread() + .getConfiguration()); } @After @@ -123,6 +126,8 @@ public class ActivityThreadTest { mCreatedVirtualDisplays = null; } WindowTokenClientController.overrideForTesting(mOriginalWindowTokenClientController); + InstrumentationRegistry.getInstrumentation().runOnMainSync( + () -> restoreConfig(ActivityThread.currentActivityThread(), mOriginalAppConfig)); } @Test @@ -564,16 +569,10 @@ public class ActivityThreadTest { activityThread.updatePendingConfiguration(newAppConfig); activityThread.handleConfigurationChanged(newAppConfig, DEVICE_ID_INVALID); - try { - assertEquals("Virtual display orientation must not change when process" - + " configuration orientation changes.", - originalVirtualDisplayOrientation, - virtualDisplayContext.getResources().getConfiguration().orientation); - } finally { - // Make sure to reset the process config to prevent side effects to other - // tests. - restoreConfig(activityThread, originalAppConfig); - } + assertEquals("Virtual display orientation must not change when process" + + " configuration orientation changes.", + originalVirtualDisplayOrientation, + virtualDisplayContext.getResources().getConfiguration().orientation); }); } diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index 06920524acfc..b8f0d5c82eac 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -131,10 +131,10 @@ public class InsetsControllerTest { mTestClock = new OffsettableClock(); mTestHandler = new TestHandler(null, mTestClock); mTestHost = spy(new TestHost(mViewRoot)); - mController = new InsetsController(mTestHost, (controller, source) -> { - if (source.getType() == ime()) { - return new InsetsSourceConsumer(source.getId(), source.getType(), - controller.getState(), Transaction::new, controller) { + mController = new InsetsController(mTestHost, (controller, id, type) -> { + if (type == ime()) { + return new InsetsSourceConsumer(id, type, controller.getState(), + Transaction::new, controller) { private boolean mImeRequestedShow; @@ -150,8 +150,8 @@ public class InsetsControllerTest { } }; } else { - return new InsetsSourceConsumer(source.getId(), source.getType(), - controller.getState(), Transaction::new, controller); + return new InsetsSourceConsumer(id, type, controller.getState(), + Transaction::new, controller); } }, mTestHandler); final Rect rect = new Rect(5, 5, 5, 5); @@ -182,7 +182,8 @@ public class InsetsControllerTest { @Test public void testControlsChanged() { mController.onControlsChanged(createSingletonControl(ID_STATUS_BAR, statusBars())); - assertNotNull(mController.getSourceConsumer(mStatusSource).getControl().getLeash()); + assertNotNull( + mController.getSourceConsumer(ID_STATUS_BAR, statusBars()).getControl().getLeash()); mController.addOnControllableInsetsChangedListener( ((controller, typeMask) -> assertEquals(statusBars(), typeMask))); } @@ -194,7 +195,7 @@ public class InsetsControllerTest { mController.addOnControllableInsetsChangedListener(listener); mController.onControlsChanged(createSingletonControl(ID_STATUS_BAR, statusBars())); mController.onControlsChanged(new InsetsSourceControl[0]); - assertNull(mController.getSourceConsumer(mStatusSource).getControl()); + assertNull(mController.getSourceConsumer(ID_STATUS_BAR, statusBars()).getControl()); InOrder inOrder = Mockito.inOrder(listener); inOrder.verify(listener).onControllableInsetsChanged(eq(mController), eq(0)); inOrder.verify(listener).onControllableInsetsChanged(eq(mController), eq(statusBars())); @@ -254,7 +255,7 @@ public class InsetsControllerTest { // only the original thread that created view hierarchy can touch its views InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { mController.setSystemDrivenInsetsAnimationLoggingListener(loggingListener); - mController.getSourceConsumer(mImeSource).onWindowFocusGained(true); + mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true); // since there is no focused view, forcefully make IME visible. mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */); // When using the animation thread, this must not invoke onReady() @@ -271,7 +272,7 @@ public class InsetsControllerTest { prepareControls(); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { - mController.getSourceConsumer(mImeSource).onWindowFocusGained(true); + mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true); // since there is no focused view, forcefully make IME visible. mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */); mController.show(all()); @@ -284,7 +285,7 @@ public class InsetsControllerTest { mController.hide(all()); mController.cancelExistingAnimations(); assertEquals(0, mController.getRequestedVisibleTypes() & types); - mController.getSourceConsumer(mImeSource).onWindowFocusLost(); + mController.getSourceConsumer(ID_IME, ime()).onWindowFocusLost(); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } @@ -294,14 +295,14 @@ public class InsetsControllerTest { InsetsSourceControl ime = createControl(ID_IME, ime()); mController.onControlsChanged(new InsetsSourceControl[] { ime }); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { - mController.getSourceConsumer(mImeSource).onWindowFocusGained(true); + mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true); mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */); mController.cancelExistingAnimations(); assertTrue(isRequestedVisible(mController, ime())); mController.hide(ime(), true /* fromIme */, null /* statsToken */); mController.cancelExistingAnimations(); assertFalse(isRequestedVisible(mController, ime())); - mController.getSourceConsumer(mImeSource).onWindowFocusLost(); + mController.getSourceConsumer(ID_IME, ime()).onWindowFocusLost(); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } @@ -914,7 +915,7 @@ public class InsetsControllerTest { // Simulate IME insets is not controllable mController.onControlsChanged(new InsetsSourceControl[0]); final InsetsSourceConsumer imeInsetsConsumer = - mController.getSourceConsumer(mImeSource); + mController.getSourceConsumer(ID_IME, ime()); assertNull(imeInsetsConsumer.getControl()); // Verify IME requested visibility should be updated to IME consumer from controller. diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java index 988e69008008..655cb4519d3c 100644 --- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java @@ -219,10 +219,10 @@ public class InsetsSourceConsumerTest { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { InsetsState state = new InsetsState(); ViewRootInsetsControllerHost host = new ViewRootInsetsControllerHost(mViewRoot); - InsetsController insetsController = new InsetsController(host, (controller, source) -> { - if (source.getType() == ime()) { + InsetsController insetsController = new InsetsController(host, (ic, id, type) -> { + if (type == ime()) { return new InsetsSourceConsumer(ID_IME, ime(), state, - () -> mMockTransaction, controller) { + () -> mMockTransaction, ic) { @Override public int requestShow(boolean fromController, ImeTracker.Token statsToken) { @@ -230,11 +230,9 @@ public class InsetsSourceConsumerTest { } }; } - return new InsetsSourceConsumer(source.getId(), source.getType(), - controller.getState(), Transaction::new, controller); + return new InsetsSourceConsumer(id, type, ic.getState(), Transaction::new, ic); }, host.getHandler()); - InsetsSource imeSource = new InsetsSource(ID_IME, ime()); - InsetsSourceConsumer imeConsumer = insetsController.getSourceConsumer(imeSource); + InsetsSourceConsumer imeConsumer = insetsController.getSourceConsumer(ID_IME, ime()); // Initial IME insets source control with its leash. imeConsumer.setControl(new InsetsSourceControl(ID_IME, ime(), mLeash, diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java index 34eac35d3c0b..8028b14d53b6 100644 --- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java +++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java @@ -75,7 +75,7 @@ public class HandwritingInitiatorTest { private static final int HW_BOUNDS_OFFSETS_TOP_PX = 20; private static final int HW_BOUNDS_OFFSETS_RIGHT_PX = 30; private static final int HW_BOUNDS_OFFSETS_BOTTOM_PX = 40; - private int mHandwritingSlop = 4; + private int mHandwritingSlop = 2; private static final Rect sHwArea1; private static final Rect sHwArea2; diff --git a/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java b/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java index 9793dde0aaa5..767dd8c20b77 100644 --- a/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java +++ b/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java @@ -76,7 +76,7 @@ public class WindowTokenClientControllerTest { mOriginalWindowManagerService = WindowManagerGlobal.getWindowManagerService(); WindowManagerGlobal.overrideWindowManagerServiceForTesting(mWindowManagerService); doReturn(mClientToken).when(mWindowTokenClient).asBinder(); - mController = spy(WindowTokenClientController.getInstance()); + mController = spy(WindowTokenClientController.createInstanceForTesting()); } @After diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java index 47d58afb6aba..9facbd542e6c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java @@ -31,6 +31,7 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.dagger.pip.TvPipModule; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreenController; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 34a6e0ae406c..422e3b0216c8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -20,7 +20,6 @@ import static com.android.wm.shell.onehanded.OneHandedController.SUPPORT_ONE_HAN import android.app.ActivityTaskManager; import android.content.Context; -import android.content.pm.PackageManager; import android.os.Handler; import android.os.SystemProperties; import android.view.IWindowManager; @@ -45,7 +44,6 @@ import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.DockStateReader; -import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.LaunchAdjacentController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; @@ -76,10 +74,6 @@ import com.android.wm.shell.keyguard.KeyguardTransitions; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.pip.Pip; -import com.android.wm.shell.pip.PipMediaController; -import com.android.wm.shell.pip.PipSurfaceTransactionHelper; -import com.android.wm.shell.pip.PipUiEventLogger; -import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.recents.RecentTasks; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.recents.RecentsTransitionHandler; @@ -103,13 +97,13 @@ import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.UnfoldTransitionHandler; import com.android.wm.shell.windowdecor.WindowDecorViewModel; -import java.util.Optional; - import dagger.BindsOptionalOf; import dagger.Lazy; import dagger.Module; import dagger.Provides; +import java.util.Optional; + /** * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only * accessible from components within the WM subcomponent (can be explicitly exposed to the @@ -470,40 +464,6 @@ public abstract class WMShellBaseModule { } // - // Pip (optional feature) - // - - @WMSingleton - @Provides - static FloatingContentCoordinator provideFloatingContentCoordinator() { - return new FloatingContentCoordinator(); - } - - // Needs handler for registering broadcast receivers - @WMSingleton - @Provides - static PipMediaController providePipMediaController(Context context, - @ShellMainThread Handler mainHandler) { - return new PipMediaController(context, mainHandler); - } - - @WMSingleton - @Provides - static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper(Context context) { - return new PipSurfaceTransactionHelper(context); - } - - @WMSingleton - @Provides - static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger, - PackageManager packageManager) { - return new PipUiEventLogger(uiEventLogger, packageManager); - } - - @BindsOptionalOf - abstract PipTouchHandler optionalPipTouchHandler(); - - // // Recent tasks // @@ -838,7 +798,6 @@ public abstract class WMShellBaseModule { Optional<BubbleController> bubblesOptional, Optional<SplitScreenController> splitScreenOptional, Optional<Pip> pipOptional, - Optional<PipTouchHandler> pipTouchHandlerOptional, FullscreenTaskListener fullscreenTaskListener, Optional<UnfoldAnimationController> unfoldAnimationController, Optional<UnfoldTransitionHandler> unfoldTransitionHandler, 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 f5c6a03336ed..881c8f5552b6 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 @@ -16,6 +16,7 @@ package com.android.wm.shell.dagger; +import android.annotation.Nullable; import android.content.Context; import android.content.pm.LauncherApps; import android.os.Handler; @@ -46,13 +47,12 @@ import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.LaunchAdjacentController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.common.SystemWindows; -import com.android.wm.shell.common.TabletopModeController; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.annotations.ShellAnimationThread; import com.android.wm.shell.common.annotations.ShellBackgroundThread; import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.dagger.pip.PipModule; import com.android.wm.shell.desktopmode.DesktopModeController; import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; @@ -67,27 +67,7 @@ import com.android.wm.shell.freeform.FreeformTaskTransitionHandler; import com.android.wm.shell.freeform.FreeformTaskTransitionObserver; import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.onehanded.OneHandedController; -import com.android.wm.shell.pip.Pip; -import com.android.wm.shell.pip.PipAnimationController; -import com.android.wm.shell.pip.PipAppOpsListener; -import com.android.wm.shell.pip.PipBoundsAlgorithm; -import com.android.wm.shell.pip.PipBoundsState; -import com.android.wm.shell.pip.PipDisplayLayoutState; -import com.android.wm.shell.pip.PipMediaController; -import com.android.wm.shell.pip.PipParamsChangedForwarder; -import com.android.wm.shell.pip.PipSnapAlgorithm; -import com.android.wm.shell.pip.PipSurfaceTransactionHelper; -import com.android.wm.shell.pip.PipTaskOrganizer; -import com.android.wm.shell.pip.PipTransition; import com.android.wm.shell.pip.PipTransitionController; -import com.android.wm.shell.pip.PipTransitionState; -import com.android.wm.shell.pip.PipUiEventLogger; -import com.android.wm.shell.pip.phone.PhonePipKeepClearAlgorithm; -import com.android.wm.shell.pip.phone.PhonePipMenuController; -import com.android.wm.shell.pip.phone.PipController; -import com.android.wm.shell.pip.phone.PipMotionHelper; -import com.android.wm.shell.pip.phone.PipSizeSpecHandler; -import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.recents.RecentsTransitionHandler; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -127,7 +107,10 @@ import java.util.Optional; * This module only defines Shell dependencies for handheld SystemUI implementation. Common * dependencies should go into {@link WMShellBaseModule}. */ -@Module(includes = WMShellBaseModule.class) +@Module(includes = { + WMShellBaseModule.class, + PipModule.class +}) public abstract class WMShellModule { // @@ -351,195 +334,6 @@ public abstract class WMShellModule { } // - // Pip - // - - @WMSingleton - @Provides - static Optional<Pip> providePip(Context context, - ShellInit shellInit, - ShellCommandHandler shellCommandHandler, - ShellController shellController, - DisplayController displayController, - PipAnimationController pipAnimationController, - PipAppOpsListener pipAppOpsListener, - PipBoundsAlgorithm pipBoundsAlgorithm, - PhonePipKeepClearAlgorithm pipKeepClearAlgorithm, - PipBoundsState pipBoundsState, - PipSizeSpecHandler pipSizeSpecHandler, - PipDisplayLayoutState pipDisplayLayoutState, - PipMotionHelper pipMotionHelper, - PipMediaController pipMediaController, - PhonePipMenuController phonePipMenuController, - PipTaskOrganizer pipTaskOrganizer, - PipTransitionState pipTransitionState, - PipTouchHandler pipTouchHandler, - PipTransitionController pipTransitionController, - WindowManagerShellWrapper windowManagerShellWrapper, - TaskStackListenerImpl taskStackListener, - PipParamsChangedForwarder pipParamsChangedForwarder, - DisplayInsetsController displayInsetsController, - TabletopModeController pipTabletopController, - Optional<OneHandedController> oneHandedController, - @ShellMainThread ShellExecutor mainExecutor) { - return Optional.ofNullable(PipController.create( - context, shellInit, shellCommandHandler, shellController, - displayController, pipAnimationController, pipAppOpsListener, pipBoundsAlgorithm, - pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler, pipDisplayLayoutState, - pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer, - pipTransitionState, pipTouchHandler, pipTransitionController, - windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder, - displayInsetsController, pipTabletopController, oneHandedController, mainExecutor)); - } - - @WMSingleton - @Provides - static PipBoundsState providePipBoundsState(Context context, - PipSizeSpecHandler pipSizeSpecHandler, PipDisplayLayoutState pipDisplayLayoutState) { - return new PipBoundsState(context, pipSizeSpecHandler, pipDisplayLayoutState); - } - - @WMSingleton - @Provides - static PipSnapAlgorithm providePipSnapAlgorithm() { - return new PipSnapAlgorithm(); - } - - @WMSingleton - @Provides - static PhonePipKeepClearAlgorithm providePhonePipKeepClearAlgorithm(Context context) { - return new PhonePipKeepClearAlgorithm(context); - } - - @WMSingleton - @Provides - static PipSizeSpecHandler providePipSizeSpecHelper(Context context, - PipDisplayLayoutState pipDisplayLayoutState) { - return new PipSizeSpecHandler(context, pipDisplayLayoutState); - } - - @WMSingleton - @Provides - static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context, - PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm, - PhonePipKeepClearAlgorithm pipKeepClearAlgorithm, - PipSizeSpecHandler pipSizeSpecHandler) { - return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm, - pipKeepClearAlgorithm, pipSizeSpecHandler); - } - - // Handler is used by Icon.loadDrawableAsync - @WMSingleton - @Provides - static PhonePipMenuController providesPipPhoneMenuController(Context context, - PipBoundsState pipBoundsState, PipMediaController pipMediaController, - SystemWindows systemWindows, - Optional<SplitScreenController> splitScreenOptional, - PipUiEventLogger pipUiEventLogger, - @ShellMainThread ShellExecutor mainExecutor, - @ShellMainThread Handler mainHandler) { - return new PhonePipMenuController(context, pipBoundsState, pipMediaController, - systemWindows, splitScreenOptional, pipUiEventLogger, mainExecutor, mainHandler); - } - - @WMSingleton - @Provides - static PipTouchHandler providePipTouchHandler(Context context, - ShellInit shellInit, - PhonePipMenuController menuPhoneController, - PipBoundsAlgorithm pipBoundsAlgorithm, - PipBoundsState pipBoundsState, - PipSizeSpecHandler pipSizeSpecHandler, - PipTaskOrganizer pipTaskOrganizer, - PipMotionHelper pipMotionHelper, - FloatingContentCoordinator floatingContentCoordinator, - PipUiEventLogger pipUiEventLogger, - @ShellMainThread ShellExecutor mainExecutor) { - return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm, - pipBoundsState, pipSizeSpecHandler, pipTaskOrganizer, pipMotionHelper, - floatingContentCoordinator, pipUiEventLogger, mainExecutor); - } - - @WMSingleton - @Provides - static PipTransitionState providePipTransitionState() { - return new PipTransitionState(); - } - - @WMSingleton - @Provides - static PipTaskOrganizer providePipTaskOrganizer(Context context, - SyncTransactionQueue syncTransactionQueue, - PipTransitionState pipTransitionState, - PipBoundsState pipBoundsState, - PipDisplayLayoutState pipDisplayLayoutState, - PipBoundsAlgorithm pipBoundsAlgorithm, - PhonePipMenuController menuPhoneController, - PipAnimationController pipAnimationController, - PipSurfaceTransactionHelper pipSurfaceTransactionHelper, - PipTransitionController pipTransitionController, - PipParamsChangedForwarder pipParamsChangedForwarder, - Optional<SplitScreenController> splitScreenControllerOptional, - DisplayController displayController, - PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer, - @ShellMainThread ShellExecutor mainExecutor) { - return new PipTaskOrganizer(context, - syncTransactionQueue, pipTransitionState, pipBoundsState, pipDisplayLayoutState, - pipBoundsAlgorithm, menuPhoneController, pipAnimationController, - pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder, - splitScreenControllerOptional, displayController, pipUiEventLogger, - shellTaskOrganizer, mainExecutor); - } - - @WMSingleton - @Provides - static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper - pipSurfaceTransactionHelper) { - return new PipAnimationController(pipSurfaceTransactionHelper); - } - - @WMSingleton - @Provides - static PipTransitionController providePipTransitionController(Context context, - ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, Transitions transitions, - PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm, - PipBoundsState pipBoundsState, PipDisplayLayoutState pipDisplayLayoutState, - PipTransitionState pipTransitionState, PhonePipMenuController pipMenuController, - PipSurfaceTransactionHelper pipSurfaceTransactionHelper, - Optional<SplitScreenController> splitScreenOptional) { - return new PipTransition(context, shellInit, shellTaskOrganizer, transitions, - pipBoundsState, pipDisplayLayoutState, pipTransitionState, pipMenuController, - pipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper, - splitScreenOptional); - } - - @WMSingleton - @Provides - static PipAppOpsListener providePipAppOpsListener(Context context, - PipTouchHandler pipTouchHandler, - @ShellMainThread ShellExecutor mainExecutor) { - return new PipAppOpsListener(context, pipTouchHandler.getMotionHelper(), mainExecutor); - } - - @WMSingleton - @Provides - static PipMotionHelper providePipMotionHelper(Context context, - PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer, - PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm, - PipTransitionController pipTransitionController, - FloatingContentCoordinator floatingContentCoordinator) { - return new PipMotionHelper(context, pipBoundsState, pipTaskOrganizer, - menuController, pipSnapAlgorithm, pipTransitionController, - floatingContentCoordinator); - } - - @WMSingleton - @Provides - static PipParamsChangedForwarder providePipParamsChangedForwarder() { - return new PipParamsChangedForwarder(); - } - - // // Transitions // @@ -548,7 +342,7 @@ public abstract class WMShellModule { static DefaultMixedHandler provideDefaultMixedHandler( ShellInit shellInit, Optional<SplitScreenController> splitScreenOptional, - Optional<PipTouchHandler> pipTouchHandlerOptional, + @Nullable PipTransitionController pipTransitionController, Optional<RecentsTransitionHandler> recentsTransitionHandler, KeyguardTransitionHandler keyguardTransitionHandler, Optional<DesktopModeController> desktopModeController, @@ -556,8 +350,9 @@ public abstract class WMShellModule { Optional<UnfoldTransitionHandler> unfoldHandler, Transitions transitions) { return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional, - pipTouchHandlerOptional, recentsTransitionHandler, keyguardTransitionHandler, - desktopModeController, desktopTasksController, unfoldHandler); + pipTransitionController, recentsTransitionHandler, + keyguardTransitionHandler, desktopModeController, desktopTasksController, + unfoldHandler); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java new file mode 100644 index 000000000000..d972f48b92fd --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.dagger.pip; + +import android.annotation.Nullable; +import android.content.Context; +import android.os.Handler; + +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.WindowManagerShellWrapper; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.FloatingContentCoordinator; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.common.SystemWindows; +import com.android.wm.shell.common.TabletopModeController; +import com.android.wm.shell.common.TaskStackListenerImpl; +import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.dagger.WMShellBaseModule; +import com.android.wm.shell.dagger.WMSingleton; +import com.android.wm.shell.onehanded.OneHandedController; +import com.android.wm.shell.pip.Pip; +import com.android.wm.shell.pip.PipAnimationController; +import com.android.wm.shell.pip.PipAppOpsListener; +import com.android.wm.shell.pip.PipBoundsAlgorithm; +import com.android.wm.shell.pip.PipBoundsState; +import com.android.wm.shell.pip.PipDisplayLayoutState; +import com.android.wm.shell.pip.PipMediaController; +import com.android.wm.shell.pip.PipParamsChangedForwarder; +import com.android.wm.shell.pip.PipSnapAlgorithm; +import com.android.wm.shell.pip.PipSurfaceTransactionHelper; +import com.android.wm.shell.pip.PipTaskOrganizer; +import com.android.wm.shell.pip.PipTransition; +import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.pip.PipTransitionState; +import com.android.wm.shell.pip.PipUiEventLogger; +import com.android.wm.shell.pip.PipUtils; +import com.android.wm.shell.pip.phone.PhonePipKeepClearAlgorithm; +import com.android.wm.shell.pip.phone.PhonePipMenuController; +import com.android.wm.shell.pip.phone.PipController; +import com.android.wm.shell.pip.phone.PipMotionHelper; +import com.android.wm.shell.pip.phone.PipSizeSpecHandler; +import com.android.wm.shell.pip.phone.PipTouchHandler; +import com.android.wm.shell.splitscreen.SplitScreenController; +import com.android.wm.shell.sysui.ShellCommandHandler; +import com.android.wm.shell.sysui.ShellController; +import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.Transitions; + +import dagger.Module; +import dagger.Provides; + +import java.util.Optional; + +/** + * Provides dependencies from {@link com.android.wm.shell.pip}, this implementation is meant to be + * replaced by the sibling {@link Pip2Module}. + */ +@Module(includes = { + Pip1SharedModule.class, + WMShellBaseModule.class +}) +public abstract class Pip1Module { + @WMSingleton + @Provides + static Optional<Pip> providePip1(Context context, + ShellInit shellInit, + ShellCommandHandler shellCommandHandler, + ShellController shellController, + DisplayController displayController, + PipAnimationController pipAnimationController, + PipAppOpsListener pipAppOpsListener, + PipBoundsAlgorithm pipBoundsAlgorithm, + PhonePipKeepClearAlgorithm pipKeepClearAlgorithm, + PipBoundsState pipBoundsState, + PipSizeSpecHandler pipSizeSpecHandler, + PipDisplayLayoutState pipDisplayLayoutState, + PipMotionHelper pipMotionHelper, + PipMediaController pipMediaController, + PhonePipMenuController phonePipMenuController, + PipTaskOrganizer pipTaskOrganizer, + PipTransitionState pipTransitionState, + PipTouchHandler pipTouchHandler, + PipTransitionController pipTransitionController, + WindowManagerShellWrapper windowManagerShellWrapper, + TaskStackListenerImpl taskStackListener, + PipParamsChangedForwarder pipParamsChangedForwarder, + DisplayInsetsController displayInsetsController, + TabletopModeController pipTabletopController, + Optional<OneHandedController> oneHandedController, + @ShellMainThread ShellExecutor mainExecutor) { + if (PipUtils.isPip2ExperimentEnabled()) { + return Optional.empty(); + } else { + return Optional.ofNullable(PipController.create( + context, shellInit, shellCommandHandler, shellController, + displayController, pipAnimationController, pipAppOpsListener, + pipBoundsAlgorithm, + pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler, + pipDisplayLayoutState, + pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer, + pipTransitionState, pipTouchHandler, pipTransitionController, + windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder, + displayInsetsController, pipTabletopController, oneHandedController, + mainExecutor)); + } + } + + @WMSingleton + @Provides + static PipBoundsState providePipBoundsState(Context context, + PipSizeSpecHandler pipSizeSpecHandler, PipDisplayLayoutState pipDisplayLayoutState) { + return new PipBoundsState(context, pipSizeSpecHandler, pipDisplayLayoutState); + } + + @WMSingleton + @Provides + static PipSnapAlgorithm providePipSnapAlgorithm() { + return new PipSnapAlgorithm(); + } + + @WMSingleton + @Provides + static PhonePipKeepClearAlgorithm providePhonePipKeepClearAlgorithm(Context context) { + return new PhonePipKeepClearAlgorithm(context); + } + + @WMSingleton + @Provides + static PipSizeSpecHandler providePipSizeSpecHelper(Context context, + PipDisplayLayoutState pipDisplayLayoutState) { + return new PipSizeSpecHandler(context, pipDisplayLayoutState); + } + + @WMSingleton + @Provides + static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context, + PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm, + PhonePipKeepClearAlgorithm pipKeepClearAlgorithm, + PipSizeSpecHandler pipSizeSpecHandler) { + return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm, + pipKeepClearAlgorithm, pipSizeSpecHandler); + } + + // Handler is used by Icon.loadDrawableAsync + @WMSingleton + @Provides + static PhonePipMenuController providesPipPhoneMenuController(Context context, + PipBoundsState pipBoundsState, PipMediaController pipMediaController, + SystemWindows systemWindows, + Optional<SplitScreenController> splitScreenOptional, + PipUiEventLogger pipUiEventLogger, + @ShellMainThread ShellExecutor mainExecutor, + @ShellMainThread Handler mainHandler) { + return new PhonePipMenuController(context, pipBoundsState, pipMediaController, + systemWindows, splitScreenOptional, pipUiEventLogger, mainExecutor, mainHandler); + } + + @WMSingleton + @Provides + static PipTouchHandler providePipTouchHandler(Context context, + ShellInit shellInit, + PhonePipMenuController menuPhoneController, + PipBoundsAlgorithm pipBoundsAlgorithm, + PipBoundsState pipBoundsState, + PipSizeSpecHandler pipSizeSpecHandler, + PipTaskOrganizer pipTaskOrganizer, + PipMotionHelper pipMotionHelper, + FloatingContentCoordinator floatingContentCoordinator, + PipUiEventLogger pipUiEventLogger, + @ShellMainThread ShellExecutor mainExecutor) { + return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm, + pipBoundsState, pipSizeSpecHandler, pipTaskOrganizer, pipMotionHelper, + floatingContentCoordinator, pipUiEventLogger, mainExecutor); + } + + @WMSingleton + @Provides + static PipTransitionState providePipTransitionState() { + return new PipTransitionState(); + } + + @WMSingleton + @Provides + static PipTaskOrganizer providePipTaskOrganizer(Context context, + SyncTransactionQueue syncTransactionQueue, + PipTransitionState pipTransitionState, + PipBoundsState pipBoundsState, + PipDisplayLayoutState pipDisplayLayoutState, + PipBoundsAlgorithm pipBoundsAlgorithm, + PhonePipMenuController menuPhoneController, + PipAnimationController pipAnimationController, + PipSurfaceTransactionHelper pipSurfaceTransactionHelper, + PipTransitionController pipTransitionController, + PipParamsChangedForwarder pipParamsChangedForwarder, + Optional<SplitScreenController> splitScreenControllerOptional, + DisplayController displayController, + PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer, + @ShellMainThread ShellExecutor mainExecutor) { + return new PipTaskOrganizer(context, + syncTransactionQueue, pipTransitionState, pipBoundsState, pipDisplayLayoutState, + pipBoundsAlgorithm, menuPhoneController, pipAnimationController, + pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder, + splitScreenControllerOptional, displayController, pipUiEventLogger, + shellTaskOrganizer, mainExecutor); + } + + @WMSingleton + @Provides + static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper + pipSurfaceTransactionHelper) { + return new PipAnimationController(pipSurfaceTransactionHelper); + } + + @WMSingleton + @Provides + @Nullable + static PipTransition providePipTransition(Context context, + ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, Transitions transitions, + PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm, + PipBoundsState pipBoundsState, PipDisplayLayoutState pipDisplayLayoutState, + PipTransitionState pipTransitionState, PhonePipMenuController pipMenuController, + PipSurfaceTransactionHelper pipSurfaceTransactionHelper, + Optional<SplitScreenController> splitScreenOptional) { + return new PipTransition(context, shellInit, shellTaskOrganizer, transitions, + pipBoundsState, pipDisplayLayoutState, pipTransitionState, pipMenuController, + pipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper, + splitScreenOptional); + } + + @WMSingleton + @Provides + static PipAppOpsListener providePipAppOpsListener(Context context, + PipTouchHandler pipTouchHandler, + @ShellMainThread ShellExecutor mainExecutor) { + return new PipAppOpsListener(context, pipTouchHandler.getMotionHelper(), mainExecutor); + } + + @WMSingleton + @Provides + static PipMotionHelper providePipMotionHelper(Context context, + PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer, + PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm, + PipTransitionController pipTransitionController, + FloatingContentCoordinator floatingContentCoordinator) { + return new PipMotionHelper(context, pipBoundsState, pipTaskOrganizer, + menuController, pipSnapAlgorithm, pipTransitionController, + floatingContentCoordinator); + } + + @WMSingleton + @Provides + static PipParamsChangedForwarder providePipParamsChangedForwarder() { + return new PipParamsChangedForwarder(); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java new file mode 100644 index 000000000000..f29b3a35128d --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.dagger.pip; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Handler; + +import com.android.internal.logging.UiEventLogger; +import com.android.wm.shell.common.FloatingContentCoordinator; +import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.dagger.WMSingleton; +import com.android.wm.shell.pip.PipMediaController; +import com.android.wm.shell.pip.PipSurfaceTransactionHelper; +import com.android.wm.shell.pip.PipUiEventLogger; + +import dagger.Module; +import dagger.Provides; + +/** + * Provides shared dependencies from {@link com.android.wm.shell.pip}, this implementation is + * shared with {@link TvPipModule} and possibly other form factors. + */ +@Module +public abstract class Pip1SharedModule { + @WMSingleton + @Provides + static FloatingContentCoordinator provideFloatingContentCoordinator() { + return new FloatingContentCoordinator(); + } + + // Needs handler for registering broadcast receivers + @WMSingleton + @Provides + static PipMediaController providePipMediaController(Context context, + @ShellMainThread Handler mainHandler) { + return new PipMediaController(context, mainHandler); + } + + @WMSingleton + @Provides + static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper(Context context) { + return new PipSurfaceTransactionHelper(context); + } + + @WMSingleton + @Provides + static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger, + PackageManager packageManager) { + return new PipUiEventLogger(uiEventLogger, packageManager); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java new file mode 100644 index 000000000000..c7c6e8a14278 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.dagger.pip; + +import android.annotation.Nullable; + +import com.android.wm.shell.dagger.WMSingleton; +import com.android.wm.shell.pip2.PipTransition; + +import dagger.Module; +import dagger.Provides; + +/** + * Provides dependencies from {@link com.android.wm.shell.pip2}, this implementation is meant to be + * the successor of its sibling {@link Pip1SharedModule}. + */ +@Module +public abstract class Pip2Module { + @WMSingleton + @Provides + @Nullable + static PipTransition providePipTransition() { + return null; + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java new file mode 100644 index 000000000000..2ded4a32a9f3 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.dagger.pip; + +import android.annotation.Nullable; + +import com.android.wm.shell.dagger.WMSingleton; +import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.pip.PipUtils; + +import dagger.Module; +import dagger.Provides; + +/** + * Provides dependencies for external components / modules reference PiP and extracts away the + * selection of legacy and new PiP implementation. + */ +@Module(includes = { + Pip1Module.class, + Pip2Module.class +}) +public abstract class PipModule { + @WMSingleton + @Provides + @Nullable + static PipTransitionController providePipTransitionController( + com.android.wm.shell.pip.PipTransition legacyPipTransition, + com.android.wm.shell.pip2.PipTransition newPipTransition) { + return PipUtils.isPip2ExperimentEnabled() ? newPipTransition : legacyPipTransition; + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java index 14daae03e6b5..360bf8b70fca 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.dagger; +package com.android.wm.shell.dagger.pip; import android.content.Context; import android.os.Handler; @@ -28,6 +28,8 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.dagger.WMShellBaseModule; +import com.android.wm.shell.dagger.WMSingleton; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipAppOpsListener; @@ -62,7 +64,9 @@ import java.util.Optional; /** * Provides TV specific dependencies for Pip. */ -@Module(includes = {WMShellBaseModule.class}) +@Module(includes = { + WMShellBaseModule.class, + Pip1SharedModule.class}) public abstract class TvPipModule { @WMSingleton @Provides diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java index f34d2a827e69..a9aa6badcfe2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java @@ -67,4 +67,11 @@ public interface Pip { * view hierarchy or destroyed. */ default void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { } + + /** + * @return {@link PipTransitionController} instance. + */ + default PipTransitionController getPipTransitionController() { + return null; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java index 2590cab9ff2e..ddffb5bdacde 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java @@ -150,13 +150,15 @@ public class PipMediaController { mContext = context; mMainHandler = mainHandler; mHandlerExecutor = new HandlerExecutor(mMainHandler); - IntentFilter mediaControlFilter = new IntentFilter(); - mediaControlFilter.addAction(ACTION_PLAY); - mediaControlFilter.addAction(ACTION_PAUSE); - mediaControlFilter.addAction(ACTION_NEXT); - mediaControlFilter.addAction(ACTION_PREV); - mContext.registerReceiverForAllUsers(mMediaActionReceiver, mediaControlFilter, - SYSTEMUI_PERMISSION, mainHandler, Context.RECEIVER_EXPORTED); + if (!PipUtils.isPip2ExperimentEnabled()) { + IntentFilter mediaControlFilter = new IntentFilter(); + mediaControlFilter.addAction(ACTION_PLAY); + mediaControlFilter.addAction(ACTION_PAUSE); + mediaControlFilter.addAction(ACTION_NEXT); + mediaControlFilter.addAction(ACTION_PREV); + mContext.registerReceiverForAllUsers(mMediaActionReceiver, mediaControlFilter, + SYSTEMUI_PERMISSION, mainHandler, Context.RECEIVER_EXPORTED); + } // Creates the standard media buttons that we may show. mPauseAction = getDefaultRemoteAction(R.string.pip_pause, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 08da4857a0b0..208f9b7432bf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -364,13 +364,15 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mMainExecutor = mainExecutor; // TODO: Can be removed once wm components are created on the shell-main thread - mMainExecutor.execute(() -> { - mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_PIP); - }); - mTaskOrganizer.addFocusListener(this); - mPipTransitionController.setPipOrganizer(this); - displayController.addDisplayWindowListener(this); - pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback); + if (!PipUtils.isPip2ExperimentEnabled()) { + mMainExecutor.execute(() -> { + mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_PIP); + }); + mTaskOrganizer.addFocusListener(this); + mPipTransitionController.setPipOrganizer(this); + displayController.addDisplayWindowListener(this); + pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback); + } } public PipTransitionController getTransitionController() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java index 63627938ec87..0f74f9e323a5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java @@ -142,8 +142,10 @@ public abstract class PipTransitionController implements Transitions.TransitionH mPipBoundsAlgorithm = pipBoundsAlgorithm; mPipAnimationController = pipAnimationController; mTransitions = transitions; - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - shellInit.addInitCallback(this::onInit, this); + if (!PipUtils.isPip2ExperimentEnabled()) { + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + shellInit.addInitCallback(this::onInit, this); + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java index 8b98790d3499..3cd9848d5686 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java @@ -27,6 +27,7 @@ import android.app.RemoteAction; import android.content.ComponentName; import android.content.Context; import android.os.RemoteException; +import android.os.SystemProperties; import android.util.DisplayMetrics; import android.util.Log; import android.util.Pair; @@ -46,6 +47,9 @@ public class PipUtils { // Minimum difference between two floats (e.g. aspect ratios) to consider them not equal. private static final double EPSILON = 1e-7; + private static final String ENABLE_PIP2_IMPLEMENTATION = + "persist.wm.debug.enable_pip2_implementation"; + /** * @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack. * The component name may be null if no such activity exists. @@ -134,4 +138,8 @@ public class PipUtils { return null; } } + + public static boolean isPip2ExperimentEnabled() { + return SystemProperties.getBoolean(ENABLE_PIP2_IMPLEMENTATION, false); + } } 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 51e7be0f8a24..5b9e47fb5188 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 @@ -461,8 +461,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb Optional<OneHandedController> oneHandedController, ShellExecutor mainExecutor ) { - - mContext = context; mShellCommandHandler = shellCommandHandler; mShellController = shellController; @@ -493,7 +491,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb mDisplayInsetsController = displayInsetsController; mTabletopModeController = tabletopModeController; - shellInit.addInitCallback(this::onInit, this); + if (!PipUtils.isPip2ExperimentEnabled()) { + shellInit.addInitCallback(this::onInit, this); + } } private void onInit() { @@ -1258,6 +1258,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb PipController.this.showPictureInPictureMenu(); }); } + + @Override + public PipTransitionController getPipTransitionController() { + return mPipTransitionController; + } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 500094335258..372ade333218 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -57,6 +57,7 @@ import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipUiEventLogger; +import com.android.wm.shell.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.sysui.ShellInit; @@ -228,7 +229,9 @@ public class PipTouchHandler { // TODO(b/181599115): This should really be initializes as part of the pip controller, but // until all PIP implementations derive from the controller, just initialize the touch handler // if it is needed - shellInit.addInitCallback(this::onInit, this); + if (!PipUtils.isPip2ExperimentEnabled()) { + shellInit.addInitCallback(this::onInit, this); + } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java new file mode 100644 index 000000000000..8ab85d0829df --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.pip2; + +import android.annotation.NonNull; + +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.pip.PipAnimationController; +import com.android.wm.shell.pip.PipBoundsAlgorithm; +import com.android.wm.shell.pip.PipBoundsState; +import com.android.wm.shell.pip.PipMenuController; +import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.Transitions; + +/** Placeholder, for demonstrate purpose only. */ +public abstract class PipTransition extends PipTransitionController { + public PipTransition( + @NonNull ShellInit shellInit, + @NonNull ShellTaskOrganizer shellTaskOrganizer, + @NonNull Transitions transitions, + PipBoundsState pipBoundsState, + PipMenuController pipMenuController, + PipBoundsAlgorithm pipBoundsAlgorithm, + PipAnimationController pipAnimationController) { + super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController, + pipBoundsAlgorithm, pipAnimationController); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/README.md b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/README.md new file mode 100644 index 000000000000..415ea8f959cc --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/README.md @@ -0,0 +1,3 @@ +# PLACEHOLDER + +This is meant to be the doc for the pip2 package.
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index c5c22ded8b71..052af3af98cc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -49,7 +49,6 @@ import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.pip.PipTransitionController; -import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentsTransitionHandler; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -147,7 +146,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, public DefaultMixedHandler(@NonNull ShellInit shellInit, @NonNull Transitions player, Optional<SplitScreenController> splitScreenControllerOptional, - Optional<PipTouchHandler> pipTouchHandlerOptional, + @Nullable PipTransitionController pipTransitionController, Optional<RecentsTransitionHandler> recentsHandlerOptional, KeyguardTransitionHandler keyguardHandler, Optional<DesktopModeController> desktopModeControllerOptional, @@ -155,11 +154,12 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, Optional<UnfoldTransitionHandler> unfoldHandler) { mPlayer = player; mKeyguardHandler = keyguardHandler; - if (Transitions.ENABLE_SHELL_TRANSITIONS && pipTouchHandlerOptional.isPresent() + if (Transitions.ENABLE_SHELL_TRANSITIONS + && pipTransitionController != null && splitScreenControllerOptional.isPresent()) { // Add after dependencies because it is higher priority shellInit.addInitCallback(() -> { - mPipHandler = pipTouchHandlerOptional.get().getTransitionHandler(); + mPipHandler = pipTransitionController; mSplitHandler = splitScreenControllerOptional.get().getTransitionHandler(); mPlayer.addHandler(this); if (mSplitHandler != null) { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt index 2993af7df600..13aa758ff119 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.FlakyTest -import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest @@ -68,7 +67,6 @@ class MovePipDownOnShelfHeightChange(flicker: LegacyFlickerTest) : /** Checks that the visible region of [pipApp] layer always moves down during the animation. */ @FlakyTest(bugId = 292813143) - @Presubmit @Test fun pipLayerMovesDown() = pipLayerMoves(Direction.DOWN) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt index 7085d559f3f2..0c6fc5636a5b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt @@ -16,7 +16,7 @@ package com.android.wm.shell.flicker.pip -import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.Presubmit import android.platform.test.annotations.RequiresDevice import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -62,7 +62,7 @@ class PipDragTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { } } - @Postsubmit + @Presubmit @Test fun pipLayerMovesAwayFromEdge() { flicker.assertLayers { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt index 2b87766daab3..de64f78a31eb 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt @@ -17,7 +17,7 @@ package com.android.wm.shell.flicker.pip import android.graphics.Rect -import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.Presubmit import android.tools.common.Rotation import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -80,7 +80,7 @@ class PipDragThenSnapTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { /** * Checks that the visible region area of [pipApp] moves to closest edge during the animation. */ - @Postsubmit + @Presubmit @Test fun pipLayerMovesToClosestEdge() { flicker.assertLayers { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt index adc5ee32cdd3..0295741bc441 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt @@ -16,8 +16,7 @@ package com.android.wm.shell.flicker.pip -import android.platform.test.annotations.FlakyTest -import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.Presubmit import android.tools.common.Rotation import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -35,14 +34,13 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@FlakyTest(bugId = 270677470) class PipPinchInTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) } } /** Checks that the visible region area of [pipApp] always decreases during the animation. */ - @Postsubmit + @Presubmit @Test fun pipLayerAreaDecreases() { flicker.assertLayers { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt index a0023c0d969e..610cedefe594 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt @@ -22,7 +22,9 @@ import android.platform.test.rule.PressHomeRule import android.platform.test.rule.UnlockScreenRule import android.tools.common.NavBar import android.tools.common.Rotation +import android.tools.device.apphelpers.MessagingAppHelper import android.tools.device.flicker.rules.ChangeDisplayOrientationRule +import android.tools.device.flicker.rules.LaunchAppRule import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule import androidx.test.platform.app.InstrumentationRegistry import org.junit.rules.RuleChain @@ -35,6 +37,9 @@ object Utils { .around( NavigationModeRule(navigationMode.value, /* changeNavigationModeAfterTest */ false) ) + .around( + LaunchAppRule(MessagingAppHelper(instrumentation), clearCacheAfterParsing = false) + ) .around(RemoveAllTasksButHomeRule()) .around( ChangeDisplayOrientationRule( diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index ad09067e6309..5ffec34be397 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -931,11 +931,11 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal( .entry = *entry, .config = *best_config, .type_flags = type_flags, + .dynamic_ref_table = package_group.dynamic_ref_table.get(), .package_name = &best_package->GetPackageName(), .type_string_ref = StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1), .entry_string_ref = StringPoolRef(best_package->GetKeyStringPool(), (*best_entry_verified)->key()), - .dynamic_ref_table = package_group.dynamic_ref_table.get(), }; } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt index 47bf85d8417b..3e96994c2de6 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt @@ -44,7 +44,7 @@ fun ApplicationInfo.getStorageSize(): State<String> { } } -private fun ApplicationInfo.calculateSizeBytes(context: Context): Long? { +fun ApplicationInfo.calculateSizeBytes(context: Context): Long? { val storageStatsManager = context.storageStatsManager return try { val stats = storageStatsManager.queryStatsForPackage(storageUuid, packageName, userHandle) diff --git a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java index 997d1f432a82..3011d3164290 100644 --- a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java +++ b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java @@ -80,6 +80,8 @@ public class CreateUserDialogController { private Bitmap mSavedPhoto; private String mSavedName; private Drawable mSavedDrawable; + private String mUserName; + private Drawable mNewUserIcon; private Boolean mIsAdmin; private Dialog mUserCreationDialog; private View mGrantAdminView; @@ -89,6 +91,7 @@ public class CreateUserDialogController { private ActivityStarter mActivityStarter; private boolean mWaitingForActivityResult; private NewUserData mSuccessCallback; + private Runnable mCancelCallback; private final String mFileAuthority; @@ -113,6 +116,7 @@ public class CreateUserDialogController { mEditUserInfoView = null; mUserNameView = null; mSuccessCallback = null; + mCancelCallback = null; mCurrentState = INITIAL_DIALOG; } @@ -184,14 +188,12 @@ public class CreateUserDialogController { mActivity = activity; mCustomDialogHelper = new CustomDialogHelper(activity); mSuccessCallback = successCallback; + mCancelCallback = cancelCallback; mActivityStarter = activityStarter; addCustomViews(isMultipleAdminEnabled); mUserCreationDialog = mCustomDialogHelper.getDialog(); updateLayout(); - mUserCreationDialog.setOnDismissListener(view -> { - cancelCallback.run(); - clear(); - }); + mUserCreationDialog.setOnDismissListener(view -> finish()); mCustomDialogHelper.setMessagePadding(MESSAGE_PADDING); mUserCreationDialog.setCanceledOnTouchOutside(true); return mUserCreationDialog; @@ -269,20 +271,14 @@ public class CreateUserDialogController { mGrantAdminView.setVisibility(View.GONE); break; case CREATE_USER_AND_CLOSE: - Drawable newUserIcon = mEditUserPhotoController != null + mNewUserIcon = mEditUserPhotoController != null ? mEditUserPhotoController.getNewUserPhotoDrawable() : null; String newName = mUserNameView.getText().toString().trim(); String defaultName = mActivity.getString(R.string.user_new_user_name); - String userName = !newName.isEmpty() ? newName : defaultName; - - if (mSuccessCallback != null) { - mSuccessCallback.onSuccess(userName, newUserIcon, - Boolean.TRUE.equals(mIsAdmin)); - } + mUserName = !newName.isEmpty() ? newName : defaultName; mCustomDialogHelper.getDialog().dismiss(); - clear(); break; case EXIT_DIALOG: mCustomDialogHelper.getDialog().dismiss(); @@ -384,4 +380,20 @@ public class CreateUserDialogController { public boolean isActive() { return mCustomDialogHelper != null && mCustomDialogHelper.getDialog() != null; } + + /** + * Runs callback and clears saved values after dialog is dismissed. + */ + public void finish() { + if (mCurrentState == CREATE_USER_AND_CLOSE) { + if (mSuccessCallback != null) { + mSuccessCallback.onSuccess(mUserName, mNewUserIcon, Boolean.TRUE.equals(mIsAdmin)); + } + } else { + if (mCancelCallback != null) { + mCancelCallback.run(); + } + } + clear(); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java index b53807744516..66a2ea69559b 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java @@ -212,6 +212,7 @@ public class CreateUserDialogControllerTest { next.performClick(); verify(successCallback, times(1)) .onSuccess(expectedNewName, null, true); + verifyNoInteractions(cancelCallback); } @Test @@ -233,6 +234,7 @@ public class CreateUserDialogControllerTest { next.performClick(); verify(successCallback, times(1)) .onSuccess(expectedNewName, null, false); + verifyNoInteractions(cancelCallback); } private class TestCreateUserDialogController extends CreateUserDialogController { diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING index c59b0f9c8e9a..1bd83551886a 100644 --- a/packages/SystemUI/TEST_MAPPING +++ b/packages/SystemUI/TEST_MAPPING @@ -35,6 +35,9 @@ }, { "exclude-annotation": "android.platform.test.annotations.FlakyTest" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" } ] } @@ -139,6 +142,9 @@ }, { "exclude-annotation": "android.platform.test.annotations.FlakyTest" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" } ] } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java index b33c5449c1eb..a5e5aaa499f1 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java @@ -26,6 +26,7 @@ import com.android.systemui.plugins.annotations.DependsOn; import com.android.systemui.plugins.annotations.ProvidesInterface; import java.util.ArrayList; +import java.util.Collection; /** * Dispatches events to {@link DarkReceiver}s about changes in darkness, tint area and dark @@ -78,7 +79,7 @@ public interface DarkIconDispatcher { * @return the tint to apply to view depending on the desired tint color and * the screen tintArea in which to apply that tint */ - static int getTint(ArrayList<Rect> tintAreas, View view, int color) { + static int getTint(Collection<Rect> tintAreas, View view, int color) { if (isInAreas(tintAreas, view)) { return color; } else { @@ -90,7 +91,7 @@ public interface DarkIconDispatcher { * @return true if more than half of the view area are in any of the given * areas, false otherwise */ - static boolean isInAreas(ArrayList<Rect> areas, View view) { + static boolean isInAreas(Collection<Rect> areas, View view) { if (areas.isEmpty()) { return true; } diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index ab754985e11d..9f4fc396ae55 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -26,6 +26,8 @@ <color name="qs_detail_button_white">#B3FFFFFF</color><!-- 70% white --> <color name="status_bar_clock_color">#FFFFFFFF</color> <color name="qs_tile_disabled_color">#9E9E9E</color> <!-- 38% black --> + <color name="status_bar_icons_hover_color_light">#38FFFFFF</color> <!-- 22% white --> + <color name="status_bar_icons_hover_color_dark">#38000000</color> <!-- 22% black --> <!-- The color of the background in the separated list of the Global Actions menu --> <color name="global_actions_separated_background">#F5F5F5</color> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 5a15dcec5223..9588498dddb3 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -475,6 +475,8 @@ <!-- Margin start of the system icons super container --> <dimen name="system_icons_super_container_margin_start">16dp</dimen> + <dimen name="status_icons_hover_state_background_radius">16dp</dimen> + <!-- Width for the notification panel and related windows --> <dimen name="match_parent">-1px</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 983b09f957ae..ee9b132facbe 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -439,6 +439,8 @@ <string name="face_reenroll_failure_dialog_content">Couldn\u2019t set up face unlock. Go to Settings to try again.</string> <!-- Message shown when the system-provided fingerprint dialog is shown, asking for authentication --> <string name="fingerprint_dialog_touch_sensor">Touch the fingerprint sensor</string> + <!-- Content description after successful auth when confirmation required --> + <string name="fingerprint_dialog_authenticated_confirmation">Press the unlock icon to continue</string> <!-- Message shown to inform the user a face cannot be recognized and fingerprint should instead be used.[CHAR LIMIT=50] --> <string name="fingerprint_dialog_use_fingerprint_instead">Can\u2019t recognize face. Use fingerprint instead.</string> <!-- Message shown to inform the user a face cannot be recognized and fingerprint should instead be used.[CHAR LIMIT=50] --> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 5833d98f04b2..48442a5f527e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -3563,6 +3563,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab */ @VisibleForTesting void handleUserSwitching(int userId, CountDownLatch latch) { + mLogger.logUserSwitching(userId, "from UserTracker"); Assert.isMainThread(); clearBiometricRecognized(); boolean trustUsuallyManaged = mTrustManager.isTrustUsuallyManaged(userId); @@ -3583,6 +3584,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab */ @VisibleForTesting void handleUserSwitchComplete(int userId) { + mLogger.logUserSwitchComplete(userId, "from UserTracker"); Assert.isMainThread(); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); @@ -4036,6 +4038,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @AnyThread public void setSwitchingUser(boolean switching) { + if (switching) { + mLogger.logUserSwitching(getCurrentUser(), "from setSwitchingUser"); + } else { + mLogger.logUserSwitchComplete(getCurrentUser(), "from setSwitchingUser"); + } mSwitchingUser = switching; // Since this comes in on a binder thread, we need to post it first mHandler.post(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt index 59e0cadec9b2..2dfb370bb382 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt @@ -602,10 +602,10 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { fun allowFingerprintOnCurrentOccludingActivityChanged(allow: Boolean) { logBuffer.log( - TAG, - VERBOSE, - { bool1 = allow }, - { "allowFingerprintOnCurrentOccludingActivityChanged: $bool1" } + TAG, + VERBOSE, + { bool1 = allow }, + { "allowFingerprintOnCurrentOccludingActivityChanged: $bool1" } ) } @@ -727,4 +727,28 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { { "notifying about enrollments changed: $str1" } ) } + + fun logUserSwitching(userId: Int, context: String) { + logBuffer.log( + TAG, + DEBUG, + { + int1 = userId + str1 = context + }, + { "userCurrentlySwitching: $str1, userId: $int1" } + ) + } + + fun logUserSwitchComplete(userId: Int, context: String) { + logBuffer.log( + TAG, + DEBUG, + { + int1 = userId + str1 = context + }, + { "userSwitchComplete: $str1, userId: $int1" } + ) + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt index 9807b9e6f1b3..a1b15f4498a7 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt @@ -142,13 +142,18 @@ open class AuthBiometricFingerprintIconController( STATE_IDLE, STATE_AUTHENTICATING_ANIMATING_IN, STATE_AUTHENTICATING, - STATE_PENDING_CONFIRMATION, STATE_AUTHENTICATED -> if (isSideFps) { R.string.security_settings_sfps_enroll_find_sensor_message } else { R.string.fingerprint_dialog_touch_sensor } + STATE_PENDING_CONFIRMATION -> + if (isSideFps) { + R.string.security_settings_sfps_enroll_find_sensor_message + } else { + R.string.fingerprint_dialog_authenticated_confirmation + } STATE_ERROR, STATE_HELP -> R.string.biometric_dialog_try_again else -> null diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt index 3eb58bba1ca4..ec76f433b23b 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt @@ -38,6 +38,7 @@ import androidx.core.view.accessibility.AccessibilityNodeInfoCompat import androidx.recyclerview.widget.RecyclerView import com.android.systemui.R import com.android.systemui.controls.ControlInterface +import com.android.systemui.controls.ui.CanUseIconPredicate import com.android.systemui.controls.ui.RenderInfo private typealias ModelFavoriteChanger = (String, Boolean) -> Unit @@ -51,7 +52,8 @@ private typealias ModelFavoriteChanger = (String, Boolean) -> Unit * @property elevation elevation of each control view */ class ControlAdapter( - private val elevation: Float + private val elevation: Float, + private val currentUserId: Int, ) : RecyclerView.Adapter<Holder>() { companion object { @@ -107,7 +109,8 @@ class ControlAdapter( background = parent.context.getDrawable( R.drawable.control_background_ripple) }, - model?.moveHelper // Indicates that position information is needed + currentUserId, + model?.moveHelper, // Indicates that position information is needed ) { id, favorite -> model?.changeFavoriteStatus(id, favorite) } @@ -212,8 +215,9 @@ private class ZoneHolder(view: View) : Holder(view) { */ internal class ControlHolder( view: View, + currentUserId: Int, val moveHelper: ControlsModel.MoveHelper?, - val favoriteCallback: ModelFavoriteChanger + val favoriteCallback: ModelFavoriteChanger, ) : Holder(view) { private val favoriteStateDescription = itemView.context.getString(R.string.accessibility_control_favorite) @@ -228,6 +232,7 @@ internal class ControlHolder( visibility = View.VISIBLE } + private val canUseIconPredicate = CanUseIconPredicate(currentUserId) private val accessibilityDelegate = ControlHolderAccessibilityDelegate( this::stateDescription, this::getLayoutPosition, @@ -287,7 +292,9 @@ internal class ControlHolder( val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme()) icon.imageTintList = null - ci.customIcon?.let { + ci.customIcon + ?.takeIf(canUseIconPredicate) + ?.let { icon.setImageIcon(it) } ?: run { icon.setImageDrawable(ri.icon) diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt index 8e41974b9acc..b387e4a66bd2 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt @@ -250,7 +250,7 @@ open class ControlsEditingActivity @Inject constructor( val elevation = resources.getFloat(R.dimen.control_card_elevation) val recyclerView = requireViewById<RecyclerView>(R.id.list) recyclerView.alpha = 0.0f - val adapter = ControlAdapter(elevation).apply { + val adapter = ControlAdapter(elevation, userTracker.userId).apply { registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { var hasAnimated = false override fun onChanged() { diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt index d3aa449b9cad..59fa7f53fc17 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -197,7 +197,7 @@ open class ControlsFavoritingActivity @Inject constructor( } executor.execute { - structurePager.adapter = StructureAdapter(listOfStructures) + structurePager.adapter = StructureAdapter(listOfStructures, userTracker.userId) structurePager.setCurrentItem(structureIndex) if (error) { statusText.text = resources.getString(R.string.controls_favorite_load_error, @@ -243,7 +243,7 @@ open class ControlsFavoritingActivity @Inject constructor( structurePager.alpha = 0.0f pageIndicator.alpha = 0.0f structurePager.apply { - adapter = StructureAdapter(emptyList()) + adapter = StructureAdapter(emptyList(), userTracker.userId) registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { super.onPageSelected(position) diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt index 747bcbe1c229..5977d379acde 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt @@ -24,13 +24,15 @@ import androidx.recyclerview.widget.RecyclerView import com.android.systemui.R class StructureAdapter( - private val models: List<StructureContainer> + private val models: List<StructureContainer>, + private val currentUserId: Int, ) : RecyclerView.Adapter<StructureAdapter.StructureHolder>() { override fun onCreateViewHolder(parent: ViewGroup, p1: Int): StructureHolder { val layoutInflater = LayoutInflater.from(parent.context) return StructureHolder( - layoutInflater.inflate(R.layout.controls_structure_page, parent, false) + layoutInflater.inflate(R.layout.controls_structure_page, parent, false), + currentUserId, ) } @@ -40,7 +42,8 @@ class StructureAdapter( holder.bind(models[index].model) } - class StructureHolder(view: View) : RecyclerView.ViewHolder(view) { + class StructureHolder(view: View, currentUserId: Int) : + RecyclerView.ViewHolder(view) { private val recyclerView: RecyclerView private val controlAdapter: ControlAdapter @@ -48,7 +51,7 @@ class StructureAdapter( init { recyclerView = itemView.requireViewById<RecyclerView>(R.id.listAll) val elevation = itemView.context.resources.getFloat(R.dimen.control_card_elevation) - controlAdapter = ControlAdapter(elevation) + controlAdapter = ControlAdapter(elevation, currentUserId) setUpRecyclerView() } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/CanUseIconPredicate.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/CanUseIconPredicate.kt new file mode 100644 index 000000000000..61c21237144d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/CanUseIconPredicate.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.ui + +import android.content.ContentProvider +import android.graphics.drawable.Icon + +class CanUseIconPredicate(private val currentUserId: Int) : (Icon) -> Boolean { + + override fun invoke(icon: Icon): Boolean = + if (icon.type == Icon.TYPE_URI || icon.type == Icon.TYPE_URI_ADAPTIVE_BITMAP) { + ContentProvider.getUserIdFromUri(icon.uri, currentUserId) == currentUserId + } else { + true + } +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt index e6361f46c8ad..c04bc8792223 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt @@ -68,7 +68,8 @@ class ControlViewHolder( val bgExecutor: DelayableExecutor, val controlActionCoordinator: ControlActionCoordinator, val controlsMetricsLogger: ControlsMetricsLogger, - val uid: Int + val uid: Int, + val currentUserId: Int, ) { companion object { @@ -85,29 +86,9 @@ class ControlViewHolder( private val ATTR_DISABLED = intArrayOf(-android.R.attr.state_enabled) const val MIN_LEVEL = 0 const val MAX_LEVEL = 10000 - - fun findBehaviorClass( - status: Int, - template: ControlTemplate, - deviceType: Int - ): Supplier<out Behavior> { - return when { - status != Control.STATUS_OK -> Supplier { StatusBehavior() } - template == ControlTemplate.NO_TEMPLATE -> Supplier { TouchBehavior() } - template is ThumbnailTemplate -> Supplier { ThumbnailBehavior() } - - // Required for legacy support, or where cameras do not use the new template - deviceType == DeviceTypes.TYPE_CAMERA -> Supplier { TouchBehavior() } - template is ToggleTemplate -> Supplier { ToggleBehavior() } - template is StatelessTemplate -> Supplier { TouchBehavior() } - template is ToggleRangeTemplate -> Supplier { ToggleRangeBehavior() } - template is RangeTemplate -> Supplier { ToggleRangeBehavior() } - template is TemperatureControlTemplate -> Supplier { TemperatureControlBehavior() } - else -> Supplier { DefaultBehavior() } - } - } } + private val canUseIconPredicate = CanUseIconPredicate(currentUserId) private val toggleBackgroundIntensity: Float = layout.context.resources .getFraction(R.fraction.controls_toggle_bg_intensity, 1, 1) private var stateAnimator: ValueAnimator? = null @@ -147,6 +128,27 @@ class ControlViewHolder( status.setSelected(true) } + fun findBehaviorClass( + status: Int, + template: ControlTemplate, + deviceType: Int + ): Supplier<out Behavior> { + return when { + status != Control.STATUS_OK -> Supplier { StatusBehavior() } + template == ControlTemplate.NO_TEMPLATE -> Supplier { TouchBehavior() } + template is ThumbnailTemplate -> Supplier { ThumbnailBehavior(currentUserId) } + + // Required for legacy support, or where cameras do not use the new template + deviceType == DeviceTypes.TYPE_CAMERA -> Supplier { TouchBehavior() } + template is ToggleTemplate -> Supplier { ToggleBehavior() } + template is StatelessTemplate -> Supplier { TouchBehavior() } + template is ToggleRangeTemplate -> Supplier { ToggleRangeBehavior() } + template is RangeTemplate -> Supplier { ToggleRangeBehavior() } + template is TemperatureControlTemplate -> Supplier { TemperatureControlBehavior() } + else -> Supplier { DefaultBehavior() } + } + } + fun bindData(cws: ControlWithState, isLocked: Boolean) { // If an interaction is in progress, the update may visually interfere with the action the // action the user wants to make. Don't apply the update, and instead assume a new update @@ -473,7 +475,9 @@ class ControlViewHolder( status.setTextColor(color) - control?.getCustomIcon()?.let { + control?.customIcon + ?.takeIf(canUseIconPredicate) + ?.let { icon.setImageIcon(it) icon.imageTintList = it.tintList } ?: run { diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index 64d4583d6de2..25eae200e950 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -690,7 +690,8 @@ class ControlsUiControllerImpl @Inject constructor ( bgExecutor, controlActionCoordinator, controlsMetricsLogger, - selected.uid + selected.uid, + controlsController.get().currentUserId, ) cvh.bindData(it, false /* isLocked, will be ignored on initial load */) controlViewsById.put(key, cvh) diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt index a7dc09bb17e5..39d69704d817 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt @@ -63,7 +63,7 @@ class TemperatureControlBehavior : Behavior { // interactions (touch, range) subBehavior = cvh.bindBehavior( subBehavior, - ControlViewHolder.findBehaviorClass( + cvh.findBehaviorClass( control.status, subTemplate, control.deviceType diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt index c2168aa8d9d9..0b57e792f9f7 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt @@ -33,7 +33,7 @@ import com.android.systemui.controls.ui.ControlViewHolder.Companion.MIN_LEVEL * Supports display of static images on the background of the tile. When marked active, the title * and subtitle will not be visible. To be used with {@link Thumbnailtemplate} only. */ -class ThumbnailBehavior : Behavior { +class ThumbnailBehavior(currentUserId: Int) : Behavior { lateinit var template: ThumbnailTemplate lateinit var control: Control lateinit var cvh: ControlViewHolder @@ -42,6 +42,7 @@ class ThumbnailBehavior : Behavior { private var shadowRadius: Float = 0f private var shadowColor: Int = 0 + private val canUseIconPredicate = CanUseIconPredicate(currentUserId) private val enabled: Boolean get() = template.isActive() @@ -80,11 +81,16 @@ class ThumbnailBehavior : Behavior { cvh.status.setShadowLayer(shadowOffsetX, shadowOffsetY, shadowRadius, shadowColor) cvh.bgExecutor.execute { - val drawable = template.getThumbnail().loadDrawable(cvh.context) + val drawable = template.thumbnail + ?.takeIf(canUseIconPredicate) + ?.loadDrawable(cvh.context) cvh.uiExecutor.execute { val radius = cvh.context.getResources() .getDimensionPixelSize(R.dimen.control_corner_radius).toFloat() - clipLayer.setDrawable(CornerDrawable(drawable, radius)) + // TODO(b/290037843): Add a placeholder + drawable?.let { + clipLayer.drawable = CornerDrawable(it, radius) + } clipLayer.setColorFilter(BlendModeColorFilter(cvh.context.resources .getColor(R.color.control_thumbnail_tint), BlendMode.LUMINOSITY)) cvh.applyRenderInfo(enabled, colorOffset) diff --git a/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java b/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java index a3e26b881a3b..d727a70de377 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java @@ -28,6 +28,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.phone.ActivityStarterImpl; import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl; +import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher; import com.android.systemui.volume.VolumeDialogControllerImpl; import dagger.Binds; @@ -49,6 +50,10 @@ public abstract class PluginModule { @Binds abstract DarkIconDispatcher provideDarkIconDispatcher(DarkIconDispatcherImpl controllerImpl); + @Binds + abstract SysuiDarkIconDispatcher provideSysuiDarkIconDispatcher( + DarkIconDispatcherImpl controllerImpl); + /** */ @Binds abstract FalsingManager provideFalsingManager(FalsingManagerProxy falsingManagerImpl); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt index c849b8495a26..508f71a2c4a5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt @@ -20,6 +20,7 @@ package com.android.systemui.keyguard.domain.interactor import android.content.res.Resources import android.hardware.biometrics.BiometricSourceType import android.hardware.biometrics.BiometricSourceType.FINGERPRINT +import android.hardware.fingerprint.FingerprintManager import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository @@ -129,7 +130,14 @@ data class BiometricMessage( val type: BiometricMessageType, val id: Int, val message: String?, -) +) { + fun isFingerprintLockoutMessage(): Boolean { + return source == FINGERPRINT && + type == BiometricMessageType.ERROR && + (id == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT || + id == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) + } +} enum class BiometricMessageType { HELP, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt index a2287c756e5b..ff8d5c908735 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt @@ -18,7 +18,6 @@ package com.android.systemui.keyguard.domain.interactor import android.content.Context import android.content.Intent -import android.hardware.fingerprint.FingerprintManager import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dagger.SysUISingleton @@ -35,6 +34,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNot import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map @@ -74,15 +74,13 @@ constructor( private val fingerprintLockoutEvents: Flow<Unit> = fingerprintAuthRepository.authenticationStatus .ifKeyguardOccludedByApp() - .filter { - it is ErrorFingerprintAuthenticationStatus && - (it.msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT || - it.msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) - } + .filter { it is ErrorFingerprintAuthenticationStatus && it.isLockoutMessage() } .map {} // maps FingerprintAuthenticationStatus => Unit val message: Flow<BiometricMessage?> = merge( - biometricMessageInteractor.fingerprintErrorMessage, + biometricMessageInteractor.fingerprintErrorMessage.filterNot { + it.isFingerprintLockoutMessage() + }, biometricMessageInteractor.fingerprintFailMessage, biometricMessageInteractor.fingerprintHelpMessage, ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt index 7fc6016bf087..ae18681a5b92 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.shared.model +import android.hardware.fingerprint.FingerprintManager import android.os.SystemClock.elapsedRealtime /** @@ -49,4 +50,9 @@ data class ErrorFingerprintAuthenticationStatus( val msg: String? = null, // present to break equality check if the same error occurs repeatedly. val createdAt: Long = elapsedRealtime(), -) : FingerprintAuthenticationStatus() +) : FingerprintAuthenticationStatus() { + fun isLockoutMessage(): Boolean { + return msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT || + msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt index 8f884d24ad21..42164c795b06 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt @@ -85,7 +85,7 @@ class MediaProjectionAppSelectorActivity( public override fun onCreate(bundle: Bundle?) { lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) - component = componentFactory.create(activity = this, view = this, resultHandler = this) + component = componentFactory.create(view = this, resultHandler = this) component.lifecycleObservers.forEach { lifecycle.addObserver(it) } // Create a separate configuration controller for this activity as the configuration diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt index 11538fadf24e..e0869ac6498e 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt @@ -161,7 +161,6 @@ interface MediaProjectionAppSelectorComponent { interface Factory { /** Create a factory to inject the activity into the graph */ fun create( - @BindsInstance activity: MediaProjectionAppSelectorActivity, @BindsInstance view: MediaProjectionAppSelectorView, @BindsInstance resultHandler: MediaProjectionAppSelectorResultHandler, ): MediaProjectionAppSelectorComponent diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt index b89179289a3d..8601b3de2f7c 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt @@ -24,8 +24,10 @@ class SceneWindowRootView( viewModel: SceneContainerViewModel, containerConfig: SceneContainerConfig, scenes: Set<Scene>, + layoutInsetController: LayoutInsetsController, ) { this.viewModel = viewModel + setLayoutInsetsController(layoutInsetController) SceneWindowRootViewBinder.bind( view = this@SceneWindowRootView, viewModel = viewModel, diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt index 860ebcb27487..c9a73e665293 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt @@ -1,9 +1,15 @@ package com.android.systemui.scene.ui.view +import android.annotation.SuppressLint import android.content.Context import android.util.AttributeSet +import android.util.Pair +import android.view.DisplayCutout import android.view.View +import android.view.WindowInsets import android.widget.FrameLayout +import androidx.core.view.updateMargins +import com.android.systemui.R import com.android.systemui.compose.ComposeFacade /** A view that can serve as the root of the main SysUI window. */ @@ -16,6 +22,10 @@ open class WindowRootView( attrs, ) { + private lateinit var layoutInsetsController: LayoutInsetsController + private var leftInset = 0 + private var rightInset = 0 + override fun onAttachedToWindow() { super.onAttachedToWindow() @@ -32,8 +42,114 @@ open class WindowRootView( } } + override fun generateLayoutParams(attrs: AttributeSet?): FrameLayout.LayoutParams? { + return LayoutParams(context, attrs) + } + + override fun generateDefaultLayoutParams(): FrameLayout.LayoutParams? { + return LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT + ) + } + + override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets? { + val insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()) + if (fitsSystemWindows) { + val paddingChanged = insets.top != paddingTop || insets.bottom != paddingBottom + + // Drop top inset, and pass through bottom inset. + if (paddingChanged) { + setPadding(0, 0, 0, 0) + } + } else { + val changed = + paddingLeft != 0 || paddingRight != 0 || paddingTop != 0 || paddingBottom != 0 + if (changed) { + setPadding(0, 0, 0, 0) + } + } + leftInset = 0 + rightInset = 0 + + val displayCutout = rootWindowInsets.displayCutout + val pairInsets: Pair<Int, Int> = + layoutInsetsController.getinsets(windowInsets, displayCutout) + leftInset = pairInsets.first + rightInset = pairInsets.second + applyMargins() + return windowInsets + } + + fun setLayoutInsetsController(layoutInsetsController: LayoutInsetsController) { + this.layoutInsetsController = layoutInsetsController + } + + private fun applyMargins() { + val count = childCount + for (i in 0 until count) { + val child = getChildAt(i) + if (child.layoutParams is LayoutParams) { + val layoutParams = child.layoutParams as LayoutParams + if ( + !layoutParams.ignoreRightInset && + (layoutParams.rightMargin != rightInset || + layoutParams.leftMargin != leftInset) + ) { + layoutParams.updateMargins(left = leftInset, right = rightInset) + child.requestLayout() + } + } + } + } + + /** + * Returns `true` if this view is the true root of the view-hierarchy; `false` otherwise. + * + * Please see the class-level documentation to understand why this is possible. + */ private fun isRoot(): Boolean { // TODO(b/283300105): remove this check once there's only one subclass of WindowRootView. return parent.let { it !is View || it.id == android.R.id.content } } + + /** Controller responsible for calculating insets for the shade window. */ + interface LayoutInsetsController { + + /** Update the insets and calculate them accordingly. */ + fun getinsets( + windowInsets: WindowInsets?, + displayCutout: DisplayCutout?, + ): Pair<Int, Int> + } + + private class LayoutParams : FrameLayout.LayoutParams { + var ignoreRightInset = false + + constructor( + width: Int, + height: Int, + ) : super( + width, + height, + ) + + @SuppressLint("CustomViewStyleable") + constructor( + context: Context, + attrs: AttributeSet?, + ) : super( + context, + attrs, + ) { + val obtainedAttributes = + context.obtainStyledAttributes(attrs, R.styleable.StatusBarWindowView_Layout) + ignoreRightInset = + obtainedAttributes.getBoolean( + R.styleable.StatusBarWindowView_Layout_ignoreRightInset, + false + ) + obtainedAttributes.recycle() + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java index 2b62b7d67c2a..a9c4aebe7ed6 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java @@ -17,19 +17,15 @@ package com.android.systemui.shade; import static android.os.Trace.TRACE_TAG_APP; -import static android.view.WindowInsets.Type.systemBars; import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG; import android.annotation.ColorInt; import android.annotation.DrawableRes; import android.annotation.LayoutRes; -import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; -import android.content.res.TypedArray; import android.graphics.Canvas; -import android.graphics.Insets; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -37,9 +33,7 @@ import android.net.Uri; import android.os.Bundle; import android.os.Trace; import android.util.AttributeSet; -import android.util.Pair; import android.view.ActionMode; -import android.view.DisplayCutout; import android.view.InputQueue; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -51,13 +45,10 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.Window; -import android.view.WindowInsets; import android.view.WindowInsetsController; -import android.widget.FrameLayout; import com.android.internal.view.FloatingActionMode; import com.android.internal.widget.floatingtoolbar.FloatingToolbar; -import com.android.systemui.R; import com.android.systemui.scene.ui.view.WindowRootView; /** @@ -68,9 +59,6 @@ import com.android.systemui.scene.ui.view.WindowRootView; public class NotificationShadeWindowView extends WindowRootView { public static final String TAG = "NotificationShadeWindowView"; - private int mRightInset = 0; - private int mLeftInset = 0; - // Implements the floating action mode for TextView's Cut/Copy/Past menu. Normally provided by // DecorView, but since this is a special window we have to roll our own. private View mFloatingActionModeOriginatingView; @@ -79,7 +67,6 @@ public class NotificationShadeWindowView extends WindowRootView { private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener; private InteractionEventHandler mInteractionEventHandler; - private LayoutInsetsController mLayoutInsetProvider; public NotificationShadeWindowView(Context context, AttributeSet attrs) { super(context, attrs); @@ -87,64 +74,6 @@ public class NotificationShadeWindowView extends WindowRootView { } @Override - public WindowInsets onApplyWindowInsets(WindowInsets windowInsets) { - final Insets insets = windowInsets.getInsetsIgnoringVisibility(systemBars()); - if (getFitsSystemWindows()) { - boolean paddingChanged = insets.top != getPaddingTop() - || insets.bottom != getPaddingBottom(); - - // Drop top inset, and pass through bottom inset. - if (paddingChanged) { - setPadding(0, 0, 0, 0); - } - } else { - boolean changed = getPaddingLeft() != 0 - || getPaddingRight() != 0 - || getPaddingTop() != 0 - || getPaddingBottom() != 0; - if (changed) { - setPadding(0, 0, 0, 0); - } - } - - mLeftInset = 0; - mRightInset = 0; - DisplayCutout displayCutout = getRootWindowInsets().getDisplayCutout(); - Pair<Integer, Integer> pairInsets = mLayoutInsetProvider - .getinsets(windowInsets, displayCutout); - mLeftInset = pairInsets.first; - mRightInset = pairInsets.second; - applyMargins(); - return windowInsets; - } - - private void applyMargins() { - final int count = getChildCount(); - for (int i = 0; i < count; i++) { - View child = getChildAt(i); - if (child.getLayoutParams() instanceof LayoutParams) { - LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (!lp.ignoreRightInset - && (lp.rightMargin != mRightInset || lp.leftMargin != mLeftInset)) { - lp.rightMargin = mRightInset; - lp.leftMargin = mLeftInset; - child.requestLayout(); - } - } - } - } - - @Override - public FrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs) { - return new LayoutParams(getContext(), attrs); - } - - @Override - protected FrameLayout.LayoutParams generateDefaultLayoutParams() { - return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); - } - - @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); setWillNotDraw(!DEBUG); @@ -172,10 +101,6 @@ public class NotificationShadeWindowView extends WindowRootView { mInteractionEventHandler = listener; } - protected void setLayoutInsetsController(LayoutInsetsController provider) { - mLayoutInsetProvider = provider; - } - @Override public boolean dispatchTouchEvent(MotionEvent ev) { Boolean result = mInteractionEventHandler.handleDispatchTouchEvent(ev); @@ -227,24 +152,6 @@ public class NotificationShadeWindowView extends WindowRootView { } } - private static class LayoutParams extends FrameLayout.LayoutParams { - - public boolean ignoreRightInset; - - LayoutParams(int width, int height) { - super(width, height); - } - - LayoutParams(Context c, AttributeSet attrs) { - super(c, attrs); - - TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.StatusBarWindowView_Layout); - ignoreRightInset = a.getBoolean( - R.styleable.StatusBarWindowView_Layout_ignoreRightInset, false); - a.recycle(); - } - } - @Override public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback, int type) { @@ -357,18 +264,6 @@ public class NotificationShadeWindowView extends WindowRootView { } } - /** - * Controller responsible for calculating insets for the shade window. - */ - public interface LayoutInsetsController { - - /** - * Update the insets and calculate them accordingly. - */ - Pair<Integer, Integer> getinsets(@Nullable WindowInsets windowInsets, - @Nullable DisplayCutout displayCutout); - } - interface InteractionEventHandler { /** * Returns a result for {@link ViewGroup#dispatchTouchEvent(MotionEvent)} or null to defer diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt index 529f12e0658e..c6cb9c4d347c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt @@ -61,6 +61,7 @@ import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider import com.android.systemui.statusbar.phone.StatusBarIconController import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.phone.StatusIconContainer +import com.android.systemui.statusbar.phone.StatusOverlayHoverListenerFactory import com.android.systemui.statusbar.policy.Clock import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.NextAlarmController @@ -99,6 +100,7 @@ constructor( private val qsBatteryModeController: QsBatteryModeController, private val nextAlarmController: NextAlarmController, private val activityStarter: ActivityStarter, + private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory, ) : ViewController<View>(header), Dumpable { companion object { @@ -326,6 +328,9 @@ constructor( demoModeController.addCallback(demoModeReceiver) statusBarIconController.addIconGroup(iconManager) nextAlarmController.addCallback(nextAlarmCallback) + systemIcons.setOnHoverListener( + statusOverlayHoverListenerFactory.createListener(systemIcons) + ) } override fun onViewDetached() { @@ -336,6 +341,7 @@ constructor( demoModeController.removeCallback(demoModeReceiver) statusBarIconController.removeIconGroup(iconManager) nextAlarmController.removeCallback(nextAlarmCallback) + systemIcons.setOnHoverListener(null) } fun disable(state1: Int, state2: Int, animate: Boolean) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt index fc6479eb62a4..e02c4275f095 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt @@ -42,6 +42,7 @@ import com.android.systemui.scene.ui.view.WindowRootView import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.LightRevealScrim +import com.android.systemui.statusbar.NotificationInsetsController import com.android.systemui.statusbar.NotificationShelf import com.android.systemui.statusbar.NotificationShelfController import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent @@ -78,6 +79,7 @@ abstract class ShadeViewProviderModule { containerConfigProvider: Provider<SceneContainerConfig>, @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>, + layoutInsetController: NotificationInsetsController, ): WindowRootView { return if ( featureFlags.isEnabled(Flags.SCENE_CONTAINER) && ComposeFacade.isComposeAvailable() @@ -88,6 +90,7 @@ abstract class ShadeViewProviderModule { viewModel = viewModelProvider.get(), containerConfig = containerConfigProvider.get(), scenes = scenesProvider.get(), + layoutInsetController = layoutInsetController, ) sceneWindowRootView } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java index 39d7d6675ed4..27f42c632cc3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java @@ -16,11 +16,11 @@ package com.android.systemui.statusbar; -import com.android.systemui.shade.NotificationShadeWindowView; +import com.android.systemui.scene.ui.view.WindowRootView; /** * Calculates insets for the notification shade window view. */ public abstract class NotificationInsetsController - implements NotificationShadeWindowView.LayoutInsetsController { + implements WindowRootView.LayoutInsetsController { } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java index 2677c3f2a8bd..c4495edbda4e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java @@ -32,6 +32,11 @@ import java.util.ArrayList; import javax.inject.Inject; +import kotlinx.coroutines.flow.FlowKt; +import kotlinx.coroutines.flow.MutableStateFlow; +import kotlinx.coroutines.flow.StateFlow; +import kotlinx.coroutines.flow.StateFlowKt; + /** */ @SysUISingleton @@ -47,6 +52,9 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher, private int mDarkModeIconColorSingleTone; private int mLightModeIconColorSingleTone; + private final MutableStateFlow<DarkChange> mDarkChangeFlow = StateFlowKt.MutableStateFlow( + DarkChange.EMPTY); + /** */ @Inject @@ -66,6 +74,11 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher, return mTransitionsController; } + @Override + public StateFlow<DarkChange> darkChangeFlow() { + return FlowKt.asStateFlow(mDarkChangeFlow); + } + public void addDarkReceiver(DarkReceiver receiver) { mReceivers.put(receiver, receiver); receiver.onDarkChanged(mTintAreas, mDarkIntensity, mIconTint); @@ -122,6 +135,7 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher, } private void applyIconTint() { + mDarkChangeFlow.setValue(new DarkChange(mTintAreas, mDarkIntensity, mIconTint)); for (int i = 0; i < mReceivers.size(); i++) { mReceivers.valueAt(i).onDarkChanged(mTintAreas, mDarkIntensity, mIconTint); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index 720eeba0fd4e..5c1f824bc687 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -34,7 +34,6 @@ import android.view.DisplayCutout; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; -import android.view.ViewTreeObserver; import android.view.WindowInsets; import android.widget.ImageView; import android.widget.LinearLayout; @@ -43,11 +42,11 @@ import android.widget.TextView; import androidx.annotation.VisibleForTesting; -import com.android.app.animation.Interpolators; import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.battery.BatteryMeterView; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; +import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange; import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer; import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder; import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel; @@ -55,6 +54,11 @@ import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel; import java.io.PrintWriter; import java.util.ArrayList; +import kotlinx.coroutines.flow.FlowKt; +import kotlinx.coroutines.flow.MutableStateFlow; +import kotlinx.coroutines.flow.StateFlow; +import kotlinx.coroutines.flow.StateFlowKt; + /** * The header group on Keyguard. */ @@ -83,6 +87,8 @@ public class KeyguardStatusBarView extends RelativeLayout { private int mStatusBarPaddingEnd; private int mMinDotWidth; private View mSystemIconsContainer; + private final MutableStateFlow<DarkChange> mDarkChange = StateFlowKt.MutableStateFlow( + DarkChange.EMPTY); private View mCutoutSpace; private ViewGroup mStatusIconArea; @@ -374,49 +380,6 @@ public class KeyguardStatusBarView extends RelativeLayout { return mKeyguardUserAvatarEnabled; } - private void animateNextLayoutChange() { - final int systemIconsCurrentX = mSystemIconsContainer.getLeft(); - final boolean userAvatarVisible = mMultiUserAvatar.getParent() == mStatusIconArea; - getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - getViewTreeObserver().removeOnPreDrawListener(this); - boolean userAvatarHiding = userAvatarVisible - && mMultiUserAvatar.getParent() != mStatusIconArea; - mSystemIconsContainer.setX(systemIconsCurrentX); - mSystemIconsContainer.animate() - .translationX(0) - .setDuration(400) - .setStartDelay(userAvatarHiding ? 300 : 0) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .start(); - if (userAvatarHiding) { - getOverlay().add(mMultiUserAvatar); - mMultiUserAvatar.animate() - .alpha(0f) - .setDuration(300) - .setStartDelay(0) - .setInterpolator(Interpolators.ALPHA_OUT) - .withEndAction(() -> { - mMultiUserAvatar.setAlpha(1f); - getOverlay().remove(mMultiUserAvatar); - }) - .start(); - - } else { - mMultiUserAvatar.setAlpha(0f); - mMultiUserAvatar.animate() - .alpha(1f) - .setDuration(300) - .setStartDelay(200) - .setInterpolator(Interpolators.ALPHA_IN); - } - return true; - } - }); - - } - @Override public void setVisibility(int visibility) { super.setVisibility(visibility); @@ -474,6 +437,7 @@ public class KeyguardStatusBarView extends RelativeLayout { iconManager.setTint(iconColor); } + mDarkChange.setValue(new DarkChange(mEmptyTintRect, intensity, iconColor)); applyDarkness(R.id.battery, mEmptyTintRect, intensity, iconColor); applyDarkness(R.id.clock, mEmptyTintRect, intensity, iconColor); } @@ -536,4 +500,8 @@ public class KeyguardStatusBarView extends RelativeLayout { super.onMeasure(widthMeasureSpec, heightMeasureSpec); Trace.endSection(); } + + public StateFlow<DarkChange> darkChangeFlow() { + return FlowKt.asStateFlow(mDarkChange); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java index 680f19a79a05..be336e59f534 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java @@ -69,8 +69,6 @@ import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel; import com.android.systemui.util.ViewController; import com.android.systemui.util.settings.SecureSettings; -import kotlin.Unit; - import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -78,6 +76,8 @@ import java.util.concurrent.Executor; import javax.inject.Inject; +import kotlin.Unit; + /** View Controller for {@link com.android.systemui.statusbar.phone.KeyguardStatusBarView}. */ public class KeyguardStatusBarViewController extends ViewController<KeyguardStatusBarView> { private static final String TAG = "KeyguardStatusBarViewController"; @@ -119,6 +119,9 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat private final Object mLock = new Object(); private final KeyguardLogger mLogger; + private View mSystemIconsContainer; + private final StatusOverlayHoverListenerFactory mStatusOverlayHoverListenerFactory; + // TODO(b/273443374): remove private NotificationMediaManager mNotificationMediaManager; @@ -286,7 +289,8 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat CommandQueue commandQueue, @Main Executor mainExecutor, KeyguardLogger logger, - NotificationMediaManager notificationMediaManager + NotificationMediaManager notificationMediaManager, + StatusOverlayHoverListenerFactory statusOverlayHoverListenerFactory ) { super(view); mCarrierTextController = carrierTextController; @@ -339,6 +343,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat this::updateViewState ); mNotificationMediaManager = notificationMediaManager; + mStatusOverlayHoverListenerFactory = statusOverlayHoverListenerFactory; } @Override @@ -363,6 +368,10 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat mTintedIconManager.setBlockList(getBlockedIcons()); mStatusBarIconController.addIconGroup(mTintedIconManager); } + mSystemIconsContainer = mView.findViewById(R.id.system_icons); + StatusOverlayHoverListener hoverListener = mStatusOverlayHoverListenerFactory + .createDarkAwareListener(mSystemIconsContainer, mView.darkChangeFlow()); + mSystemIconsContainer.setOnHoverListener(hoverListener); mView.setOnApplyWindowInsetsListener( (view, windowInsets) -> mView.updateWindowInsets(windowInsets, mInsetsProvider)); mSecureSettings.registerContentObserverForUser( @@ -376,6 +385,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat @Override protected void onViewDetached() { + mSystemIconsContainer.setOnHoverListener(null); mConfigurationController.removeCallback(mConfigurationListener); mAnimationScheduler.removeCallback(mAnimationCallback); mUserInfoController.removeCallback(mOnUserInfoChangedListener); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt index 4e136deab5e3..8c3050d48d53 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt @@ -63,9 +63,12 @@ class PhoneStatusBarViewController private constructor( private val userChipViewModel: StatusBarUserChipViewModel, private val viewUtil: ViewUtil, private val featureFlags: FeatureFlags, - private val configurationController: ConfigurationController + private val configurationController: ConfigurationController, + private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory, ) : ViewController<PhoneStatusBarView>(view) { + private lateinit var statusContainer: View + private val configurationListener = object : ConfigurationController.ConfigurationListener { override fun onConfigChanged(newConfig: Configuration?) { mView.updateResources() @@ -73,6 +76,9 @@ class PhoneStatusBarViewController private constructor( } override fun onViewAttached() { + statusContainer = mView.findViewById(R.id.system_icons) + statusContainer.setOnHoverListener( + statusOverlayHoverListenerFactory.createDarkAwareListener(statusContainer)) if (moveFromCenterAnimationController == null) return val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_start_side_except_heads_up) @@ -104,6 +110,7 @@ class PhoneStatusBarViewController private constructor( } override fun onViewDetached() { + statusContainer.setOnHoverListener(null) progressProvider?.setReadyToHandleTransition(false) moveFromCenterAnimationController?.onViewDetached() configurationController.removeCallback(configurationListener) @@ -245,6 +252,7 @@ class PhoneStatusBarViewController private constructor( private val shadeLogger: ShadeLogger, private val viewUtil: ViewUtil, private val configurationController: ConfigurationController, + private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory, ) { fun create( view: PhoneStatusBarView @@ -268,7 +276,8 @@ class PhoneStatusBarViewController private constructor( userChipViewModel, viewUtil, featureFlags, - configurationController + configurationController, + statusOverlayHoverListenerFactory, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index ec0c00e26c2f..8de213f262c2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -215,8 +215,7 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte public void onNotificationClicked(NotificationEntry entry, ExpandableNotificationRow row) { mLogger.logStartingActivityFromClick(entry); - if (mRemoteInputManager.isRemoteInputActive(entry) - && !TextUtils.isEmpty(row.getActiveRemoteInputText())) { + if (mRemoteInputManager.isRemoteInputActive(entry)) { // We have an active remote input typed and the user clicked on the notification. // this was probably unintentional, so we're closing the edit text instead. mRemoteInputManager.closeRemoteInputs(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListener.kt new file mode 100644 index 000000000000..881741ae5746 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListener.kt @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone + +import android.content.res.Configuration +import android.content.res.Resources +import android.graphics.Color +import android.graphics.drawable.PaintDrawable +import android.view.MotionEvent +import android.view.View +import android.view.View.OnHoverListener +import androidx.annotation.ColorInt +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.R +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.plugins.DarkIconDispatcher +import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch + +class StatusOverlayHoverListenerFactory +@Inject +constructor( + @Main private val resources: Resources, + private val configurationController: ConfigurationController, + private val darkIconDispatcher: SysuiDarkIconDispatcher, +) { + + /** Creates listener always using the same light color for overlay */ + fun createListener(view: View) = + StatusOverlayHoverListener( + view, + configurationController, + resources, + flowOf(HoverTheme.LIGHT), + ) + + /** + * Creates listener using [DarkIconDispatcher] to determine light or dark color of the overlay + */ + fun createDarkAwareListener(view: View) = + createDarkAwareListener(view, darkIconDispatcher.darkChangeFlow()) + + /** + * Creates listener using provided [DarkChange] producer to determine light or dark color of the + * overlay + */ + fun createDarkAwareListener(view: View, darkFlow: StateFlow<DarkChange>) = + StatusOverlayHoverListener( + view, + configurationController, + resources, + darkFlow.map { toHoverTheme(view, it) }, + ) + + private fun toHoverTheme(view: View, darkChange: DarkChange): HoverTheme { + val calculatedTint = DarkIconDispatcher.getTint(darkChange.areas, view, darkChange.tint) + // currently calculated tint is either white or some shade of black. + // So checking for Color.WHITE is deterministic compared to checking for Color.BLACK. + // In the future checking Color.luminance() might be more appropriate. + return if (calculatedTint == Color.WHITE) HoverTheme.LIGHT else HoverTheme.DARK + } +} + +/** + * theme of hover drawable - it's different from device theme. This theme depends on view's + * background and/or dark value returned from [DarkIconDispatcher] + */ +enum class HoverTheme { + LIGHT, + DARK +} + +/** + * [OnHoverListener] that adds [Drawable] overlay on top of the status icons when cursor/stylus + * starts hovering over them and removes overlay when status icons are no longer hovered + */ +class StatusOverlayHoverListener( + view: View, + configurationController: ConfigurationController, + private val resources: Resources, + private val themeFlow: Flow<HoverTheme>, +) : OnHoverListener { + + @ColorInt private var darkColor: Int = 0 + @ColorInt private var lightColor: Int = 0 + private var cornerRadius = 0f + + private var lastTheme = HoverTheme.LIGHT + + val backgroundColor + get() = if (lastTheme == HoverTheme.LIGHT) lightColor else darkColor + + init { + view.repeatWhenAttached { + lifecycleScope.launch { + val configurationListener = + object : ConfigurationListener { + override fun onConfigChanged(newConfig: Configuration?) { + updateResources() + } + } + repeatOnLifecycle(Lifecycle.State.CREATED) { + configurationController.addCallback(configurationListener) + } + configurationController.removeCallback(configurationListener) + } + lifecycleScope.launch { themeFlow.collect { lastTheme = it } } + } + updateResources() + } + + override fun onHover(v: View, event: MotionEvent): Boolean { + if (event.action == MotionEvent.ACTION_HOVER_ENTER) { + val drawable = + PaintDrawable(backgroundColor).apply { + setCornerRadius(cornerRadius) + setBounds(0, 0, v.width, v.height) + } + v.overlay.add(drawable) + } else if (event.action == MotionEvent.ACTION_HOVER_EXIT) { + v.overlay.clear() + } + return true + } + + private fun updateResources() { + lightColor = resources.getColor(R.color.status_bar_icons_hover_color_light) + darkColor = resources.getColor(R.color.status_bar_icons_hover_color_dark) + cornerRadius = resources.getDimension(R.dimen.status_icons_hover_state_background_radius) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SysuiDarkIconDispatcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SysuiDarkIconDispatcher.java index d53772127601..f5e90348a7c4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SysuiDarkIconDispatcher.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SysuiDarkIconDispatcher.java @@ -16,9 +16,16 @@ package com.android.systemui.statusbar.phone; +import android.graphics.Rect; + import com.android.systemui.Dumpable; import com.android.systemui.plugins.DarkIconDispatcher; +import java.util.ArrayList; +import java.util.Collection; + +import kotlinx.coroutines.flow.StateFlow; + /** * Dispatches events to {@link DarkReceiver}s about changes in darkness, tint area * and dark intensity. @@ -29,4 +36,26 @@ public interface SysuiDarkIconDispatcher extends DarkIconDispatcher, Dumpable { * @return LightBarTransitionsController */ LightBarTransitionsController getTransitionsController(); + + /** + * Flow equivalent of registering {@link DarkReceiver} using + * {@link DarkIconDispatcher#addDarkReceiver(DarkReceiver)} + */ + StateFlow<DarkChange> darkChangeFlow(); + + /** Model for {@link #darkChangeFlow()} */ + class DarkChange { + + public static final DarkChange EMPTY = new DarkChange(new ArrayList<>(), 0, 0); + + public DarkChange(Collection<Rect> areas, float darkIntensity, int tint) { + this.areas = areas; + this.darkIntensity = darkIntensity; + this.tint = tint; + } + + public final Collection<Rect> areas; + public final float darkIntensity; + public final int tint; + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java index 36cdfe642874..11ad20672100 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java @@ -775,7 +775,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { final View mirrorView = mWindowManager.getAttachedView(); - final long timeout = SystemClock.uptimeMillis() + 1000; + final long timeout = SystemClock.uptimeMillis() + 5000; final AtomicDouble maxScaleX = new AtomicDouble(); final Runnable onAnimationFrame = new Runnable() { @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt index a6ad4b24b63d..a86937fcad3c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt @@ -89,6 +89,22 @@ class AuthenticationInteractorTest : SysuiTestCase() { utils.authenticationRepository.setLockscreenEnabled(false) val isUnlocked by collectLastValue(underTest.isUnlocked) + // Toggle isUnlocked, twice. + // + // This is done because the underTest.isUnlocked flow doesn't receive values from + // just changing the state above; the actual isUnlocked state needs to change to + // cause the logic under test to "pick up" the current state again. + // + // It is done twice to make sure that we don't actually change the isUnlocked + // state from what it originally was. + utils.authenticationRepository.setUnlocked( + !utils.authenticationRepository.isUnlocked.value + ) + runCurrent() + utils.authenticationRepository.setUnlocked( + !utils.authenticationRepository.isUnlocked.value + ) + runCurrent() assertThat(isUnlocked).isTrue() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt new file mode 100644 index 000000000000..bfdb9231a9f8 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.ui + +import android.content.ContentProvider +import android.graphics.Bitmap +import android.graphics.drawable.Icon +import android.net.Uri +import android.os.UserHandle +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class CanUseIconPredicateTest : SysuiTestCase() { + + private companion object { + const val USER_ID_1 = 1 + const val USER_ID_2 = 2 + } + + val underTest: CanUseIconPredicate = CanUseIconPredicate(USER_ID_1) + + @Test + fun testReturnsFalseForDifferentUser() { + val user2Icon = + Icon.createWithContentUri( + ContentProvider.createContentUriForUser( + Uri.parse("content://test"), + UserHandle.of(USER_ID_2) + ) + ) + + assertThat(underTest.invoke(user2Icon)).isFalse() + } + + @Test + fun testReturnsTrueForCorrectUser() { + val user1Icon = + Icon.createWithContentUri( + ContentProvider.createContentUriForUser( + Uri.parse("content://test"), + UserHandle.of(USER_ID_1) + ) + ) + + assertThat(underTest.invoke(user1Icon)).isTrue() + } + + @Test + fun testReturnsTrueForUriWithoutUser() { + val uriIcon = Icon.createWithContentUri(Uri.parse("content://test")) + + assertThat(underTest.invoke(uriIcon)).isTrue() + } + + @Test + fun testReturnsTrueForNonUriIcon() { + val bitmapIcon = Icon.createWithBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)) + + assertThat(underTest.invoke(bitmapIcon)).isTrue() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt index d3c465dab438..42f28c8c6043 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt @@ -66,7 +66,8 @@ class ControlViewHolderTest : SysuiTestCase() { FakeExecutor(clock), mock(ControlActionCoordinator::class.java), mock(ControlsMetricsLogger::class.java), - uid = 100 + uid = 100, + 0, ) val cws = ControlWithState( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt index 6e52d1af7eb1..baa5ee81cc4a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt @@ -233,12 +233,12 @@ class OccludingAppDeviceEntryInteractorTest : SysuiTestCase() { // ERROR message fingerprintAuthRepository.setAuthenticationStatus( ErrorFingerprintAuthenticationStatus( - FingerprintManager.FINGERPRINT_ERROR_LOCKOUT, + FingerprintManager.FINGERPRINT_ERROR_CANCELED, "testError", ) ) assertThat(message?.source).isEqualTo(BiometricSourceType.FINGERPRINT) - assertThat(message?.id).isEqualTo(FingerprintManager.FINGERPRINT_ERROR_LOCKOUT) + assertThat(message?.id).isEqualTo(FingerprintManager.FINGERPRINT_ERROR_CANCELED) assertThat(message?.message).isEqualTo("testError") assertThat(message?.type).isEqualTo(BiometricMessageType.ERROR) @@ -262,6 +262,34 @@ class OccludingAppDeviceEntryInteractorTest : SysuiTestCase() { assertThat(message?.type).isEqualTo(BiometricMessageType.FAIL) } + @Test + fun message_fpError_lockoutFilteredOut() = + testScope.runTest { + val message by collectLastValue(underTest.message) + + givenOnOccludingApp(true) + givenPrimaryAuthRequired(false) + runCurrent() + + // permanent lockout error message + fingerprintAuthRepository.setAuthenticationStatus( + ErrorFingerprintAuthenticationStatus( + FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT, + "testPermanentLockoutMessageFiltered", + ) + ) + assertThat(message).isNull() + + // temporary lockout error message + fingerprintAuthRepository.setAuthenticationStatus( + ErrorFingerprintAuthenticationStatus( + FingerprintManager.FINGERPRINT_ERROR_LOCKOUT, + "testLockoutMessageFiltered", + ) + ) + assertThat(message).isNull() + } + private fun givenOnOccludingApp(isOnOccludingApp: Boolean) { keyguardRepository.setKeyguardOccluded(isOnOccludingApp) keyguardRepository.setKeyguardShowing(isOnOccludingApp) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt index bf25f2975253..2501f85798aa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt @@ -55,6 +55,7 @@ import com.android.systemui.shade.carrier.ShadeCarrierGroupController import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider import com.android.systemui.statusbar.phone.StatusBarIconController import com.android.systemui.statusbar.phone.StatusIconContainer +import com.android.systemui.statusbar.phone.StatusOverlayHoverListenerFactory import com.android.systemui.statusbar.policy.Clock import com.android.systemui.statusbar.policy.FakeConfigurationController import com.android.systemui.statusbar.policy.NextAlarmController @@ -123,6 +124,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() { @Mock private lateinit var qsBatteryModeController: QsBatteryModeController @Mock private lateinit var nextAlarmController: NextAlarmController @Mock private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var mStatusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory @JvmField @Rule val mockitoRule = MockitoJUnit.rule() var viewVisibility = View.GONE @@ -194,6 +196,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() { qsBatteryModeController, nextAlarmController, activityStarter, + mStatusOverlayHoverListenerFactory ) whenever(view.isAttachedToWindow).thenReturn(true) shadeHeaderController.init() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java index e838a480f3cc..d100c687d802 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java @@ -122,6 +122,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { @Mock private KeyguardLogger mLogger; @Mock private NotificationMediaManager mNotificationMediaManager; + @Mock private StatusOverlayHoverListenerFactory mStatusOverlayHoverListenerFactory; private TestShadeViewStateProvider mShadeViewStateProvider; private KeyguardStatusBarView mKeyguardStatusBarView; @@ -171,7 +172,8 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { mCommandQueue, mFakeExecutor, mLogger, - mNotificationMediaManager + mNotificationMediaManager, + mStatusOverlayHoverListenerFactory ); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt index 7de0075c45ff..2e92bb948c60 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt @@ -69,6 +69,8 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { @Mock private lateinit var configurationController: ConfigurationController @Mock + private lateinit var mStatusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory + @Mock private lateinit var userChipViewModel: StatusBarUserChipViewModel @Mock private lateinit var centralSurfacesImpl: CentralSurfacesImpl @@ -204,7 +206,8 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { sceneInteractor, shadeLogger, viewUtil, - configurationController + configurationController, + mStatusOverlayHoverListenerFactory ).create(view).also { it.init() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt new file mode 100644 index 000000000000..63508e193bb8 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone + +import android.content.Context +import android.graphics.Color +import android.graphics.drawable.Drawable +import android.graphics.drawable.PaintDrawable +import android.os.SystemClock +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.testing.TestableLooper.RunWithLooper +import android.testing.ViewUtils +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroupOverlay +import android.widget.LinearLayout +import androidx.annotation.ColorInt +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange +import com.android.systemui.statusbar.policy.FakeConfigurationController +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.flow.MutableStateFlow +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.verify + +@RunWith(AndroidTestingRunner::class) +@RunWithLooper(setAsMainLooper = true) +@SmallTest +class StatusOverlayHoverListenerTest : SysuiTestCase() { + + private val viewOverlay = mock<ViewGroupOverlay>() + private val overlayCaptor = argumentCaptor<Drawable>() + private val darkDispatcher = mock<SysuiDarkIconDispatcher>() + private val darkChange: MutableStateFlow<DarkChange> = MutableStateFlow(DarkChange.EMPTY) + + private val factory = + StatusOverlayHoverListenerFactory( + context.resources, + FakeConfigurationController(), + darkDispatcher + ) + private val view = TestableStatusContainer(context, viewOverlay) + + private lateinit var looper: TestableLooper + + @Before + fun setUp() { + looper = TestableLooper.get(this) + whenever(darkDispatcher.darkChangeFlow()).thenReturn(darkChange) + } + + @Test + fun onHoverStarted_addsOverlay() { + view.setUpHoverListener() + + view.hoverStarted() + + assertThat(overlayDrawable).isNotNull() + } + + @Test + fun onHoverEnded_removesOverlay() { + view.setUpHoverListener() + + view.hoverStarted() // stopped callback will be called only if hover has started + view.hoverStopped() + + verify(viewOverlay).clear() + } + + @Test + fun onHoverStarted_overlayHasLightColor() { + view.setUpHoverListener() + + view.hoverStarted() + + assertThat(overlayColor) + .isEqualTo(context.resources.getColor(R.color.status_bar_icons_hover_color_light)) + } + + @Test + fun onDarkAwareHoverStarted_withBlackIcons_overlayHasDarkColor() { + view.setUpDarkAwareHoverListener() + setIconsTint(Color.BLACK) + + view.hoverStarted() + + assertThat(overlayColor) + .isEqualTo(context.resources.getColor(R.color.status_bar_icons_hover_color_dark)) + } + + @Test + fun onHoverStarted_withBlackIcons_overlayHasLightColor() { + view.setUpHoverListener() + setIconsTint(Color.BLACK) + + view.hoverStarted() + + assertThat(overlayColor) + .isEqualTo(context.resources.getColor(R.color.status_bar_icons_hover_color_light)) + } + + @Test + fun onDarkAwareHoverStarted_withWhiteIcons_overlayHasLightColor() { + view.setUpDarkAwareHoverListener() + setIconsTint(Color.WHITE) + + view.hoverStarted() + + assertThat(overlayColor) + .isEqualTo(context.resources.getColor(R.color.status_bar_icons_hover_color_light)) + } + + private fun View.setUpHoverListener() { + setOnHoverListener(factory.createListener(view)) + attachView(view) + } + + private fun View.setUpDarkAwareHoverListener() { + setOnHoverListener(factory.createDarkAwareListener(view)) + attachView(view) + } + + private fun attachView(view: View) { + ViewUtils.attachView(view) + // attaching is async so processAllMessages is required for view.repeatWhenAttached to run + looper.processAllMessages() + } + + private val overlayDrawable: Drawable + get() { + verify(viewOverlay).add(overlayCaptor.capture()) + return overlayCaptor.value + } + + private val overlayColor + get() = (overlayDrawable as PaintDrawable).paint.color + + private fun setIconsTint(@ColorInt color: Int) { + // passing empty ArrayList is equivalent to just accepting passed color as icons color + darkChange.value = DarkChange(/* areas= */ ArrayList(), /* darkIntensity= */ 1f, color) + } + + private fun TestableStatusContainer.hoverStarted() { + injectHoverEvent(hoverEvent(MotionEvent.ACTION_HOVER_ENTER)) + } + + private fun TestableStatusContainer.hoverStopped() { + injectHoverEvent(hoverEvent(MotionEvent.ACTION_HOVER_EXIT)) + } + + class TestableStatusContainer(context: Context, private val mockOverlay: ViewGroupOverlay) : + LinearLayout(context) { + + fun injectHoverEvent(event: MotionEvent) = dispatchHoverEvent(event) + + override fun getOverlay() = mockOverlay + } + + private fun hoverEvent(action: Int): MotionEvent { + return MotionEvent.obtain( + /* downTime= */ SystemClock.uptimeMillis(), + /* eventTime= */ SystemClock.uptimeMillis(), + /* action= */ action, + /* x= */ 0f, + /* y= */ 0f, + /* metaState= */ 0 + ) + } +} diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index 8e7d27795c07..f3a540b1c7a5 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -40,6 +40,7 @@ import android.view.accessibility.AccessibilityEvent; import com.android.server.LocalServices; import com.android.server.accessibility.gestures.TouchExplorer; import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler; +import com.android.server.accessibility.magnification.FullScreenMagnificationVibrationHelper; import com.android.server.accessibility.magnification.MagnificationGestureHandler; import com.android.server.accessibility.magnification.WindowMagnificationGestureHandler; import com.android.server.accessibility.magnification.WindowMagnificationPromptController; @@ -654,11 +655,14 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } else { final Context uiContext = displayContext.createWindowContext( TYPE_MAGNIFICATION_OVERLAY, null /* options */); + FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper = + new FullScreenMagnificationVibrationHelper(uiContext); magnificationGestureHandler = new FullScreenMagnificationGestureHandler(uiContext, mAms.getMagnificationController().getFullScreenMagnificationController(), mAms.getTraceManager(), mAms.getMagnificationController(), detectControlGestures, triggerable, - new WindowMagnificationPromptController(displayContext, mUserId), displayId); + new WindowMagnificationPromptController(displayContext, mUserId), displayId, + fullScreenMagnificationVibrationHelper); } return magnificationGestureHandler; } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java index fd8babbbd5b1..58b61b36af17 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java @@ -155,7 +155,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH boolean detectTripleTap, boolean detectShortcutTrigger, @NonNull WindowMagnificationPromptController promptController, - int displayId) { + int displayId, + FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper) { super(displayId, detectTripleTap, detectShortcutTrigger, trace, callback); if (DEBUG_ALL) { Log.i(mLogTag, @@ -203,7 +204,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH mDetectingState = new DetectingState(context); mViewportDraggingState = new ViewportDraggingState(); mPanningScalingState = new PanningScalingState(context); - mSinglePanningState = new SinglePanningState(context); + mSinglePanningState = new SinglePanningState(context, + fullScreenMagnificationVibrationHelper); setSinglePanningEnabled( context.getResources() .getBoolean(R.bool.config_enable_a11y_magnification_single_panning)); @@ -1334,11 +1336,17 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } final class SinglePanningState extends SimpleOnGestureListener implements State { + + private final GestureDetector mScrollGestureDetector; private MotionEventInfo mEvent; + private final FullScreenMagnificationVibrationHelper + mFullScreenMagnificationVibrationHelper; - SinglePanningState(Context context) { + SinglePanningState(Context context, FullScreenMagnificationVibrationHelper + fullScreenMagnificationVibrationHelper) { mScrollGestureDetector = new GestureDetector(context, this, Handler.getMain()); + mFullScreenMagnificationVibrationHelper = fullScreenMagnificationVibrationHelper; } @Override @@ -1378,10 +1386,20 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH if (mFullScreenMagnificationController.isAtEdge(mDisplayId)) { clear(); transitionTo(mDelegatingState); + vibrateIfNeeded(); } return /* event consumed: */ true; } + private void vibrateIfNeeded() { + if ((mFullScreenMagnificationController.isAtLeftEdge(mDisplayId) + || mFullScreenMagnificationController.isAtRightEdge(mDisplayId))) { + mFullScreenMagnificationVibrationHelper.vibrateIfSettingEnabled(); + } + } + + + @Override public String toString() { return "SinglePanningState{" diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationVibrationHelper.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationVibrationHelper.java new file mode 100644 index 000000000000..37a2eb5c2b75 --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationVibrationHelper.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.accessibility.magnification; + +import android.annotation.Nullable; +import android.content.ContentResolver; +import android.content.Context; +import android.os.UserHandle; +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.provider.Settings; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * Class to encapsulate all the logic to fire a vibration when user reaches the screen's left or + * right edge, when it's in magnification mode. + */ +public class FullScreenMagnificationVibrationHelper { + private static final long VIBRATION_DURATION_MS = 10L; + private static final int VIBRATION_AMPLITUDE = VibrationEffect.MAX_AMPLITUDE / 2; + + @Nullable + private final Vibrator mVibrator; + private final ContentResolver mContentResolver; + private final VibrationEffect mVibrationEffect = VibrationEffect.get( + VibrationEffect.EFFECT_CLICK); + @VisibleForTesting + VibrationEffectSupportedProvider mIsVibrationEffectSupportedProvider; + + public FullScreenMagnificationVibrationHelper(Context context) { + mContentResolver = context.getContentResolver(); + mVibrator = context.getSystemService(Vibrator.class); + mIsVibrationEffectSupportedProvider = + () -> mVibrator != null && mVibrator.areAllEffectsSupported( + VibrationEffect.EFFECT_CLICK) == Vibrator.VIBRATION_EFFECT_SUPPORT_YES; + } + + + void vibrateIfSettingEnabled() { + if (mVibrator != null && mVibrator.hasVibrator() && isEdgeHapticSettingEnabled()) { + if (mIsVibrationEffectSupportedProvider.isVibrationEffectSupported()) { + mVibrator.vibrate(mVibrationEffect); + } else { + mVibrator.vibrate(VibrationEffect.createOneShot(VIBRATION_DURATION_MS, + VIBRATION_AMPLITUDE)); + } + } + } + + private boolean isEdgeHapticSettingEnabled() { + return Settings.Secure.getIntForUser( + mContentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_EDGE_HAPTIC_ENABLED, + 0, UserHandle.USER_CURRENT) + == 1; + } + + @VisibleForTesting + interface VibrationEffectSupportedProvider { + boolean isVibrationEffectSupported(); + } +} + diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 7b459aa4a0ad..b76e99eb5431 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -2472,6 +2472,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } + @Override + public void requestHideFillUiWhenDestroyed(AutofillId id) { + synchronized (mLock) { + // NOTE: We allow this call in a destroyed state as the UI is + // asked to go away after we get destroyed, so let it do that. + try { + mClient.requestHideFillUiWhenDestroyed(this.id, id); + } catch (RemoteException e) { + Slog.e(TAG, "Error requesting to hide fill UI", e); + } + + mInlineSessionController.hideInlineSuggestionsUiLocked(id); + } + } + // AutoFillUiCallback @Override public void cancelSession() { diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java index f92d38dc0deb..d479dfb512ca 100644 --- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java +++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java @@ -95,6 +95,7 @@ public final class AutoFillUI { void requestShowFillUi(AutofillId id, int width, int height, IAutofillWindowPresenter presenter); void requestHideFillUi(AutofillId id); + void requestHideFillUiWhenDestroyed(AutofillId id); void startIntentSenderAndFinishSession(IntentSender intentSender); void startIntentSender(IntentSender intentSender, Intent intent); void dispatchUnhandledKey(AutofillId id, KeyEvent keyEvent); @@ -289,6 +290,13 @@ public final class AutoFillUI { } @Override + public void requestHideFillUiWhenDestroyed() { + if (mCallback != null) { + mCallback.requestHideFillUiWhenDestroyed(focusedId); + } + } + + @Override public void startIntentSender(IntentSender intentSender) { if (mCallback != null) { mCallback.startIntentSenderAndFinishSession(intentSender); diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java index cdfe7bb4f4a7..cdd9ef4e1a76 100644 --- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java @@ -91,6 +91,7 @@ final class FillUi { void requestShowFillUi(int width, int height, IAutofillWindowPresenter windowPresenter); void requestHideFillUi(); + void requestHideFillUiWhenDestroyed(); void startIntentSender(IntentSender intentSender); void dispatchUnhandledKey(KeyEvent keyEvent); void cancelSession(); @@ -482,7 +483,7 @@ final class FillUi { } mCallback.onDestroy(); if (notifyClient) { - mCallback.requestHideFillUi(); + mCallback.requestHideFillUiWhenDestroyed(); } mDestroyed = true; } diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java index 8fc30e43d1a0..173a75be10b0 100644 --- a/services/core/java/com/android/server/BinaryTransparencyService.java +++ b/services/core/java/com/android/server/BinaryTransparencyService.java @@ -229,8 +229,8 @@ public class BinaryTransparencyService extends SystemService { // Only digest and split name are different between splits. Digest digest = measureApk(split.getPath()); - appInfo.digest = digest.value; - appInfo.digestAlgorithm = digest.algorithm; + appInfo.digest = digest.value(); + appInfo.digestAlgorithm = digest.algorithm(); results.add(appInfo); } @@ -391,8 +391,8 @@ public class BinaryTransparencyService extends SystemService { var apexInfo = new IBinaryTransparencyService.ApexInfo(); apexInfo.packageName = packageState.getPackageName(); apexInfo.longVersion = packageState.getVersionCode(); - apexInfo.digest = apexChecksum.value; - apexInfo.digestAlgorithm = apexChecksum.algorithm; + apexInfo.digest = apexChecksum.value(); + apexInfo.digestAlgorithm = apexChecksum.algorithm(); apexInfo.signerDigests = computePackageSignerSha256Digests(packageState.getSigningInfo()); @@ -1693,13 +1693,5 @@ public class BinaryTransparencyService extends SystemService { return slice.getList(); } - private static class Digest { - public int algorithm; - public byte[] value; - - Digest(int algorithm, byte[] value) { - this.algorithm = algorithm; - this.value = value; - } - } + private record Digest(int algorithm, byte[] value) {} } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 9f958be84e98..3ffca0e22fc0 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -1386,6 +1386,10 @@ public class AudioService extends IAudioService.Stub new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); intentFilter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED); intentFilter.addAction(Intent.ACTION_DOCK_EVENT); + if (mDisplayManager == null) { + intentFilter.addAction(Intent.ACTION_SCREEN_ON); + intentFilter.addAction(Intent.ACTION_SCREEN_OFF); + } intentFilter.addAction(Intent.ACTION_USER_SWITCHED); intentFilter.addAction(Intent.ACTION_USER_BACKGROUND); intentFilter.addAction(Intent.ACTION_USER_FOREGROUND); @@ -1416,7 +1420,9 @@ public class AudioService extends IAudioService.Stub subscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionChangedListener); } - mDisplayManager.registerDisplayListener(mDisplayListener, mAudioHandler); + if (mDisplayManager != null) { + mDisplayManager.registerDisplayListener(mDisplayListener, mAudioHandler); + } } public void systemReady() { @@ -3637,8 +3643,14 @@ public class AudioService extends IAudioService.Stub hdmiClient = mHdmiTvClient; } - if (((mHdmiPlaybackClient != null && isFullVolumeDevice(device)) - || (mHdmiTvClient != null && mHdmiSystemAudioSupported)) + boolean playbackDeviceConditions = mHdmiPlaybackClient != null + && isFullVolumeDevice(device); + boolean tvConditions = mHdmiTvClient != null + && mHdmiSystemAudioSupported + && !isAbsoluteVolumeDevice(device) + && !isA2dpAbsoluteVolumeDevice(device); + + if ((playbackDeviceConditions || tvConditions) && mHdmiCecVolumeControlEnabled && streamTypeAlias == AudioSystem.STREAM_MUSIC) { int keyCode = KeyEvent.KEYCODE_UNKNOWN; @@ -9587,6 +9599,17 @@ public class AudioService extends IAudioService.Stub } else if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED) || action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { mDeviceBroker.receiveBtEvent(intent); + } else if (action.equals(Intent.ACTION_SCREEN_ON)) { + if (mMonitorRotation) { + RotationHelper.enable(); + } + AudioSystem.setParameters("screen_state=on"); + } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { + if (mMonitorRotation) { + //reduce wakeups (save current) by only listening when display is on + RotationHelper.disable(); + } + AudioSystem.setParameters("screen_state=off"); } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) { sendMsg(mAudioHandler, MSG_CONFIGURATION_CHANGED, diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index de7b687bc49b..4353c5787d4b 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -2058,6 +2058,12 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt if (!isStorageOrMedia) { continue; } + boolean isSystemOrPolicyFixed = (getPermissionFlags(newPackage.getPackageName(), + permInfo.name, userId) & (FLAG_PERMISSION_SYSTEM_FIXED + | FLAG_PERMISSION_POLICY_FIXED)) != 0; + if (isSystemOrPolicyFixed) { + continue; + } EventLog.writeEvent(0x534e4554, "171430330", newPackage.getUid(), "Revoking permission " + permInfo.name + " from package " diff --git a/services/core/java/com/android/server/uri/UriPermissionOwner.java b/services/core/java/com/android/server/uri/UriPermissionOwner.java index d22bb58f6ded..b5e7cb057717 100644 --- a/services/core/java/com/android/server/uri/UriPermissionOwner.java +++ b/services/core/java/com/android/server/uri/UriPermissionOwner.java @@ -155,18 +155,22 @@ public class UriPermissionOwner { public void removeReadPermission(UriPermission perm) { synchronized (this) { - mReadPerms.remove(perm); - if (mReadPerms.isEmpty()) { - mReadPerms = null; + if (mReadPerms != null) { + mReadPerms.remove(perm); + if (mReadPerms.isEmpty()) { + mReadPerms = null; + } } } } public void removeWritePermission(UriPermission perm) { synchronized (this) { - mWritePerms.remove(perm); - if (mWritePerms.isEmpty()) { - mWritePerms = null; + if (mWritePerms != null) { + mWritePerms.remove(perm); + if (mWritePerms.isEmpty()) { + mWritePerms = null; + } } } } diff --git a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java index 6d01123f6ead..e13ec6c2d4ce 100644 --- a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java +++ b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java @@ -37,7 +37,7 @@ final class SetAmplitudeVibratorStep extends AbstractVibratorStep { * The repeating waveform keeps the vibrator ON all the time. Use a minimum duration to * prevent short patterns from turning the vibrator ON too frequently. */ - private static final int REPEATING_EFFECT_ON_DURATION = 5000; // 5s + static final int REPEATING_EFFECT_ON_DURATION = 5000; // 5s SetAmplitudeVibratorStep(VibrationStepConductor conductor, long startTime, VibratorController controller, VibrationEffect.Composed effect, int index, diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index cc130c407690..f9dac3ad930d 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -621,6 +621,13 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { mFinishingActivities.remove(r); stopWaitingForActivityVisible(r); + + final Task task = r.getTask(); + if (task != null && task.mKillProcessesOnDestroyed && task.getTopMostActivity() == r) { + // The activity is destroyed or its process is died, so cancel the pending kill. + task.mKillProcessesOnDestroyed = false; + removeTimeoutOfKillProcessesOnDestroyed(task); + } } /** There is no valid launch time, just stop waiting. */ @@ -1903,9 +1910,13 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { killTaskProcessesIfPossible(task); } + private void removeTimeoutOfKillProcessesOnDestroyed(Task task) { + mHandler.removeMessages(KILL_TASK_PROCESSES_TIMEOUT_MSG, task); + } + void killTaskProcessesOnDestroyedIfNeeded(Task task) { if (task == null || !task.mKillProcessesOnDestroyed) return; - mHandler.removeMessages(KILL_TASK_PROCESSES_TIMEOUT_MSG, task); + removeTimeoutOfKillProcessesOnDestroyed(task); killTaskProcessesIfPossible(task); } @@ -2778,7 +2789,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } break; case KILL_TASK_PROCESSES_TIMEOUT_MSG: { final Task task = (Task) msg.obj; - if (task.mKillProcessesOnDestroyed) { + if (task.mKillProcessesOnDestroyed && task.hasActivity()) { Slog.i(TAG, "Destroy timeout of remove-task, attempt to kill " + task); killTaskProcessesIfPossible(task); } diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 394105a646f1..7d3c87a7556b 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -37,6 +37,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER; import static android.content.pm.ActivityInfo.isFixedOrientation; import static android.content.pm.ActivityInfo.isFixedOrientationLandscape; import static android.content.pm.ActivityInfo.screenOrientationToString; @@ -44,6 +45,7 @@ import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9; import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2; import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3; import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN; import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN; import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; @@ -642,6 +644,10 @@ final class LetterboxUiController { @ScreenOrientation int overrideOrientationIfNeeded(@ScreenOrientation int candidate) { + if (shouldApplyUserFullscreenOverride()) { + return SCREEN_ORIENTATION_USER; + } + // In some cases (e.g. Kids app) we need to map the candidate orientation to some other // orientation. candidate = mActivityRecord.mWmService.mapOrientationRequest(candidate); @@ -1103,20 +1109,28 @@ final class LetterboxUiController { } boolean shouldApplyUserMinAspectRatioOverride() { - if (!mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()) { + if (!mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled() + || mActivityRecord.mDisplayContent == null + || !mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) { return false; } - try { - final int userAspectRatio = mActivityRecord.mAtmService.getPackageManager() - .getUserMinAspectRatio(mActivityRecord.packageName, mActivityRecord.mUserId); - mUserAspectRatio = userAspectRatio; - return userAspectRatio != USER_MIN_ASPECT_RATIO_UNSET; - } catch (RemoteException e) { - // Don't apply user aspect ratio override - Slog.w(TAG, "Exception thrown retrieving aspect ratio user override " + this, e); + mUserAspectRatio = getUserMinAspectRatioOverrideCode(); + + return mUserAspectRatio != USER_MIN_ASPECT_RATIO_UNSET + && mUserAspectRatio != USER_MIN_ASPECT_RATIO_FULLSCREEN; + } + + boolean shouldApplyUserFullscreenOverride() { + if (!mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled() + || mActivityRecord.mDisplayContent == null + || !mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) { return false; } + + mUserAspectRatio = getUserMinAspectRatioOverrideCode(); + + return mUserAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN; } float getUserMinAspectRatio() { @@ -1137,6 +1151,16 @@ final class LetterboxUiController { } } + private int getUserMinAspectRatioOverrideCode() { + try { + return mActivityRecord.mAtmService.getPackageManager() + .getUserMinAspectRatio(mActivityRecord.packageName, mActivityRecord.mUserId); + } catch (RemoteException e) { + Slog.w(TAG, "Exception thrown retrieving aspect ratio user override " + this, e); + } + return mUserAspectRatio; + } + private float getDisplaySizeMinAspectRatio() { final DisplayArea displayArea = mActivityRecord.getDisplayArea(); if (displayArea == null) { diff --git a/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java index b53ee4b6afb2..4e9b6c78edfd 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java @@ -49,7 +49,7 @@ public class LocationFudgerTest { private static final double APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR = 111_000; private static final float ACCURACY_M = 2000; private static final float MAX_COARSE_FUDGE_DISTANCE_M = - (float) Math.sqrt(2 * ACCURACY_M * ACCURACY_M); + (float) Math.sqrt(2 * ACCURACY_M * ACCURACY_M) + ACCURACY_M / 4f; private Random mRandom; diff --git a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java index 2f039654ca7e..58cdb1b79d83 100644 --- a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java @@ -53,7 +53,6 @@ import com.android.server.policy.WindowManagerPolicy; import com.android.server.power.batterysaver.BatterySaverController; import com.android.server.power.batterysaver.BatterySaverPolicy; import com.android.server.power.batterysaver.BatterySavingStats; -import com.android.server.power.stats.BatteryStatsImpl; import com.android.server.statusbar.StatusBarManagerInternal; import org.junit.Before; @@ -78,7 +77,6 @@ public class NotifierTest { @Mock private AmbientDisplayConfiguration mAmbientDisplayConfigurationMock; @Mock private SystemPropertiesWrapper mSystemPropertiesMock; @Mock private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock; - @Mock private BatteryStatsImpl mBatteryStats; @Mock private Vibrator mVibrator; @Mock private StatusBarManagerInternal mStatusBarManagerInternal; diff --git a/services/tests/powerservicetests/src/com/android/server/power/ShutdownCheckPointsTest.java b/services/tests/powerservicetests/src/com/android/server/power/ShutdownCheckPointsTest.java index fe6cc28f03d3..67ed6b740c4b 100644 --- a/services/tests/powerservicetests/src/com/android/server/power/ShutdownCheckPointsTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/ShutdownCheckPointsTest.java @@ -366,7 +366,7 @@ public class ShutdownCheckPointsTest { } /** Fake system dependencies for testing. */ - private final class TestInjector implements ShutdownCheckPoints.Injector { + private static final class TestInjector implements ShutdownCheckPoints.Injector { private long mNow; private int mCheckPointsLimit; private int mDumpFilesLimit; diff --git a/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java b/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java index 7af4b3d87a3a..0fad25d35515 100644 --- a/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java @@ -424,7 +424,7 @@ public class WakeLockLogTest { return sw.toString(); } - public class TestInjector extends WakeLockLog.Injector { + public static class TestInjector extends WakeLockLog.Injector { private final int mTagDatabaseSize; private final int mLogSize; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java index 68bb5a4878c3..4d3bd92551d7 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java @@ -36,6 +36,7 @@ import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -165,6 +166,8 @@ public class FullScreenMagnificationGestureHandlerTest { WindowMagnificationPromptController mWindowMagnificationPromptController; @Mock AccessibilityTraceManager mMockTraceManager; + @Mock + FullScreenMagnificationVibrationHelper mMockFullScreenMagnificationVibrationHelper; @Rule public final TestableContext mContext = new TestableContext(getInstrumentation().getContext()); @@ -247,7 +250,8 @@ public class FullScreenMagnificationGestureHandlerTest { FullScreenMagnificationGestureHandler h = new FullScreenMagnificationGestureHandler( mContext, mFullScreenMagnificationController, mMockTraceManager, mMockCallback, detectTripleTap, detectShortcutTrigger, - mWindowMagnificationPromptController, DISPLAY_0); + mWindowMagnificationPromptController, DISPLAY_0, + mMockFullScreenMagnificationVibrationHelper); h.setSinglePanningEnabled(true); mHandler = new TestHandler(h.mDetectingState, mClock) { @Override @@ -634,6 +638,52 @@ public class FullScreenMagnificationGestureHandlerTest { } @Test + public void testScroll_singleHorizontalPanningAndAtEdge_vibrate() { + goFromStateIdleTo(STATE_SINGLE_PANNING); + mFullScreenMagnificationController.setCenter( + DISPLAY_0, + INITIAL_MAGNIFICATION_BOUNDS.left, + INITIAL_MAGNIFICATION_BOUNDS.top / 2, + false, + 1); + final float swipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop() + 1; + PointF initCoords = + new PointF( + mFullScreenMagnificationController.getCenterX(DISPLAY_0), + mFullScreenMagnificationController.getCenterY(DISPLAY_0)); + PointF endCoords = new PointF(initCoords.x, initCoords.y); + endCoords.offset(swipeMinDistance, 0); + allowEventDelegation(); + + swipeAndHold(initCoords, endCoords); + + verify(mMockFullScreenMagnificationVibrationHelper).vibrateIfSettingEnabled(); + } + + @Test + public void testScroll_singleVerticalPanningAndAtEdge_doNotVibrate() { + goFromStateIdleTo(STATE_SINGLE_PANNING); + mFullScreenMagnificationController.setCenter( + DISPLAY_0, + INITIAL_MAGNIFICATION_BOUNDS.left, + INITIAL_MAGNIFICATION_BOUNDS.top, + false, + 1); + final float swipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop() + 1; + PointF initCoords = + new PointF( + mFullScreenMagnificationController.getCenterX(DISPLAY_0), + mFullScreenMagnificationController.getCenterY(DISPLAY_0)); + PointF endCoords = new PointF(initCoords.x, initCoords.y); + endCoords.offset(0, swipeMinDistance); + allowEventDelegation(); + + swipeAndHold(initCoords, endCoords); + + verify(mMockFullScreenMagnificationVibrationHelper, never()).vibrateIfSettingEnabled(); + } + + @Test public void testShortcutTriggered_invokeShowWindowPromptAction() { goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationVibrationHelperTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationVibrationHelperTest.java new file mode 100644 index 000000000000..85638818f156 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationVibrationHelperTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.accessibility.magnification; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.provider.Settings; +import android.testing.TestableContext; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link FullScreenMagnificationVibrationHelper}. + */ +public class FullScreenMagnificationVibrationHelperTest { + private static final long VIBRATION_DURATION_MS = 10L; + private static final int VIBRATION_AMPLITUDE = VibrationEffect.MAX_AMPLITUDE / 2; + + + @Rule + public final TestableContext mContext = new TestableContext(getInstrumentation().getContext()); + @Mock + Vibrator mMockVibrator; + + private FullScreenMagnificationVibrationHelper mFullScreenMagnificationVibrationHelper; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext.addMockSystemService(Vibrator.class, mMockVibrator); + mFullScreenMagnificationVibrationHelper = new FullScreenMagnificationVibrationHelper( + mContext); + mFullScreenMagnificationVibrationHelper.mIsVibrationEffectSupportedProvider = () -> true; + } + + @Test + public void edgeHapticSettingEnabled_vibrate() { + setEdgeHapticSettingEnabled(true); + when(mMockVibrator.hasVibrator()).thenReturn(true); + + mFullScreenMagnificationVibrationHelper.vibrateIfSettingEnabled(); + + verify(mMockVibrator).vibrate(any()); + } + + @Test + public void edgeHapticSettingDisabled_doNotVibrate() { + setEdgeHapticSettingEnabled(false); + when(mMockVibrator.hasVibrator()).thenReturn(true); + + mFullScreenMagnificationVibrationHelper.vibrateIfSettingEnabled(); + + verify(mMockVibrator, never()).vibrate(any()); + } + + @Test + public void hasNoVibrator_doNotVibrate() { + setEdgeHapticSettingEnabled(true); + when(mMockVibrator.hasVibrator()).thenReturn(false); + + mFullScreenMagnificationVibrationHelper.vibrateIfSettingEnabled(); + + verify(mMockVibrator, never()).vibrate(any()); + } + + @Test + public void notSupportVibrationEffect_vibrateOneShotEffect() { + setEdgeHapticSettingEnabled(true); + when(mMockVibrator.hasVibrator()).thenReturn(true); + mFullScreenMagnificationVibrationHelper.mIsVibrationEffectSupportedProvider = () -> false; + + mFullScreenMagnificationVibrationHelper.vibrateIfSettingEnabled(); + + verify(mMockVibrator).vibrate(eq(VibrationEffect.createOneShot(VIBRATION_DURATION_MS, + VIBRATION_AMPLITUDE))); + } + + + private boolean setEdgeHapticSettingEnabled(boolean enabled) { + return Settings.Secure.putInt( + mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_EDGE_HAPTIC_ENABLED, + enabled ? 1 : 0); + } +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java index c3beff74ca1c..02bed229d181 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java @@ -81,6 +81,20 @@ public class SystemAudioAutoInitiationActionTest { protected void writeStringSystemProperty(String key, String value) { // do nothing } + + /** + * Override displayOsd to prevent it from broadcasting an intent, which + * can trigger a SecurityException. + */ + @Override + void displayOsd(int messageId) { + // do nothing + } + + @Override + void displayOsd(int messageId, int extra) { + // do nothing + } }; mHdmiControlService.setIoLooper(myLooper); 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 74d996c4bfda..709e9c3a2cb1 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java @@ -69,7 +69,6 @@ import com.android.server.LocalServices; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.mockito.InOrder; @@ -451,21 +450,21 @@ public class VibrationThreadTest { fakeVibrator.getEffectSegments(vibrationId)); } - @Ignore("b/290940400") @LargeTest @Test public void vibrate_singleVibratorRepeatingAlwaysOnWaveform_turnsVibratorBackOn() throws Exception { FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); + int expectedOnDuration = SetAmplitudeVibratorStep.REPEATING_EFFECT_ON_DURATION; - int[] amplitudes = new int[]{1, 2}; VibrationEffect effect = VibrationEffect.createWaveform( - new long[]{4900, 50}, amplitudes, 0); + /* timings= */ new long[]{expectedOnDuration - 100, 50}, + /* amplitudes= */ new int[]{1, 2}, /* repeat= */ 0); long vibrationId = startThreadAndDispatcher(effect); assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibrationId).size() > 1, - 5000 + TEST_TIMEOUT_MILLIS)); + expectedOnDuration + TEST_TIMEOUT_MILLIS)); mVibrationConductor.notifyCancelled( new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER), /* immediate= */ false); @@ -473,12 +472,12 @@ public class VibrationThreadTest { verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); - // First time turn vibrator ON for minimum of 5s. - assertEquals(5000L, fakeVibrator.getEffectSegments(vibrationId).get(0).getDuration()); - // Vibrator turns off in the middle of the second execution of first step, turn it back ON - // for another 5s + remaining of 850ms. - assertEquals(4900 + 50 + 4900, - fakeVibrator.getEffectSegments(vibrationId).get(1).getDuration(), /* delta= */ 20); + List<VibrationEffectSegment> effectSegments = fakeVibrator.getEffectSegments(vibrationId); + // First time, turn vibrator ON for the expected fixed duration. + assertEquals(expectedOnDuration, effectSegments.get(0).getDuration()); + // Vibrator turns off in the middle of the second execution of the first step. Expect it to + // be turned back ON at least for the fixed duration + the remaining duration of the step. + assertTrue(expectedOnDuration < effectSegments.get(1).getDuration()); // Set amplitudes for a cycle {1, 2}, start second loop then turn it back on to same value. assertEquals(expectedAmplitudes(1, 2, 1, 1), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().subList(0, 4)); 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 81a37943505f..72ab18dca02f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -36,6 +36,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER; 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; @@ -778,6 +779,34 @@ public class LetterboxUiControllerTest extends WindowTestsBase { /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_PORTRAIT); } + @Test + public void testOverrideOrientationIfNeeded_userFullscreenOverride_returnsUser() { + spyOn(mController); + doReturn(true).when(mController).shouldApplyUserFullscreenOverride(); + + assertEquals(mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_USER); + } + + @Test + @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT, OVERRIDE_ANY_ORIENTATION}) + public void testOverrideOrientationIfNeeded_userFullScreenOverrideOverSystem_returnsUser() { + spyOn(mController); + doReturn(true).when(mController).shouldApplyUserFullscreenOverride(); + + assertEquals(mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_USER); + } + + @Test + public void testOverrideOrientationIfNeeded_userFullScreenOverrideDisabled_returnsUnchanged() { + spyOn(mController); + doReturn(false).when(mController).shouldApplyUserFullscreenOverride(); + + assertEquals(mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_PORTRAIT); + } + // shouldUseDisplayLandscapeNaturalOrientation @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index 7cb58022c0e7..77d5908c63a5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -1038,6 +1038,13 @@ public class RecentTasksTest extends WindowTestsBase { top.setState(ActivityRecord.State.DESTROYING, "test"); top.destroyed("test"); assertFalse(task.mKillProcessesOnDestroyed); + + // If the process is died, the state should be cleared. + final Task lastTask = tasks.get(0); + lastTask.addChild(top); + lastTask.mKillProcessesOnDestroyed = true; + top.handleAppDied(); + assertFalse(lastTask.mKillProcessesOnDestroyed); } @Test diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index fdcb9749c38e..53154e6063b2 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -358,6 +358,8 @@ interface ITelecomService { int cleanupOrphanPhoneAccounts(); + boolean isNonUiInCallServiceBound(in String packageName); + void resetCarMode(); void setTestDefaultCallRedirectionApp(String packageName); diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt index 7457e8049663..d41840746cf9 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt @@ -67,9 +67,11 @@ class HorizontalSplitChangeRatioTest(flicker: LegacyFlickerTest) : } @FlakyTest(bugId = 293075402) + @Test override fun backgroundLayerNeverVisible() = super.backgroundLayerNeverVisible() @FlakyTest(bugId = 293075402) + @Test override fun visibleLayersShownMoreThanOneConsecutiveEntry() = super.visibleLayersShownMoreThanOneConsecutiveEntry() diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt index 6984762c19bb..e9e2b637f2d1 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt @@ -104,7 +104,8 @@ class OpenTrampolineActivityTest(flicker: LegacyFlickerTest) : ActivityEmbedding flicker.assertWm { notContains(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) .then() - .isAppWindowInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + .isAppWindowInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT, + isOptional = true) .then() .isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) } @@ -164,7 +165,6 @@ class OpenTrampolineActivityTest(flicker: LegacyFlickerTest) : ActivityEmbedding @FlakyTest(bugId = 290736037) /** Main activity should go from fullscreen to being a split with secondary activity. */ - @Presubmit @Test fun mainActivityLayerGoesFromFullscreenToSplit() { flicker.assertLayers { @@ -196,6 +196,12 @@ class OpenTrampolineActivityTest(flicker: LegacyFlickerTest) : ActivityEmbedding } } + @FlakyTest(bugId = 288591571) + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + super.visibleLayersShownMoreThanOneConsecutiveEntry() + } + companion object { /** {@inheritDoc} */ private var startDisplayBounds = Rect.EMPTY diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/Utils.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/Utils.kt index 276f97962d7f..8242e9a31992 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/service/Utils.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/Utils.kt @@ -22,7 +22,9 @@ import android.platform.test.rule.PressHomeRule import android.platform.test.rule.UnlockScreenRule import android.tools.common.NavBar import android.tools.common.Rotation +import android.tools.device.apphelpers.MessagingAppHelper import android.tools.device.flicker.rules.ChangeDisplayOrientationRule +import android.tools.device.flicker.rules.LaunchAppRule import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule import androidx.test.platform.app.InstrumentationRegistry import org.junit.rules.RuleChain @@ -35,6 +37,9 @@ object Utils { .around( NavigationModeRule(navigationMode.value, /* changeNavigationModeAfterTest */ false) ) + .around( + LaunchAppRule(MessagingAppHelper(instrumentation), clearCacheAfterParsing = false) + ) .around(RemoveAllTasksButHomeRule()) .around( ChangeDisplayOrientationRule( diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp index a43bf1b60f42..6bf265d2e363 100644 --- a/tools/aapt2/dump/DumpManifest.cpp +++ b/tools/aapt2/dump/DumpManifest.cpp @@ -837,9 +837,9 @@ class UsesSdkBadging : public ManifestExtractor::Element { void Print(text::Printer* printer) override { if (min_sdk) { - printer->Print(StringPrintf("sdkVersion:'%d'\n", *min_sdk)); + printer->Print(StringPrintf("minSdkVersion:'%d'\n", *min_sdk)); } else if (min_sdk_name) { - printer->Print(StringPrintf("sdkVersion:'%s'\n", min_sdk_name->data())); + printer->Print(StringPrintf("minSdkVersion:'%s'\n", min_sdk_name->data())); } if (max_sdk) { printer->Print(StringPrintf("maxSdkVersion:'%d'\n", *max_sdk)); diff --git a/tools/aapt2/integration-tests/DumpTest/built_with_aapt_expected.txt b/tools/aapt2/integration-tests/DumpTest/built_with_aapt_expected.txt index cc0b3bf5d2fb..d14e5fb5c477 100644 --- a/tools/aapt2/integration-tests/DumpTest/built_with_aapt_expected.txt +++ b/tools/aapt2/integration-tests/DumpTest/built_with_aapt_expected.txt @@ -1,5 +1,5 @@ package: name='com.aapt.app' versionCode='222' versionName='222' platformBuildVersionName='12' platformBuildVersionCode='32' compileSdkVersion='32' compileSdkVersionCodename='12' -sdkVersion:'22' +minSdkVersion:'22' targetSdkVersion:'32' application: label='App' icon='' feature-group: label='' diff --git a/tools/aapt2/integration-tests/DumpTest/components_expected.txt b/tools/aapt2/integration-tests/DumpTest/components_expected.txt index 9c81fb83ca15..93cce0a29024 100644 --- a/tools/aapt2/integration-tests/DumpTest/components_expected.txt +++ b/tools/aapt2/integration-tests/DumpTest/components_expected.txt @@ -1,5 +1,5 @@ package: name='com.example.bundletool.minimal' versionCode='1' versionName='1.0' platformBuildVersionName='12' platformBuildVersionCode='31' compileSdkVersion='31' compileSdkVersionCodename='12' -sdkVersion:'21' +minSdkVersion:'21' targetSdkVersion:'31' uses-configuration: reqTouchScreen='3' reqKeyboardType='2' reqHardKeyboard='-1' reqNavigation='3' reqFiveWayNav='-1' supports-gl-texture:'GL_OES_compressed_paletted_texture' diff --git a/tools/aapt2/integration-tests/DumpTest/minimal_expected.txt b/tools/aapt2/integration-tests/DumpTest/minimal_expected.txt index 85ab5d80cd39..aafca68443a9 100644 --- a/tools/aapt2/integration-tests/DumpTest/minimal_expected.txt +++ b/tools/aapt2/integration-tests/DumpTest/minimal_expected.txt @@ -1,5 +1,5 @@ package: name='com.lato.bubblegirl' versionCode='33' versionName='1.0.0' platformBuildVersionName='8.1.0' platformBuildVersionCode='27' -sdkVersion:'19' +minSdkVersion:'19' targetSdkVersion:'26' application-label:'Bubble Girl' application-label-ar:'Bubble Girl' diff --git a/tools/aapt2/integration-tests/DumpTest/multiple_uses_sdk_expected.txt b/tools/aapt2/integration-tests/DumpTest/multiple_uses_sdk_expected.txt index 85e8d0a3cbba..f48f381e3012 100644 --- a/tools/aapt2/integration-tests/DumpTest/multiple_uses_sdk_expected.txt +++ b/tools/aapt2/integration-tests/DumpTest/multiple_uses_sdk_expected.txt @@ -1,5 +1,5 @@ package: name='com.test.e17wmultiapknexus' versionCode='107' versionName='14' platformBuildVersionName='2.3.3' platformBuildVersionCode='10' -sdkVersion:'1' +minSdkVersion:'1' application-label:'w45wmultiapknexus_10' application-icon-120:'res/drawable-ldpi-v4/icon.png' application-icon-160:'res/drawable-mdpi-v4/icon.png' |