diff options
162 files changed, 2407 insertions, 1908 deletions
diff --git a/Android.bp b/Android.bp index 5ada10d19f5d..8d7ab983593d 100644 --- a/Android.bp +++ b/Android.bp @@ -386,6 +386,7 @@ java_defaults { // TODO(b/120066492): remove gps_debug and protolog.conf.json when the build // system propagates "required" properly. "gps_debug.conf", + "protolog.conf.json.gz", "core.protolog.pb", "framework-res", // any install dependencies should go into framework-minus-apex-install-dependencies diff --git a/Ravenwood.bp b/Ravenwood.bp index 4791640239b3..f43c37bf637d 100644 --- a/Ravenwood.bp +++ b/Ravenwood.bp @@ -205,8 +205,10 @@ android_ravenwood_libgroup { // Provide runtime versions of utils linked in below "junit", "truth", + "flag-junit", "ravenwood-framework", "ravenwood-junit-impl", + "ravenwood-junit-impl-flag", "mockito-ravenwood-prebuilt", "inline-mockito-ravenwood-prebuilt", ], @@ -220,6 +222,7 @@ android_ravenwood_libgroup { libs: [ "junit", "truth", + "flag-junit", "ravenwood-framework", "ravenwood-junit", "mockito-ravenwood-prebuilt", diff --git a/apct-tests/perftests/rubidium/src/android/rubidium/js/JSScriptEnginePerfTests.java b/apct-tests/perftests/rubidium/src/android/rubidium/js/JSScriptEnginePerfTests.java index ba15796f47fe..fc3738c7134a 100644 --- a/apct-tests/perftests/rubidium/src/android/rubidium/js/JSScriptEnginePerfTests.java +++ b/apct-tests/perftests/rubidium/src/android/rubidium/js/JSScriptEnginePerfTests.java @@ -53,6 +53,7 @@ import com.android.adservices.service.adselection.AdSelectionConfigArgumentUtil; import com.android.adservices.service.adselection.AdWithBidArgumentUtil; import com.android.adservices.service.adselection.CustomAudienceBiddingSignalsArgumentUtil; import com.android.adservices.service.adselection.CustomAudienceScoringSignalsArgumentUtil; +import com.android.adservices.service.common.NoOpRetryStrategyImpl; import com.android.adservices.service.js.IsolateSettings; import com.android.adservices.service.js.JSScriptArgument; import com.android.adservices.service.js.JSScriptArrayArgument; @@ -411,7 +412,8 @@ public class JSScriptEnginePerfTests { jsScript, args, functionName, - IsolateSettings.forMaxHeapSizeEnforcementDisabled()); + IsolateSettings.forMaxHeapSizeEnforcementDisabled(), + new NoOpRetryStrategyImpl()); result.addListener(resultLatch::countDown, sExecutorService); return result; } @@ -430,7 +432,8 @@ public class JSScriptEnginePerfTests { wasmScript, args, functionName, - IsolateSettings.forMaxHeapSizeEnforcementDisabled()); + IsolateSettings.forMaxHeapSizeEnforcementDisabled(), + new NoOpRetryStrategyImpl()); result.addListener(resultLatch::countDown, sExecutorService); return result; } diff --git a/apex/jobscheduler/service/Android.bp b/apex/jobscheduler/service/Android.bp index 558629537253..0104ee14fec4 100644 --- a/apex/jobscheduler/service/Android.bp +++ b/apex/jobscheduler/service/Android.bp @@ -21,6 +21,7 @@ java_library { libs: [ "app-compat-annotations", + "error_prone_annotations", "framework", "services.core", "unsupportedappusage", diff --git a/core/api/test-current.txt b/core/api/test-current.txt index af40c3d658a7..a28dc497c508 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1547,6 +1547,10 @@ package android.hardware.biometrics { method public boolean isAllowBackgroundAuthentication(); } + public abstract static class BiometricPrompt.AuthenticationCallback { + method @FlaggedApi("android.hardware.biometrics.face_background_authentication") public void onAuthenticationAcquired(int); + } + public static class BiometricPrompt.Builder { method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.TEST_BIOMETRIC, "android.permission.USE_BIOMETRIC_INTERNAL"}) public android.hardware.biometrics.BiometricPrompt.Builder setAllowBackgroundAuthentication(boolean); method @FlaggedApi("android.multiuser.enable_biometrics_to_unlock_private_space") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.TEST_BIOMETRIC, "android.permission.USE_BIOMETRIC_INTERNAL"}) public android.hardware.biometrics.BiometricPrompt.Builder setAllowBackgroundAuthentication(boolean, boolean); @@ -1565,6 +1569,7 @@ package android.hardware.biometrics { } public class SensorProperties { + ctor @FlaggedApi("android.hardware.biometrics.face_background_authentication") public SensorProperties(int, int, @NonNull java.util.List<android.hardware.biometrics.SensorProperties.ComponentInfo>); method @NonNull public java.util.List<android.hardware.biometrics.SensorProperties.ComponentInfo> getComponentInfo(); method public int getSensorId(); method public int getSensorStrength(); @@ -1709,6 +1714,18 @@ package android.hardware.display { } +package android.hardware.face { + + @FlaggedApi("android.hardware.biometrics.face_background_authentication") public class FaceManager { + method @FlaggedApi("android.hardware.biometrics.face_background_authentication") @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public android.hardware.biometrics.BiometricTestSession createTestSession(int); + method @FlaggedApi("android.hardware.biometrics.face_background_authentication") @NonNull public java.util.List<android.hardware.face.FaceSensorProperties> getSensorProperties(); + } + + @FlaggedApi("android.hardware.biometrics.face_background_authentication") public class FaceSensorProperties extends android.hardware.biometrics.SensorProperties { + } + +} + package android.hardware.fingerprint { @Deprecated public class FingerprintManager { @@ -3944,6 +3961,7 @@ package android.view.inputmethod { method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodListAsUser(int); method public boolean hasActiveInputConnection(@Nullable android.view.View); method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean hasPendingImeVisibilityRequests(); + method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void hideSoftInputFromServerForTest(); method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isCurrentRootView(@NonNull android.view.View); method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isInputMethodPickerShown(); method @FlaggedApi("android.view.inputmethod.imm_userhandle_hostsidetests") @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public boolean isStylusHandwritingAvailableAsUser(@NonNull android.os.UserHandle); diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java index 79696e047904..48081bb04863 100644 --- a/core/java/android/app/servertransaction/ClientTransaction.java +++ b/core/java/android/app/servertransaction/ClientTransaction.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.app.ClientTransactionHandler; import android.app.IApplicationThread; import android.compat.annotation.UnsupportedAppUsage; +import android.os.Build; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; @@ -54,10 +55,12 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { @Nullable private List<ClientTransactionItem> mTransactionItems; - /** A list of individual callbacks to a client. */ - // TODO(b/324203798): cleanup after remove UnsupportedAppUsage - @UnsupportedAppUsage + /** @deprecated use {@link #getTransactionItems} instead. */ @Nullable + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, + trackingBug = 324203798, + publicAlternatives = "Use {@code #getTransactionItems()}") + @Deprecated private List<ClientTransactionItem> mActivityCallbacks; /** @@ -126,42 +129,42 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { setActivityTokenIfNotSet(activityCallback); } - /** - * Gets the list of callbacks. - * @deprecated use {@link #getTransactionItems()} instead. - */ - // TODO(b/324203798): cleanup after remove UnsupportedAppUsage - @Nullable + /** @deprecated use {@link #getTransactionItems()} instead. */ @VisibleForTesting - @UnsupportedAppUsage + @Nullable + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, + trackingBug = 324203798, + publicAlternatives = "Use {@code #getTransactionItems()}") @Deprecated public List<ClientTransactionItem> getCallbacks() { return mActivityCallbacks; } /** - * @deprecated a transaction can contain {@link ClientTransactionItem} of different activities, + * A transaction can contain {@link ClientTransactionItem} of different activities, * this must not be used. For any unsupported app usages, please be aware that this is set to * the activity of the first item in {@link #getTransactionItems()}. + * + * @deprecated use {@link ClientTransactionItem#getActivityToken()} instead. */ - // TODO(b/324203798): cleanup after remove UnsupportedAppUsage @VisibleForTesting @Nullable - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, + trackingBug = 324203798, + publicAlternatives = "Use {@code android.app.servertransaction" + + ".ClientTransactionItem#getActivityToken()}") @Deprecated public IBinder getActivityToken() { return mActivityToken; } - /** - * Gets the target state lifecycle request. - * @deprecated use {@link #getTransactionItems()} instead. - */ - // TODO(b/324203798): cleanup after remove UnsupportedAppUsage + /** @deprecated use {@link #getTransactionItems()} instead. */ @VisibleForTesting(visibility = PACKAGE) - @UnsupportedAppUsage - @Deprecated @Nullable + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, + trackingBug = 324203798, + publicAlternatives = "Use {@code #getTransactionItems()}") + @Deprecated public ActivityLifecycleItem getLifecycleStateRequest() { return mLifecycleStateRequest; } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index fd2af99b6a7e..42dd87a711f3 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1064,7 +1064,11 @@ public class Intent implements Parcelable, Cloneable { } if (sender != null) { - intent.putExtra(EXTRA_CHOOSER_RESULT_INTENT_SENDER, sender); + if (android.service.chooser.Flags.enableChooserResult()) { + intent.putExtra(EXTRA_CHOOSER_RESULT_INTENT_SENDER, sender); + } else { + intent.putExtra(EXTRA_CHOSEN_COMPONENT_INTENT_SENDER, sender); + } } // Migrate any clip data and flags from target. diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index 0208fed6040f..d9d4305cc630 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -23,6 +23,7 @@ import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; import static android.hardware.biometrics.BiometricManager.Authenticators; import static android.hardware.biometrics.Flags.FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT; import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT; +import static android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION; import static android.hardware.biometrics.Flags.FLAG_GET_OP_ID_CRYPTO_OBJECT; import static android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE; @@ -1127,6 +1128,8 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan * @hide */ @Override + @TestApi + @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION) public void onAuthenticationAcquired(int acquireInfo) {} /** diff --git a/core/java/android/hardware/biometrics/SensorProperties.java b/core/java/android/hardware/biometrics/SensorProperties.java index 3b9cad49250f..16f71414c8c9 100644 --- a/core/java/android/hardware/biometrics/SensorProperties.java +++ b/core/java/android/hardware/biometrics/SensorProperties.java @@ -16,6 +16,9 @@ package android.hardware.biometrics; +import static android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION; + +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.TestApi; @@ -141,8 +144,10 @@ public class SensorProperties { /** * @hide */ + @TestApi + @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION) public SensorProperties(int sensorId, @Strength int sensorStrength, - List<ComponentInfo> componentInfo) { + @NonNull List<ComponentInfo> componentInfo) { mSensorId = sensorId; mSensorStrength = sensorStrength; mComponentInfo = componentInfo; diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index 066c45f03ec4..1b0a485bdbda 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -18,6 +18,7 @@ package android.hardware.face; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.MANAGE_BIOMETRIC; +import static android.Manifest.permission.TEST_BIOMETRIC; import static android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION; import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE; @@ -29,6 +30,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.TestApi; import android.content.Context; import android.content.pm.PackageManager; import android.hardware.biometrics.BiometricAuthenticator; @@ -36,6 +38,7 @@ import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricFaceConstants; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.BiometricStateListener; +import android.hardware.biometrics.BiometricTestSession; import android.hardware.biometrics.CryptoObject; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.os.Binder; @@ -69,6 +72,7 @@ import java.util.concurrent.Executor; */ @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION) @SystemApi +@TestApi @SystemService(Context.FACE_SERVICE) public class FaceManager implements BiometricAuthenticator, BiometricFaceConstants { @@ -780,6 +784,8 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan * @hide */ @NonNull + @TestApi + @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION) public List<FaceSensorProperties> getSensorProperties() { final List<FaceSensorProperties> properties = new ArrayList<>(); final List<FaceSensorPropertiesInternal> internalProperties @@ -1628,4 +1634,23 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan Slog.w(TAG, "Unknown enrollment acquired message: " + acquireInfo + ", " + vendorCode); return null; } -} + + /** + * Retrieves a test session for FaceManager. + * + * @hide + */ + @TestApi + @NonNull + @RequiresPermission(TEST_BIOMETRIC) + @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION) + public BiometricTestSession createTestSession(int sensorId) { + try { + return new BiometricTestSession(mContext, sensorId, + (context, sensorId1, callback) -> mService + .createTestSession(sensorId1, callback, context.getOpPackageName())); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +}
\ No newline at end of file diff --git a/core/java/android/hardware/face/FaceSensorProperties.java b/core/java/android/hardware/face/FaceSensorProperties.java index f61312785919..a1ddb0e3495b 100644 --- a/core/java/android/hardware/face/FaceSensorProperties.java +++ b/core/java/android/hardware/face/FaceSensorProperties.java @@ -16,8 +16,12 @@ package android.hardware.face; +import static android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION; + +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.TestApi; import android.hardware.biometrics.ComponentInfoInternal; import android.hardware.biometrics.SensorProperties; @@ -30,6 +34,8 @@ import java.util.List; * Container for face sensor properties. * @hide */ +@TestApi +@FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION) public class FaceSensorProperties extends SensorProperties { /** * @hide diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl index b98c0cb41ac9..6515312e337c 100644 --- a/core/java/android/hardware/face/IFaceService.aidl +++ b/core/java/android/hardware/face/IFaceService.aidl @@ -39,7 +39,7 @@ import android.view.Surface; interface IFaceService { // Creates a test session with the specified sensorId - @EnforcePermission("USE_BIOMETRIC_INTERNAL") + @EnforcePermission("TEST_BIOMETRIC") ITestSession createTestSession(int sensorId, ITestSessionCallback callback, String opPackageName); // Requests a proto dump of the specified sensor diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index f5b58b920efb..9dc8c5d9f2e9 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -453,7 +453,7 @@ class IInputMethodWrapper extends IInputMethod.Stub @BinderThread @Override - public void showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken, + public void showSoftInput(IBinder showInputToken, @NonNull ImeTracker.Token statsToken, @InputMethod.ShowFlags int flags, ResultReceiver resultReceiver) { ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER); mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_SHOW_SOFT_INPUT, @@ -462,7 +462,7 @@ class IInputMethodWrapper extends IInputMethod.Stub @BinderThread @Override - public void hideSoftInput(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken, + public void hideSoftInput(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken, int flags, ResultReceiver resultReceiver) { ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER); mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_HIDE_SOFT_INPUT, diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 2c7ca27e6b07..4dbdd91d5fc7 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -701,7 +701,13 @@ public class InputMethodService extends AbstractInputMethodService { */ private IBinder mCurHideInputToken; - /** The token tracking the current IME request or {@code null} otherwise. */ + /** + * The token tracking the current IME request. + * + * <p> This exists as a workaround to changing the signatures of public methods. It will get + * set to a {@code non-null} value before every call that uses it, stored locally inside the + * callee, and immediately after reset to {@code null} from the callee. + */ @Nullable private ImeTracker.Token mCurStatsToken; @@ -907,14 +913,13 @@ public class InputMethodService extends AbstractInputMethodService { @MainThread @Override public void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver, - IBinder hideInputToken, @Nullable ImeTracker.Token statsToken) { + IBinder hideInputToken, @NonNull ImeTracker.Token statsToken) { mSystemCallingHideSoftInput = true; mCurHideInputToken = hideInputToken; mCurStatsToken = statsToken; try { hideSoftInput(flags, resultReceiver); } finally { - mCurStatsToken = null; mCurHideInputToken = null; mSystemCallingHideSoftInput = false; } @@ -926,23 +931,33 @@ public class InputMethodService extends AbstractInputMethodService { @MainThread @Override public void hideSoftInput(int flags, ResultReceiver resultReceiver) { - ImeTracker.forLogging().onProgress( - mCurStatsToken, ImeTracker.PHASE_IME_HIDE_SOFT_INPUT); if (DEBUG) Log.v(TAG, "hideSoftInput()"); + + final var statsToken = mCurStatsToken != null ? mCurStatsToken + : createStatsToken(false /* show */, + SoftInputShowHideReason.HIDE_SOFT_INPUT_LEGACY_DIRECT, + ImeTracker.isFromUser(mRootView)); + mCurStatsToken = null; + + // TODO(b/148086656): Disallow IME developers from calling InputMethodImpl methods. if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R && !mSystemCallingHideSoftInput) { Log.e(TAG, "IME shouldn't call hideSoftInput on itself." + " Use requestHideSelf(int) itself"); + ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_IME_HIDE_SOFT_INPUT); return; } + ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_HIDE_SOFT_INPUT); + + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.hideSoftInput"); ImeTracing.getInstance().triggerServiceDump( "InputMethodService.InputMethodImpl#hideSoftInput", mDumper, null /* icProto */); final boolean wasVisible = isInputViewShown(); - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.hideSoftInput"); mShowInputFlags = 0; mShowInputRequested = false; + mCurStatsToken = statsToken; hideWindow(); final boolean isVisible = isInputViewShown(); final boolean visibilityChanged = isVisible != wasVisible; @@ -963,14 +978,13 @@ public class InputMethodService extends AbstractInputMethodService { @Override public void showSoftInputWithToken(@InputMethod.ShowFlags int flags, ResultReceiver resultReceiver, IBinder showInputToken, - @Nullable ImeTracker.Token statsToken) { + @NonNull ImeTracker.Token statsToken) { mSystemCallingShowSoftInput = true; mCurShowInputToken = showInputToken; mCurStatsToken = statsToken; try { showSoftInput(flags, resultReceiver); } finally { - mCurStatsToken = null; mCurShowInputToken = null; mSystemCallingShowSoftInput = false; } @@ -982,16 +996,23 @@ public class InputMethodService extends AbstractInputMethodService { @MainThread @Override public void showSoftInput(@InputMethod.ShowFlags int flags, ResultReceiver resultReceiver) { - ImeTracker.forLogging().onProgress( - mCurStatsToken, ImeTracker.PHASE_IME_SHOW_SOFT_INPUT); if (DEBUG) Log.v(TAG, "showSoftInput()"); + + final var statsToken = mCurStatsToken != null ? mCurStatsToken + : createStatsToken(true /* show */, + SoftInputShowHideReason.SHOW_SOFT_INPUT_LEGACY_DIRECT, + ImeTracker.isFromUser(mRootView)); + mCurStatsToken = null; + // TODO(b/148086656): Disallow IME developers from calling InputMethodImpl methods. if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R && !mSystemCallingShowSoftInput) { - Log.e(TAG," IME shouldn't call showSoftInput on itself." + Log.e(TAG, "IME shouldn't call showSoftInput on itself." + " Use requestShowSelf(int) itself"); + ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_IME_SHOW_SOFT_INPUT); return; } + ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_SHOW_SOFT_INPUT); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.showSoftInput"); ImeTracing.getInstance().triggerServiceDump( @@ -999,11 +1020,12 @@ public class InputMethodService extends AbstractInputMethodService { null /* icProto */); final boolean wasVisible = isInputViewShown(); if (dispatchOnShowInputRequested(flags, false)) { - ImeTracker.forLogging().onProgress(mCurStatsToken, + ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE); - showWindow(true); + mCurStatsToken = statsToken; + showWindow(true /* showInput */); } else { - ImeTracker.forLogging().onFailed(mCurStatsToken, + ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE); } setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition); @@ -1895,21 +1917,23 @@ public class InputMethodService extends AbstractInputMethodService { if (showingInput) { // If we were last showing the soft keyboard, try to do so again. if (dispatchOnShowInputRequested(showFlags, true)) { - showWindow(true); + showWindowWithToken(true /* showInput */, + SoftInputShowHideReason.RESET_NEW_CONFIGURATION); if (completions != null) { mCurCompletions = completions; onDisplayCompletions(completions); } } else { - hideWindow(); + hideWindowWithToken(SoftInputShowHideReason.RESET_NEW_CONFIGURATION); } } else if (mCandidatesVisibility == View.VISIBLE) { // If the candidates are currently visible, make sure the // window is shown for them. - showWindow(false); + showWindowWithToken(false /* showInput */, + SoftInputShowHideReason.RESET_NEW_CONFIGURATION); } else { // Otherwise hide the window. - hideWindow(); + hideWindowWithToken(SoftInputShowHideReason.RESET_NEW_CONFIGURATION); } // If user uses hard keyboard, IME button should always be shown. boolean showing = onEvaluateInputViewShown(); @@ -2368,13 +2392,15 @@ public class InputMethodService extends AbstractInputMethodService { // has not asked for the input view to be shown, then we need // to update whether the window is shown. if (shown) { - showWindow(false); + showWindowWithToken(false /* showInput */, + SoftInputShowHideReason.UPDATE_CANDIDATES_VIEW_VISIBILITY); } else { - hideWindow(); + hideWindowWithToken( + SoftInputShowHideReason.UPDATE_CANDIDATES_VIEW_VISIBILITY); } } } - + void updateCandidatesVisibility(boolean shown) { int vis = shown ? View.VISIBLE : getCandidatesHiddenVisibility(); if (mCandidatesVisibility != vis) { @@ -3009,6 +3035,19 @@ public class InputMethodService extends AbstractInputMethodService { return result; } + /** + * Utility function that creates an IME request tracking token before + * calling {@link #showWindow}. + * + * @param showInput whether the input window should be shown. + * @param reason the reason why the IME request was created. + */ + private void showWindowWithToken(boolean showInput, @SoftInputShowHideReason int reason) { + mCurStatsToken = createStatsToken(true /* show */, reason, + ImeTracker.isFromUser(mRootView)); + showWindow(showInput); + } + public void showWindow(boolean showInput) { if (DEBUG) Log.v(TAG, "Showing window: showInput=" + showInput + " mShowInputRequested=" + mShowInputRequested @@ -3018,11 +3057,20 @@ public class InputMethodService extends AbstractInputMethodService { + " mInputStarted=" + mInputStarted + " mShowInputFlags=" + mShowInputFlags); + final var statsToken = mCurStatsToken != null ? mCurStatsToken + : createStatsToken(true /* show */, + SoftInputShowHideReason.SHOW_WINDOW_LEGACY_DIRECT, + ImeTracker.isFromUser(mRootView)); + mCurStatsToken = null; + if (mInShowWindow) { Log.w(TAG, "Re-entrance in to showWindow"); + ImeTracker.forLogging().onCancelled(statsToken, ImeTracker.PHASE_IME_SHOW_WINDOW); return; } + ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_SHOW_WINDOW); + ImeTracing.getInstance().triggerServiceDump("InputMethodService#showWindow", mDumper, null /* icProto */); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.showWindow"); @@ -3046,7 +3094,7 @@ public class InputMethodService extends AbstractInputMethodService { if (DEBUG) Log.v(TAG, "showWindow: draw decorView!"); mWindow.show(); mDecorViewWasVisible = true; - applyVisibilityInInsetsConsumerIfNecessary(true); + applyVisibilityInInsetsConsumerIfNecessary(true /* setVisible */, statsToken); cancelImeSurfaceRemoval(); mInShowWindow = false; Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); @@ -3137,13 +3185,15 @@ public class InputMethodService extends AbstractInputMethodService { * Applies the IME visibility in {@link android.view.ImeInsetsSourceConsumer}. * * @param setVisible {@code true} to make it visible, false to hide it. + * @param statsToken the token tracking the current IME request. */ - private void applyVisibilityInInsetsConsumerIfNecessary(boolean setVisible) { + private void applyVisibilityInInsetsConsumerIfNecessary(boolean setVisible, + @NonNull ImeTracker.Token statsToken) { ImeTracing.getInstance().triggerServiceDump( "InputMethodService#applyVisibilityInInsetsConsumerIfNecessary", mDumper, null /* icProto */); mPrivOps.applyImeVisibilityAsync(setVisible - ? mCurShowInputToken : mCurHideInputToken, setVisible, mCurStatsToken); + ? mCurShowInputToken : mCurHideInputToken, setVisible, statsToken); } private void finishViews(boolean finishingInput) { @@ -3159,12 +3209,35 @@ public class InputMethodService extends AbstractInputMethodService { mCandidatesViewStarted = false; } + /** + * Utility function that creates an IME request tracking token before + * calling {@link #hideWindow}. + * + * @param reason the reason why the IME request was created. + */ + private void hideWindowWithToken(@SoftInputShowHideReason int reason) { + // TODO(b/303041796): this should be handled by ImeTracker.isFromUser after fixing it + // to work with onClickListeners + final boolean isFromUser = ImeTracker.isFromUser(mRootView) + || reason == SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_BACK_KEY; + mCurStatsToken = createStatsToken(false /* show */, reason, isFromUser); + hideWindow(); + } + public void hideWindow() { if (DEBUG) Log.v(TAG, "CALL: hideWindow"); + + final var statsToken = mCurStatsToken != null ? mCurStatsToken + : createStatsToken(false /* show */, + SoftInputShowHideReason.HIDE_WINDOW_LEGACY_DIRECT, + ImeTracker.isFromUser(mRootView)); + mCurStatsToken = null; + + ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_HIDE_WINDOW); ImeTracing.getInstance().triggerServiceDump("InputMethodService#hideWindow", mDumper, null /* icProto */); setImeWindowStatus(0, mBackDisposition); - applyVisibilityInInsetsConsumerIfNecessary(false); + applyVisibilityInInsetsConsumerIfNecessary(false /* setVisible */, statsToken); mWindowVisible = false; finishViews(false /* finishingInput */); if (mDecorViewVisible) { @@ -3440,9 +3513,14 @@ public class InputMethodService extends AbstractInputMethodService { private void requestHideSelf(@InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason) { + // TODO(b/303041796): this should be handled by ImeTracker.isFromUser after fixing it + // to work with onClickListeners + final boolean isFromUser = ImeTracker.isFromUser(mRootView) + || reason == SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_BACK_KEY; + final var statsToken = createStatsToken(false /* show */, reason, isFromUser); ImeTracing.getInstance().triggerServiceDump("InputMethodService#requestHideSelf", mDumper, null /* icProto */); - mPrivOps.hideMySoftInput(flags, reason); + mPrivOps.hideMySoftInput(statsToken, flags, reason); } /** @@ -3450,9 +3528,16 @@ public class InputMethodService extends AbstractInputMethodService { * interact with it. */ public final void requestShowSelf(@InputMethodManager.ShowFlags int flags) { + requestShowSelf(flags, SoftInputShowHideReason.SHOW_SOFT_INPUT_FROM_IME); + } + + private void requestShowSelf(@InputMethodManager.ShowFlags int flags, + @SoftInputShowHideReason int reason) { + final var statsToken = createStatsToken(true /* show */, reason, + ImeTracker.isFromUser(mRootView)); ImeTracing.getInstance().triggerServiceDump("InputMethodService#requestShowSelf", mDumper, null /* icProto */); - mPrivOps.showMySoftInput(flags); + mPrivOps.showMySoftInput(statsToken, flags, reason); } private boolean handleBack(boolean doIt) { @@ -3472,7 +3557,7 @@ public class InputMethodService extends AbstractInputMethodService { // If we have the window visible for some other reason -- // most likely to show candidates -- then just get rid // of it. This really shouldn't happen, but just in case... - if (doIt) hideWindow(); + if (doIt) hideWindowWithToken(SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_BACK_KEY); } return true; } @@ -3627,10 +3712,11 @@ public class InputMethodService extends AbstractInputMethodService { @InputMethodManager.HideFlags int hideFlags) { if (DEBUG) Log.v(TAG, "toggleSoftInput()"); if (isInputViewShown()) { - requestHideSelf( - hideFlags, SoftInputShowHideReason.HIDE_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT); + requestHideSelf(hideFlags, + SoftInputShowHideReason.HIDE_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT); } else { - requestShowSelf(showFlags); + requestShowSelf(showFlags, + SoftInputShowHideReason.SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT); } } @@ -4272,6 +4358,20 @@ public class InputMethodService extends AbstractInputMethodService { } /** + * Creates an IME request tracking token. + * + * @param show whether this is a show or a hide request. + * @param reason the reason why the IME request was created. + * @param isFromUser whether this request was created directly from user interaction. + */ + @NonNull + private ImeTracker.Token createStatsToken(boolean show, @SoftInputShowHideReason int reason, + boolean isFromUser) { + return ImeTracker.forLogging().onStart(show ? ImeTracker.TYPE_SHOW : ImeTracker.TYPE_HIDE, + ImeTracker.ORIGIN_IME, reason, isFromUser); + } + + /** * Performs a dump of the InputMethodService's internal state. Override * to add your own information to the dump. */ diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl index 11180aef4479..5ee526e0343d 100644 --- a/core/java/android/view/IWindow.aidl +++ b/core/java/android/view/IWindow.aidl @@ -73,7 +73,7 @@ oneway interface IWindow { * * @param types internal insets types (WindowInsets.Type.InsetsType) to show * @param fromIme true if this request originated from IME (InputMethodService). - * @param statsToken the token tracking the current IME show request or {@code null} otherwise. + * @param statsToken the token tracking the current IME request or {@code null} otherwise. */ void showInsets(int types, boolean fromIme, in @nullable ImeTracker.Token statsToken); @@ -82,7 +82,7 @@ oneway interface IWindow { * * @param types internal insets types (WindowInsets.Type.InsetsType) to hide * @param fromIme true if this request originated from IME (InputMethodService). - * @param statsToken the token tracking the current IME hide request or {@code null} otherwise. + * @param statsToken the token tracking the current IME request or {@code null} otherwise. */ void hideInsets(int types, boolean fromIme, in @nullable ImeTracker.Token statsToken); diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java index de809c8489fd..821e13d85370 100644 --- a/core/java/android/view/ImeInsetsSourceConsumer.java +++ b/core/java/android/view/ImeInsetsSourceConsumer.java @@ -21,9 +21,9 @@ import static android.view.ImeInsetsSourceConsumerProto.HAS_PENDING_REQUEST; import static android.view.ImeInsetsSourceConsumerProto.INSETS_SOURCE_CONSUMER; import static android.view.ImeInsetsSourceConsumerProto.IS_REQUESTED_VISIBLE_AWAITING_CONTROL; +import android.annotation.NonNull; import android.annotation.Nullable; import android.os.IBinder; -import android.os.Process; import android.os.Trace; import android.util.proto.ProtoOutputStream; import android.view.SurfaceControl.Transaction; @@ -70,7 +70,11 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { if (!isShowRequested()) { mIsRequestedVisibleAwaitingControl = false; if (!running && !mHasPendingRequest) { - notifyHidden(null /* statsToken */); + final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE, + ImeTracker.ORIGIN_CLIENT, + SoftInputShowHideReason.HIDE_SOFT_INPUT_ON_ANIMATION_STATE_CHANGED, + mController.getHost().isHandlingPointerEvent() /* fromUser */); + notifyHidden(statsToken); removeSurface(); } } @@ -144,9 +148,17 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { void requestHide(boolean fromIme, @Nullable ImeTracker.Token statsToken) { if (!fromIme) { + // Create a new token to track the hide request when we have control, + // as we use the passed in token for the insets animation already. + final var notifyStatsToken = getControl() != null + ? ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE, + ImeTracker.ORIGIN_CLIENT, + SoftInputShowHideReason.HIDE_SOFT_INPUT_REQUEST_HIDE_WITH_CONTROL, + mController.getHost().isHandlingPointerEvent() /* fromUser */) + : statsToken; // The insets might be controlled by a remote target. Let the server know we are // requested to hide. - notifyHidden(statsToken); + notifyHidden(notifyStatsToken); } if (mAnimationState == ANIMATION_STATE_SHOW) { mHasPendingRequest = true; @@ -157,21 +169,9 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { * Notify {@link com.android.server.inputmethod.InputMethodManagerService} that * IME insets are hidden. * - * @param statsToken the token tracking the current IME hide request or {@code null} otherwise. + * @param statsToken the token tracking the current IME request or {@code null} otherwise. */ - private void notifyHidden(@Nullable ImeTracker.Token statsToken) { - // Create a new stats token to track the hide request when: - // - we do not already have one, or - // - we do already have one, but we have control and use the passed in token - // for the insets animation already. - if (statsToken == null || getControl() != null) { - statsToken = ImeTracker.forLogging().onRequestHide(null /* component */, - Process.myUid(), - ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, - SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API, - mController.getHost().isHandlingPointerEvent() /* fromUser */); - } - + private void notifyHidden(@NonNull ImeTracker.Token statsToken) { ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_NOTIFY_HIDDEN); diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index 7f1e037e92d4..85c779bc8c79 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -43,7 +43,6 @@ import static android.view.WindowInsets.Type.ime; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY; -import static android.view.inputmethod.ImeTracker.TOKEN_NONE; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; @@ -165,9 +164,9 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro mStatsToken = statsToken; if (DEBUG_IME_VISIBILITY && (types & ime()) != 0) { EventLog.writeEvent(IMF_IME_ANIM_START, - mStatsToken != null ? mStatsToken.getTag() : TOKEN_NONE, mAnimationType, - mCurrentAlpha, "Current:" + mCurrentInsets, "Shown:" + mShownInsets, - "Hidden:" + mHiddenInsets); + mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE, + mAnimationType, mCurrentAlpha, "Current:" + mCurrentInsets, + "Shown:" + mShownInsets, "Hidden:" + mHiddenInsets); } mController.startAnimation(this, listener, types, mAnimation, new Bounds(mHiddenInsets, mShownInsets)); @@ -245,6 +244,7 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro } @Override + @Nullable public ImeTracker.Token getStatsToken() { return mStatsToken; } @@ -330,8 +330,8 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro mListener.onFinished(this); if (DEBUG_IME_VISIBILITY && (mTypes & ime()) != 0) { EventLog.writeEvent(IMF_IME_ANIM_FINISH, - mStatsToken != null ? mStatsToken.getTag() : TOKEN_NONE, mAnimationType, - mCurrentAlpha, shown ? 1 : 0, Objects.toString(insets)); + mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE, + mAnimationType, mCurrentAlpha, shown ? 1 : 0, Objects.toString(insets)); } } @@ -355,8 +355,8 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro if (DEBUG) Log.d(TAG, "notify Control request cancelled for types: " + mTypes); if (DEBUG_IME_VISIBILITY && (mTypes & ime()) != 0) { EventLog.writeEvent(IMF_IME_ANIM_CANCEL, - mStatsToken != null ? mStatsToken.getTag() : TOKEN_NONE, mAnimationType, - Objects.toString(mPendingInsets)); + mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE, + mAnimationType, Objects.toString(mPendingInsets)); } releaseLeashes(); } diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java index 079991a81e77..92e20e09d8c4 100644 --- a/core/java/android/view/InsetsAnimationThreadControlRunner.java +++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java @@ -137,6 +137,7 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro } @Override + @Nullable public ImeTracker.Token getStatsToken() { return mControl.getStatsToken(); } diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 1803a6ed237f..6cc4b20dcde9 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -28,7 +28,6 @@ import static android.view.WindowInsets.Type.LAST; import static android.view.WindowInsets.Type.all; import static android.view.WindowInsets.Type.captionBar; import static android.view.WindowInsets.Type.ime; -import static android.view.inputmethod.ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL; import android.animation.AnimationHandler; import android.animation.Animator; @@ -47,7 +46,6 @@ import android.graphics.Rect; import android.os.CancellationSignal; import android.os.Handler; import android.os.IBinder; -import android.os.Process; import android.os.Trace; import android.text.TextUtils; import android.util.IntArray; @@ -659,6 +657,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private final Runnable mAnimCallback; /** Pending control request that is waiting on IME to be ready to be shown */ + @Nullable private PendingControlRequest mPendingImeControlRequest; private int mWindowType; @@ -1043,12 +1042,18 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation hideTypes[0] &= ~animatingTypes; if (showTypes[0] != 0) { - applyAnimation(showTypes[0], true /* show */, false /* fromIme */, - null /* statsToken */); + final var statsToken = (showTypes[0] & ime()) == 0 ? null + : ImeTracker.forLogging().onStart(ImeTracker.TYPE_SHOW, + ImeTracker.ORIGIN_CLIENT, SoftInputShowHideReason.CONTROLS_CHANGED, + mHost.isHandlingPointerEvent() /* fromUser */); + applyAnimation(showTypes[0], true /* show */, false /* fromIme */, statsToken); } if (hideTypes[0] != 0) { - applyAnimation(hideTypes[0], false /* show */, false /* fromIme */, - null /* statsToken */); + final var statsToken = (hideTypes[0] & ime()) == 0 ? null + : ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE, + ImeTracker.ORIGIN_CLIENT, SoftInputShowHideReason.CONTROLS_CHANGED, + mHost.isHandlingPointerEvent() /* fromUser */); + applyAnimation(hideTypes[0], false /* show */, false /* fromIme */, statsToken); } if (mControllableTypes != controllableTypes) { @@ -1064,15 +1069,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @Override public void show(@InsetsType int types) { - ImeTracker.Token statsToken = null; - if ((types & ime()) != 0) { - statsToken = ImeTracker.forLogging().onRequestShow(null /* component */, - Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT, - SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API, - mHost.isHandlingPointerEvent() /* fromUser */); - } - - show(types, false /* fromIme */, statsToken); + show(types, false /* fromIme */, null /* statsToken */); } @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) @@ -1080,6 +1077,13 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @Nullable ImeTracker.Token statsToken) { if ((types & ime()) != 0) { Log.d(TAG, "show(ime(), fromIme=" + fromIme + ")"); + + if (statsToken == null) { + statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_SHOW, + ImeTracker.ORIGIN_CLIENT, + SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API, + mHost.isHandlingPointerEvent() /* fromUser */); + } } if (fromIme) { ImeTracing.getInstance().triggerClientDump("InsetsController#show", @@ -1148,9 +1152,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } /** - * Handle the {@link #mPendingImeControlRequest} when - * - The IME insets is ready to show. - * - The IME insets has being requested invisible. + * Handle the {@link #mPendingImeControlRequest} when: + * <ul> + * <li> The IME insets is ready to show. + * <li> The IME insets has being requested invisible. + * </ul> */ private void handlePendingControlRequest(@Nullable ImeTracker.Token statsToken) { PendingControlRequest pendingRequest = mPendingImeControlRequest; @@ -1170,20 +1176,22 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @Override public void hide(@InsetsType int types) { - ImeTracker.Token statsToken = null; - if ((types & ime()) != 0) { - statsToken = ImeTracker.forLogging().onRequestHide(null /* component */, - Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, - SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API, - mHost.isHandlingPointerEvent() /* fromUser */); - } - - hide(types, false /* fromIme */, statsToken); + hide(types, false /* fromIme */, null /* statsToken */); } @VisibleForTesting public void hide(@InsetsType int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) { + if ((types & ime()) != 0) { + Log.d(TAG, "hide(ime(), fromIme=" + fromIme + ")"); + + if (statsToken == null) { + statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE, + ImeTracker.ORIGIN_CLIENT, + SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API, + mHost.isHandlingPointerEvent() /* fromUser */); + } + } if (fromIme) { ImeTracing.getInstance().triggerClientDump("InsetsController#hide", mHost.getInputMethodManager(), null /* icProto */); @@ -1307,10 +1315,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (monitoredAnimation && (types & Type.ime()) != 0) { if (animationType == ANIMATION_TYPE_SHOW) { ImeTracker.forLatency().onShowCancelled(statsToken, - PHASE_CLIENT_ANIMATION_CANCEL, ActivityThread::currentApplication); + ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL, + ActivityThread::currentApplication); } else { ImeTracker.forLatency().onHideCancelled(statsToken, - PHASE_CLIENT_ANIMATION_CANCEL, ActivityThread::currentApplication); + ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL, + ActivityThread::currentApplication); } ImeTracker.forLogging().onCancelled(statsToken, ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION); @@ -1602,12 +1612,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private void cancelAnimation(InsetsAnimationControlRunner control, boolean invokeCallback) { if (invokeCallback) { ImeTracker.forLogging().onCancelled(control.getStatsToken(), - PHASE_CLIENT_ANIMATION_CANCEL); + ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL); control.cancel(); } else { // Succeeds if invokeCallback is false (i.e. when called from notifyFinished). ImeTracker.forLogging().onProgress(control.getStatsToken(), - PHASE_CLIENT_ANIMATION_CANCEL); + ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL); } if (DEBUG) { Log.d(TAG, TextUtils.formatSimple( diff --git a/core/java/android/view/InsetsResizeAnimationRunner.java b/core/java/android/view/InsetsResizeAnimationRunner.java index bffaeea6a731..ebdddd537ae3 100644 --- a/core/java/android/view/InsetsResizeAnimationRunner.java +++ b/core/java/android/view/InsetsResizeAnimationRunner.java @@ -29,6 +29,7 @@ import static android.view.InsetsController.ANIMATION_TYPE_RESIZE; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; +import android.annotation.Nullable; import android.graphics.Insets; import android.graphics.Rect; import android.util.SparseArray; @@ -92,6 +93,7 @@ public class InsetsResizeAnimationRunner implements InsetsAnimationControlRunner } @Override + @Nullable public ImeTracker.Token getStatsToken() { // Return null as resizing the IME view is not explicitly tracked. return null; diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index 0ce61bb17774..fdb2a6ee1791 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -314,7 +314,7 @@ public class InsetsSourceConsumer { * @param fromController {@code true} if request is coming from controller. * (e.g. in IME case, controller is * {@link android.inputmethodservice.InputMethodService}). - * @param statsToken the token tracking the current IME show request or {@code null} otherwise. + * @param statsToken the token tracking the current IME request or {@code null} otherwise. * * @implNote The {@code statsToken} is ignored here, and only handled in * {@link ImeInsetsSourceConsumer} for IME animations only. diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java index 491b0e349cde..cedf8d04ed99 100644 --- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java +++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java @@ -25,7 +25,6 @@ import android.annotation.RequiresNoPermission; import android.annotation.RequiresPermission; import android.annotation.UserIdInt; import android.content.Context; -import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; @@ -298,7 +297,7 @@ final class IInputMethodManagerGlobalInvoker { @AnyThread static boolean showSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken, - @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, + @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, int lastClickToolType, @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { final IInputMethodManager service = getService(); @@ -315,7 +314,7 @@ final class IInputMethodManagerGlobalInvoker { @AnyThread static boolean hideSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken, - @Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, + @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { final IInputMethodManager service = getService(); if (service == null) { @@ -331,6 +330,20 @@ final class IInputMethodManagerGlobalInvoker { // TODO(b/293640003): Remove method once Flags.useZeroJankProxy() is enabled. @AnyThread + @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD) + static void hideSoftInputFromServerForTest() { + final IInputMethodManager service = getService(); + if (service == null) { + return; + } + try { + service.hideSoftInputFromServerForTest(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @AnyThread @NonNull @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true) static InputBindResult startInputOrWindowGainedFocus(@StartInputReason int startInputReason, @@ -654,35 +667,18 @@ final class IInputMethodManagerGlobalInvoker { } } - /** @see com.android.server.inputmethod.ImeTrackerService#onRequestShow */ - @AnyThread - @NonNull - static ImeTracker.Token onRequestShow(@NonNull String tag, int uid, - @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason, boolean fromUser) { - final IImeTracker service = getImeTrackerService(); - if (service == null) { - // Create token with "fake" binder if the service was not found. - return new ImeTracker.Token(new Binder(), tag); - } - try { - return service.onRequestShow(tag, uid, origin, reason, fromUser); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** @see com.android.server.inputmethod.ImeTrackerService#onRequestHide */ + /** @see com.android.server.inputmethod.ImeTrackerService#onStart */ @AnyThread @NonNull - static ImeTracker.Token onRequestHide(@NonNull String tag, int uid, + static ImeTracker.Token onStart(@NonNull String tag, int uid, @ImeTracker.Type int type, @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason, boolean fromUser) { - final IImeTracker service = getImeTrackerService(); + final var service = getImeTrackerService(); if (service == null) { - // Create token with "fake" binder if the service was not found. - return new ImeTracker.Token(new Binder(), tag); + // Create token with "empty" binder if the service was not found. + return ImeTracker.Token.empty(tag); } try { - return service.onRequestHide(tag, uid, origin, reason, fromUser); + return service.onStart(tag, uid, type, origin, reason, fromUser); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java index b1fdaa97ffe0..7f796611d217 100644 --- a/core/java/android/view/inputmethod/ImeTracker.java +++ b/core/java/android/view/inputmethod/ImeTracker.java @@ -28,17 +28,20 @@ import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_SHOWN; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.ActivityThread; import android.content.Context; +import android.os.Binder; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; +import android.os.Process; import android.os.SystemProperties; import android.util.Log; import android.view.InsetsController.AnimationType; import android.view.SurfaceControl; import android.view.View; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.annotations.VisibleForTesting.Visibility; import com.android.internal.inputmethod.InputMethodDebug; import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.jank.InteractionJankMonitor; @@ -108,34 +111,32 @@ public interface ImeTracker { /** * The origin of the IME request * - * The name follows the format {@code PHASE_x_...} where {@code x} denotes - * where the origin is (i.e. {@code PHASE_SERVER_...} occurs in the server). + * <p> The name follows the format {@code ORIGIN_x_...} where {@code x} denotes + * where the origin is (i.e. {@code ORIGIN_SERVER} occurs in the server). */ @IntDef(prefix = { "ORIGIN_" }, value = { - ORIGIN_CLIENT_SHOW_SOFT_INPUT, - ORIGIN_CLIENT_HIDE_SOFT_INPUT, - ORIGIN_SERVER_START_INPUT, - ORIGIN_SERVER_HIDE_INPUT + ORIGIN_CLIENT, + ORIGIN_SERVER, + ORIGIN_IME }) @Retention(RetentionPolicy.SOURCE) @interface Origin {} - /** The IME show request originated in the client. */ - int ORIGIN_CLIENT_SHOW_SOFT_INPUT = ImeProtoEnums.ORIGIN_CLIENT_SHOW_SOFT_INPUT; + /** The IME request originated in the client. */ + int ORIGIN_CLIENT = ImeProtoEnums.ORIGIN_CLIENT; - /** The IME hide request originated in the client. */ - int ORIGIN_CLIENT_HIDE_SOFT_INPUT = ImeProtoEnums.ORIGIN_CLIENT_HIDE_SOFT_INPUT; + /** The IME request originated in the server. */ + int ORIGIN_SERVER = ImeProtoEnums.ORIGIN_SERVER; - /** The IME show request originated in the server. */ - int ORIGIN_SERVER_START_INPUT = ImeProtoEnums.ORIGIN_SERVER_START_INPUT; - - /** The IME hide request originated in the server. */ - int ORIGIN_SERVER_HIDE_INPUT = ImeProtoEnums.ORIGIN_SERVER_HIDE_INPUT; + /** The IME request originated in the IME. */ + int ORIGIN_IME = ImeProtoEnums.ORIGIN_IME; + /** The IME request originated in the WindowManager Shell. */ + int ORIGIN_WM_SHELL = ImeProtoEnums.ORIGIN_WM_SHELL; /** * The current phase of the IME request. * - * The name follows the format {@code PHASE_x_...} where {@code x} denotes + * <p> The name follows the format {@code PHASE_x_...} where {@code x} denotes * where the phase is (i.e. {@code PHASE_SERVER_...} occurs in the server). */ @IntDef(prefix = { "PHASE_" }, value = { @@ -155,7 +156,6 @@ public interface ImeTracker { PHASE_IME_SHOW_SOFT_INPUT, PHASE_IME_HIDE_SOFT_INPUT, PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE, - PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER, PHASE_SERVER_APPLY_IME_VISIBILITY, PHASE_WM_SHOW_IME_RUNNER, PHASE_WM_SHOW_IME_READY, @@ -182,6 +182,11 @@ public interface ImeTracker { PHASE_CLIENT_ANIMATION_FINISHED_SHOW, PHASE_CLIENT_ANIMATION_FINISHED_HIDE, PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT, + PHASE_CLIENT_ANIMATION_FINISHED_HIDE, + PHASE_IME_SHOW_WINDOW, + PHASE_IME_HIDE_WINDOW, + PHASE_IME_PRIVILEGED_OPERATIONS, + PHASE_SERVER_CURRENT_ACTIVE_IME, }) @Retention(RetentionPolicy.SOURCE) @interface Phase {} @@ -224,19 +229,15 @@ public interface ImeTracker { /** Dispatched from the IME wrapper to the IME. */ int PHASE_IME_WRAPPER_DISPATCH = ImeProtoEnums.PHASE_IME_WRAPPER_DISPATCH; - /** Reached the IME' showSoftInput method. */ + /** Reached the IME's showSoftInput method. */ int PHASE_IME_SHOW_SOFT_INPUT = ImeProtoEnums.PHASE_IME_SHOW_SOFT_INPUT; - /** Reached the IME' hideSoftInput method. */ + /** Reached the IME's hideSoftInput method. */ int PHASE_IME_HIDE_SOFT_INPUT = ImeProtoEnums.PHASE_IME_HIDE_SOFT_INPUT; /** The server decided the IME should be shown. */ int PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE = ImeProtoEnums.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE; - /** Requested applying the IME visibility in the insets source consumer. */ - int PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER = - ImeProtoEnums.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER; - /** Applied the IME visibility. */ int PHASE_SERVER_APPLY_IME_VISIBILITY = ImeProtoEnums.PHASE_SERVER_APPLY_IME_VISIBILITY; @@ -323,37 +324,49 @@ public interface ImeTracker { int PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT = ImeProtoEnums.PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT; + /** Reached the IME's showWindow method. */ + int PHASE_IME_SHOW_WINDOW = ImeProtoEnums.PHASE_IME_SHOW_WINDOW; + + /** Reached the IME's hideWindow method. */ + int PHASE_IME_HIDE_WINDOW = ImeProtoEnums.PHASE_IME_HIDE_WINDOW; + + /** Reached the InputMethodPrivilegedOperations handler. */ + int PHASE_IME_PRIVILEGED_OPERATIONS = ImeProtoEnums.PHASE_IME_PRIVILEGED_OPERATIONS; + + /** Checked that the calling IME is the currently active IME. */ + int PHASE_SERVER_CURRENT_ACTIVE_IME = ImeProtoEnums.PHASE_SERVER_CURRENT_ACTIVE_IME; + /** - * Creates an IME show request tracking token. + * Called when an IME request is started. * - * @param component the name of the component that created the IME request, or {@code null} - * otherwise (defaulting to {@link ActivityThread#currentProcessName()}). - * @param uid the uid of the client that requested the IME. - * @param origin the origin of the IME show request. - * @param reason the reason why the IME show request was created. + * @param component the name of the component that started the request. + * @param uid the uid of the client that started the request. + * @param type the type of the request. + * @param origin the origin of the request. + * @param reason the reason for starting the request. * @param fromUser whether this request was created directly from user interaction. * - * @return An IME tracking token. + * @return An IME request tracking token. */ @NonNull - Token onRequestShow(@Nullable String component, int uid, @Origin int origin, + Token onStart(@NonNull String component, int uid, @Type int type, @Origin int origin, @SoftInputShowHideReason int reason, boolean fromUser); /** - * Creates an IME hide request tracking token. + * Called when an IME request is started for the current process. * - * @param component the name of the component that created the IME request, or {@code null} - * otherwise (defaulting to {@link ActivityThread#currentProcessName()}). - * @param uid the uid of the client that requested the IME. - * @param origin the origin of the IME hide request. - * @param reason the reason why the IME hide request was created. + * @param type the type of the request. + * @param origin the origin of the request. + * @param reason the reason for starting the request. * @param fromUser whether this request was created directly from user interaction. * - * @return An IME tracking token. + * @return An IME request tracking token. */ @NonNull - Token onRequestHide(@Nullable String component, int uid, @Origin int origin, - @SoftInputShowHideReason int reason, boolean fromUser); + default Token onStart(@Type int type, @Origin int origin, @SoftInputShowHideReason int reason, + boolean fromUser) { + return onStart(Process.myProcessName(), Process.myUid(), type, origin, reason, fromUser); + } /** * Called when an IME request progresses to a further phase. @@ -390,14 +403,14 @@ public interface ImeTracker { /** * Called when the IME show request is successful. * - * @param token the token tracking the current IME show request or {@code null} otherwise. + * @param token the token tracking the current IME request or {@code null} otherwise. */ void onShown(@Nullable Token token); /** * Called when the IME hide request is successful. * - * @param token the token tracking the current IME hide request or {@code null} otherwise. + * @param token the token tracking the current IME request or {@code null} otherwise. */ void onHidden(@Nullable Token token); @@ -479,33 +492,17 @@ public interface ImeTracker { @NonNull @Override - public Token onRequestShow(@Nullable String component, int uid, @Origin int origin, - @SoftInputShowHideReason int reason, boolean fromUser) { - final var tag = getTag(component); - final var token = IInputMethodManagerGlobalInvoker.onRequestShow(tag, uid, origin, - reason, fromUser); - - Log.i(TAG, token.mTag + ": onRequestShow at " + Debug.originToString(origin) - + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason) - + " fromUser " + fromUser, - mLogStackTrace ? new Throwable() : null); - - return token; - } - - @NonNull - @Override - public Token onRequestHide(@Nullable String component, int uid, @Origin int origin, + public Token onStart(@NonNull String component, int uid, @Type int type, @Origin int origin, @SoftInputShowHideReason int reason, boolean fromUser) { - final var tag = getTag(component); - final var token = IInputMethodManagerGlobalInvoker.onRequestHide(tag, uid, origin, - reason, fromUser); + final var tag = Token.createTag(component); + final var token = IInputMethodManagerGlobalInvoker.onStart(tag, uid, type, + origin, reason, fromUser); - Log.i(TAG, token.mTag + ": onRequestHide at " + Debug.originToString(origin) + Log.i(TAG, token.mTag + ": onRequest" + (type == TYPE_SHOW ? "Show" : "Hide") + + " at " + Debug.originToString(origin) + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason) + " fromUser " + fromUser, mLogStackTrace ? new Throwable() : null); - return token; } @@ -556,20 +553,6 @@ public interface ImeTracker { Log.i(TAG, token.mTag + ": onHidden"); } - - /** - * Returns a logging tag using the given component name. - * - * @param component the name of the component that created the IME request, or {@code null} - * otherwise (defaulting to {@link ActivityThread#currentProcessName()}). - */ - @NonNull - private String getTag(@Nullable String component) { - if (component == null) { - component = ActivityThread.currentProcessName(); - } - return component + ":" + Integer.toHexString(ThreadLocalRandom.current().nextInt()); - } }; /** The singleton IME tracker instance for instrumenting jank metrics. */ @@ -581,6 +564,10 @@ public interface ImeTracker { /** A token that tracks the progress of an IME request. */ final class Token implements Parcelable { + /** Empty binder, lazily initialized, used for empty token instantiation. */ + @Nullable + private static IBinder sEmptyBinder; + /** The binder used to identify this token. */ @NonNull private final IBinder mBinder; @@ -599,16 +586,56 @@ public interface ImeTracker { mTag = in.readString8(); } + /** Returns the binder used to identify this token. */ @NonNull public IBinder getBinder() { return mBinder; } + /** Returns the logging tag of this token. */ @NonNull public String getTag() { return mTag; } + /** + * Creates a logging tag. + * + * @param component the name of the component that created the IME request. + */ + @NonNull + private static String createTag(@NonNull String component) { + return component + ":" + Integer.toHexString(ThreadLocalRandom.current().nextInt()); + } + + /** Returns a new token with an empty binder. */ + @NonNull + @VisibleForTesting(visibility = Visibility.PACKAGE) + public static Token empty() { + final var tag = createTag(Process.myProcessName()); + return empty(tag); + } + + /** Returns a new token with an empty binder and the given logging tag. */ + @NonNull + static Token empty(@NonNull String tag) { + return new Token(getEmptyBinder(), tag); + } + + /** Returns the empty binder instance for empty token creation, lazily initializing it. */ + @NonNull + private static IBinder getEmptyBinder() { + if (sEmptyBinder == null) { + sEmptyBinder = new Binder(); + } + return sEmptyBinder; + } + + @Override + public String toString() { + return super.toString() + "(tag: " + mTag + ")"; + } + /** For Parcelable, no special marshalled objects. */ @Override public int describeContents() { diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java index 33f34c5697c4..88607fc80f69 100644 --- a/core/java/android/view/inputmethod/InputMethod.java +++ b/core/java/android/view/inputmethod/InputMethod.java @@ -281,7 +281,7 @@ public interface InputMethod { }) @Retention(RetentionPolicy.SOURCE) @interface ShowFlags {} - + /** * Flag for {@link #showSoftInput}: this show has been explicitly * requested by the user. If not set, the system has decided it may be @@ -314,18 +314,18 @@ public interface InputMethod { * @param showInputToken an opaque {@link android.os.Binder} token to identify which API call * of {@link InputMethodManager#showSoftInput(View, int)} is associated with * this callback. - * @param statsToken the token tracking the current IME show request or {@code null} otherwise. + * @param statsToken the token tracking the current IME request. * @hide */ @MainThread public default void showSoftInputWithToken(@ShowFlags int flags, ResultReceiver resultReceiver, - IBinder showInputToken, @Nullable ImeTracker.Token statsToken) { + IBinder showInputToken, @NonNull ImeTracker.Token statsToken) { showSoftInput(flags, resultReceiver); } /** * Request that any soft input part of the input method be shown to the user. - * + * * @param resultReceiver The client requesting the show may wish to * be told the impact of their request, which should be supplied here. * The result code should be @@ -352,12 +352,12 @@ public interface InputMethod { * @param hideInputToken an opaque {@link android.os.Binder} token to identify which API call * of {@link InputMethodManager#hideSoftInputFromWindow(IBinder, int)}} is associated * with this callback. - * @param statsToken the token tracking the current IME hide request or {@code null} otherwise. + * @param statsToken the token tracking the current IME request. * @hide */ @MainThread public default void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver, - IBinder hideInputToken, @Nullable ImeTracker.Token statsToken) { + IBinder hideInputToken, @NonNull ImeTracker.Token statsToken) { hideSoftInput(flags, resultReceiver); } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 68940d699076..3be76cc8a60f 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -2263,21 +2263,22 @@ public final class InputMethodManager { * {@link #RESULT_HIDDEN}. */ public boolean showSoftInput(View view, @ShowFlags int flags, ResultReceiver resultReceiver) { - return showSoftInput(view, null /* statsToken */, flags, resultReceiver, - SoftInputShowHideReason.SHOW_SOFT_INPUT); + return showSoftInput(view, flags, resultReceiver, SoftInputShowHideReason.SHOW_SOFT_INPUT); } - private boolean showSoftInput(View view, @Nullable ImeTracker.Token statsToken, - @ShowFlags int flags, ResultReceiver resultReceiver, + private boolean showSoftInput(View view, @ShowFlags int flags, + @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { + // TODO(b/303041796): handle tracking physical keyboard and DPAD as user interactions + final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_SHOW, + ImeTracker.ORIGIN_CLIENT, reason, ImeTracker.isFromUser(view)); + return showSoftInput(view, statsToken, flags, resultReceiver, reason); + } + + private boolean showSoftInput(View view, @NonNull ImeTracker.Token statsToken, + @ShowFlags int flags, @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { - if (statsToken == null) { - // TODO(b/303041796): handle tracking physical keyboard and DPAD as user interactions - statsToken = ImeTracker.forLogging().onRequestShow(null /* component */, - Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT, reason, - ImeTracker.isFromUser(view)); - } - ImeTracker.forLatency().onRequestShow(statsToken, ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT, - reason, ActivityThread::currentApplication); + ImeTracker.forLatency().onRequestShow(statsToken, + ImeTracker.ORIGIN_CLIENT, reason, ActivityThread::currentApplication); ImeTracing.getInstance().triggerClientDump("InputMethodManager#showSoftInput", this, null /* icProto */); // Re-dispatch if there is a context mismatch. @@ -2290,9 +2291,8 @@ public final class InputMethodManager { synchronized (mH) { if (!hasServedByInputMethodLocked(view)) { ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED); - ImeTracker.forLatency().onShowFailed( - statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED, - ActivityThread::currentApplication); + ImeTracker.forLatency().onShowFailed(statsToken, + ImeTracker.PHASE_CLIENT_VIEW_SERVED, ActivityThread::currentApplication); Log.w(TAG, "Ignoring showSoftInput() as view=" + view + " is not served."); return false; } @@ -2327,9 +2327,9 @@ public final class InputMethodManager { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768499) public void showSoftInputUnchecked(@ShowFlags int flags, ResultReceiver resultReceiver) { synchronized (mH) { - final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestShow( - null /* component */, Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT, - SoftInputShowHideReason.SHOW_SOFT_INPUT, false /* fromUser */); + final int reason = SoftInputShowHideReason.SHOW_SOFT_INPUT; + final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_SHOW, + ImeTracker.ORIGIN_CLIENT, reason, false /* fromUser */); Log.w(TAG, "showSoftInputUnchecked() is a hidden method, which will be" + " removed soon. If you are using androidx.appcompat.widget.SearchView," @@ -2353,7 +2353,7 @@ public final class InputMethodManager { flags, mCurRootView.getLastClickToolType(), resultReceiver, - SoftInputShowHideReason.SHOW_SOFT_INPUT); + reason); } } @@ -2429,11 +2429,10 @@ public final class InputMethodManager { initialServedView = getServedViewLocked(); } - final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestHide( - null /* component */, Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, - reason, ImeTracker.isFromUser(initialServedView)); - ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, - reason, ActivityThread::currentApplication); + final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE, + ImeTracker.ORIGIN_CLIENT, reason, ImeTracker.isFromUser(initialServedView)); + ImeTracker.forLatency().onRequestHide(statsToken, + ImeTracker.ORIGIN_CLIENT, reason, ActivityThread::currentApplication); ImeTracing.getInstance().triggerClientDump("InputMethodManager#hideSoftInputFromWindow", this, null /* icProto */); checkFocus(); @@ -2472,20 +2471,18 @@ public final class InputMethodManager { } } - final var reason = SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_VIEW; - final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestHide( - null /* component */, Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, - reason, ImeTracker.isFromUser(view)); - ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, - reason, ActivityThread::currentApplication); + final int reason = SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_VIEW; + final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE, + ImeTracker.ORIGIN_CLIENT, reason, ImeTracker.isFromUser(view)); + ImeTracker.forLatency().onRequestHide(statsToken, + ImeTracker.ORIGIN_CLIENT, reason, ActivityThread::currentApplication); ImeTracing.getInstance().triggerClientDump("InputMethodManager#hideSoftInputFromView", this, null /* icProto */); synchronized (mH) { if (!hasServedByInputMethodLocked(view)) { ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED); - ImeTracker.forLatency().onShowFailed( - statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED, - ActivityThread::currentApplication); + ImeTracker.forLatency().onShowFailed(statsToken, + ImeTracker.PHASE_CLIENT_VIEW_SERVED, ActivityThread::currentApplication); Log.w(TAG, "Ignoring hideSoftInputFromView() as view=" + view + " is not served."); return false; } @@ -2498,6 +2495,19 @@ public final class InputMethodManager { } /** + * A test API for CTS to request hiding the current soft input window, with the request origin + * on the server side. + * + * @hide + */ + @SuppressLint("UnflaggedApi") // @TestApi without associated feature. + @TestApi + @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD) + public void hideSoftInputFromServerForTest() { + IInputMethodManagerGlobalInvoker.hideSoftInputFromServerForTest(); + } + + /** * Start stylus handwriting session. * * If supported by the current input method, a stylus handwriting session is started on the @@ -2973,10 +2983,11 @@ public final class InputMethodManager { if (view != null) { final WindowInsets rootInsets = view.getRootWindowInsets(); if (rootInsets != null && rootInsets.isVisible(WindowInsets.Type.ime())) { - hideSoftInputFromWindow(view.getWindowToken(), hideFlags, null, + hideSoftInputFromWindow(view.getWindowToken(), hideFlags, + null /* resultReceiver */, SoftInputShowHideReason.HIDE_TOGGLE_SOFT_INPUT); } else { - showSoftInput(view, null /* statsToken */, showFlags, null /* resultReceiver */, + showSoftInput(view, showFlags, null /* resultReceiver */, SoftInputShowHideReason.SHOW_TOGGLE_SOFT_INPUT); } } @@ -3537,11 +3548,11 @@ public final class InputMethodManager { @UnsupportedAppUsage void closeCurrentInput() { - final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestHide( - null /* component */, Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, - SoftInputShowHideReason.HIDE_CLOSE_CURRENT_SESSION, false /* fromUser */); - ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, - SoftInputShowHideReason.HIDE_CLOSE_CURRENT_SESSION, + final int reason = SoftInputShowHideReason.HIDE_CLOSE_CURRENT_SESSION; + final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE, + ImeTracker.ORIGIN_CLIENT, reason, false /* fromUser */); + ImeTracker.forLatency().onRequestHide(statsToken, + ImeTracker.ORIGIN_CLIENT, reason, ActivityThread::currentApplication); synchronized (mH) { @@ -3562,7 +3573,7 @@ public final class InputMethodManager { statsToken, HIDE_NOT_ALWAYS, null, - SoftInputShowHideReason.HIDE_CLOSE_CURRENT_SESSION); + reason); } } @@ -3603,12 +3614,12 @@ public final class InputMethodManager { * * @param windowToken the window from which this request originates. If this doesn't match the * currently served view, the request is ignored and returns {@code false}. - * @param statsToken the token tracking the current IME show request or {@code null} otherwise. + * @param statsToken the token tracking the current IME request. * * @return {@code true} if IME can (eventually) be shown, {@code false} otherwise. * @hide */ - public boolean requestImeShow(IBinder windowToken, @Nullable ImeTracker.Token statsToken) { + public boolean requestImeShow(IBinder windowToken, @NonNull ImeTracker.Token statsToken) { checkFocus(); synchronized (mH) { final View servedView = getServedViewLocked(); @@ -3632,16 +3643,11 @@ public final class InputMethodManager { * * @param windowToken the window from which this request originates. If this doesn't match the * currently served view, the request is ignored. - * @param statsToken the token tracking the current IME show request or {@code null} otherwise. + * @param statsToken the token tracking the current IME request. * @hide */ - public void notifyImeHidden(IBinder windowToken, @Nullable ImeTracker.Token statsToken) { - if (statsToken == null) { - statsToken = ImeTracker.forLogging().onRequestHide(null /* component */, - Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, - SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API, false /* fromUser */); - } - ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, + public void notifyImeHidden(IBinder windowToken, @NonNull ImeTracker.Token statsToken) { + ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT, SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API, ActivityThread::currentApplication); ImeTracing.getInstance().triggerClientDump("InputMethodManager#notifyImeHidden", this, @@ -4025,8 +4031,11 @@ public final class InputMethodManager { */ @Deprecated public void hideSoftInputFromInputMethod(IBinder token, @HideFlags int flags) { - InputMethodPrivilegedOperationsRegistry.get(token).hideMySoftInput( - flags, SoftInputShowHideReason.HIDE_SOFT_INPUT_IMM_DEPRECATION); + final int reason = SoftInputShowHideReason.HIDE_SOFT_INPUT_IMM_DEPRECATION; + final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE, + ImeTracker.ORIGIN_CLIENT, reason, false /* fromUser */); + InputMethodPrivilegedOperationsRegistry.get(token).hideMySoftInput(statsToken, flags, + reason); } /** @@ -4044,7 +4053,11 @@ public final class InputMethodManager { */ @Deprecated public void showSoftInputFromInputMethod(IBinder token, @ShowFlags int flags) { - InputMethodPrivilegedOperationsRegistry.get(token).showMySoftInput(flags); + final int reason = SoftInputShowHideReason.SHOW_SOFT_INPUT_IMM_DEPRECATION; + final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_SHOW, + ImeTracker.ORIGIN_CLIENT, reason, false /* fromUser */); + InputMethodPrivilegedOperationsRegistry.get(token).showMySoftInput(statsToken, flags, + reason); } /** diff --git a/core/java/com/android/internal/inputmethod/IImeTracker.aidl b/core/java/com/android/internal/inputmethod/IImeTracker.aidl index 275904347d2b..b45bc1c46967 100644 --- a/core/java/com/android/internal/inputmethod/IImeTracker.aidl +++ b/core/java/com/android/internal/inputmethod/IImeTracker.aidl @@ -25,28 +25,19 @@ import android.view.inputmethod.ImeTracker; interface IImeTracker { /** - * Called when an IME show request is created. + * Called when an IME request is started. * * @param tag the logging tag. - * @param uid the uid of the client that requested the IME. - * @param origin the origin of the IME show request. - * @param reason the reason why the IME show request was created. + * @param uid the uid of the client that started the request. + * @param type the type of the request. + * @param origin the origin of the request. * @param fromUser whether this request was created directly from user interaction. - * @return A new IME tracking token. - */ - ImeTracker.Token onRequestShow(String tag, int uid, int origin, int reason, boolean fromUser); - - /** - * Called when an IME hide request is created. + * @param reason the reason for starting the request. * - * @param tag the logging tag. - * @param uid the uid of the client that requested the IME. - * @param origin the origin of the IME hide request. - * @param reason the reason why the IME hide request was created. - * @param fromUser whether this request was created directly from user interaction. - * @return A new IME tracking token. + * @return An IME request tracking token. */ - ImeTracker.Token onRequestHide(String tag, int uid, int origin, int reason, boolean fromUser); + ImeTracker.Token onStart(String tag, int uid, int type, int origin, int reason, + boolean fromUser); /** * Called when the IME request progresses to a further phase. diff --git a/core/java/com/android/internal/inputmethod/IInputMethod.aidl b/core/java/com/android/internal/inputmethod/IInputMethod.aidl index 6abd9e8a8a17..2593b78f5182 100644 --- a/core/java/com/android/internal/inputmethod/IInputMethod.aidl +++ b/core/java/com/android/internal/inputmethod/IInputMethod.aidl @@ -71,11 +71,11 @@ oneway interface IInputMethod { void setSessionEnabled(IInputMethodSession session, boolean enabled); - void showSoftInput(in IBinder showInputToken, in @nullable ImeTracker.Token statsToken, - int flags, in ResultReceiver resultReceiver); + void showSoftInput(in IBinder showInputToken, in ImeTracker.Token statsToken, int flags, + in ResultReceiver resultReceiver); - void hideSoftInput(in IBinder hideInputToken, in @nullable ImeTracker.Token statsToken, - int flags, in ResultReceiver resultReceiver); + void hideSoftInput(in IBinder hideInputToken, in ImeTracker.Token statsToken, int flags, + in ResultReceiver resultReceiver); void updateEditorToolType(int toolType); diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl index 65a2f4be9901..457b9dd34644 100644 --- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl +++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl @@ -35,15 +35,17 @@ oneway interface IInputMethodPrivilegedOperations { void setInputMethod(String id, in AndroidFuture future /* T=Void */); void setInputMethodAndSubtype(String id, in InputMethodSubtype subtype, in AndroidFuture future /* T=Void */); - void hideMySoftInput(int flags, int reason, in AndroidFuture future /* T=Void */); - void showMySoftInput(int flags, in AndroidFuture future /* T=Void */); + void hideMySoftInput(in ImeTracker.Token statsToken, int flags, int reason, + in AndroidFuture future /* T=Void */); + void showMySoftInput(in ImeTracker.Token statsToken, int flags, int reason, + in AndroidFuture future /* T=Void */); void updateStatusIconAsync(String packageName, int iconId); void switchToPreviousInputMethod(in AndroidFuture future /* T=Boolean */); void switchToNextInputMethod(boolean onlyCurrentIme, in AndroidFuture future /* T=Boolean */); void shouldOfferSwitchingToNextInputMethod(in AndroidFuture future /* T=Boolean */); void notifyUserActionAsync(); void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible, - in @nullable ImeTracker.Token statsToken); + in ImeTracker.Token statsToken); void onStylusHandwritingReady(int requestId, int pid); void resetStylusHandwriting(int requestId); void switchKeyboardLayoutAsync(int direction); diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java index 9b7fa2f4f9e9..a0aad31d2e04 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java +++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java @@ -189,6 +189,8 @@ public final class InputMethodDebug { */ public static String softInputDisplayReasonToString(@SoftInputShowHideReason int reason) { switch (reason) { + case SoftInputShowHideReason.NOT_SET: + return "NOT_SET"; case SoftInputShowHideReason.SHOW_SOFT_INPUT: return "SHOW_SOFT_INPUT"; case SoftInputShowHideReason.ATTACH_NEW_INPUT: @@ -265,6 +267,36 @@ public final class InputMethodDebug { return "HIDE_SOFT_INPUT_CLOSE_CURRENT_SESSION"; case SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_VIEW: return "HIDE_SOFT_INPUT_FROM_VIEW"; + case SoftInputShowHideReason.SHOW_SOFT_INPUT_LEGACY_DIRECT: + return "SHOW_SOFT_INPUT_LEGACY_DIRECT"; + case SoftInputShowHideReason.HIDE_SOFT_INPUT_LEGACY_DIRECT: + return "HIDE_SOFT_INPUT_LEGACY_DIRECT"; + case SoftInputShowHideReason.SHOW_WINDOW_LEGACY_DIRECT: + return "SHOW_WINDOW_LEGACY_DIRECT"; + case SoftInputShowHideReason.HIDE_WINDOW_LEGACY_DIRECT: + return "HIDE_WINDOW_LEGACY_DIRECT"; + case SoftInputShowHideReason.RESET_NEW_CONFIGURATION: + return "RESET_NEW_CONFIGURATION"; + case SoftInputShowHideReason.UPDATE_CANDIDATES_VIEW_VISIBILITY: + return "UPDATE_CANDIDATES_VIEW_VISIBILITY"; + case SoftInputShowHideReason.CONTROLS_CHANGED: + return "CONTROLS_CHANGED"; + case SoftInputShowHideReason.DISPLAY_CONFIGURATION_CHANGED: + return "DISPLAY_CONFIGURATION_CHANGED"; + case SoftInputShowHideReason.DISPLAY_INSETS_CHANGED: + return "DISPLAY_INSETS_CHANGED"; + case SoftInputShowHideReason.DISPLAY_CONTROLS_CHANGED: + return "DISPLAY_CONTROLS_CHANGED"; + case SoftInputShowHideReason.UNBIND_CURRENT_METHOD: + return "UNBIND_CURRENT_METHOD"; + case SoftInputShowHideReason.HIDE_SOFT_INPUT_ON_ANIMATION_STATE_CHANGED: + return "HIDE_SOFT_INPUT_ON_ANIMATION_STATE_CHANGED"; + case SoftInputShowHideReason.HIDE_SOFT_INPUT_REQUEST_HIDE_WITH_CONTROL: + return "HIDE_SOFT_INPUT_REQUEST_HIDE_WITH_CONTROL"; + case SoftInputShowHideReason.SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT: + return "SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT"; + case SoftInputShowHideReason.SHOW_SOFT_INPUT_IMM_DEPRECATION: + return "SHOW_SOFT_INPUT_IMM_DEPRECATION"; default: return "Unknown=" + reason; } diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java index 792388dca339..635a227e5862 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java +++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java @@ -252,20 +252,21 @@ public final class InputMethodPrivilegedOperations { } /** - * Calls {@link IInputMethodPrivilegedOperations#hideMySoftInput(int, int, AndroidFuture)} - * - * @param reason the reason to hide soft input + * Calls {@link IInputMethodPrivilegedOperations#hideMySoftInput} */ @AnyThread - public void hideMySoftInput(@InputMethodManager.HideFlags int flags, - @SoftInputShowHideReason int reason) { + public void hideMySoftInput(@NonNull ImeTracker.Token statsToken, + @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason) { final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull(); if (ops == null) { + ImeTracker.forLogging().onFailed(statsToken, + ImeTracker.PHASE_IME_PRIVILEGED_OPERATIONS); return; } + ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_PRIVILEGED_OPERATIONS); try { final AndroidFuture<Void> future = new AndroidFuture<>(); - ops.hideMySoftInput(flags, reason, future); + ops.hideMySoftInput(statsToken, flags, reason, future); CompletableFutureUtil.getResult(future); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -273,17 +274,21 @@ public final class InputMethodPrivilegedOperations { } /** - * Calls {@link IInputMethodPrivilegedOperations#showMySoftInput(int, AndroidFuture)} + * Calls {@link IInputMethodPrivilegedOperations#showMySoftInput} */ @AnyThread - public void showMySoftInput(@InputMethodManager.ShowFlags int flags) { + public void showMySoftInput(@NonNull ImeTracker.Token statsToken, + @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason) { final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull(); if (ops == null) { + ImeTracker.forLogging().onFailed(statsToken, + ImeTracker.PHASE_IME_PRIVILEGED_OPERATIONS); return; } + ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_PRIVILEGED_OPERATIONS); try { final AndroidFuture<Void> future = new AndroidFuture<>(); - ops.showMySoftInput(flags, future); + ops.showMySoftInput(statsToken, flags, reason, future); CompletableFutureUtil.getResult(future); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -379,19 +384,19 @@ public final class InputMethodPrivilegedOperations { * {@link android.view.inputmethod.InputMethodManager#hideSoftInputFromWindow(IBinder, * int)} * @param setVisible {@code true} to set IME visible, else hidden. - * @param statsToken the token tracking the current IME request or {@code null} otherwise. + * @param statsToken the token tracking the current IME request. */ @AnyThread public void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible, - @Nullable ImeTracker.Token statsToken) { + @NonNull ImeTracker.Token statsToken) { final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull(); if (ops == null) { ImeTracker.forLogging().onFailed(statsToken, - ImeTracker.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER); + ImeTracker.PHASE_IME_PRIVILEGED_OPERATIONS); return; } ImeTracker.forLogging().onProgress(statsToken, - ImeTracker.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER); + ImeTracker.PHASE_IME_PRIVILEGED_OPERATIONS); try { ops.applyImeVisibilityAsync(showOrHideInputToken, setVisible, statsToken); } catch (RemoteException e) { diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java index 861b8a75f730..da738a01ec39 100644 --- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java +++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java @@ -34,6 +34,7 @@ import java.lang.annotation.Retention; */ @Retention(SOURCE) @IntDef(value = { + SoftInputShowHideReason.NOT_SET, SoftInputShowHideReason.SHOW_SOFT_INPUT, SoftInputShowHideReason.ATTACH_NEW_INPUT, SoftInputShowHideReason.SHOW_SOFT_INPUT_FROM_IME, @@ -72,8 +73,26 @@ import java.lang.annotation.Retention; SoftInputShowHideReason.HIDE_WHEN_INPUT_TARGET_INVISIBLE, SoftInputShowHideReason.HIDE_CLOSE_CURRENT_SESSION, SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_VIEW, + SoftInputShowHideReason.SHOW_SOFT_INPUT_LEGACY_DIRECT, + SoftInputShowHideReason.HIDE_SOFT_INPUT_LEGACY_DIRECT, + SoftInputShowHideReason.SHOW_WINDOW_LEGACY_DIRECT, + SoftInputShowHideReason.HIDE_WINDOW_LEGACY_DIRECT, + SoftInputShowHideReason.RESET_NEW_CONFIGURATION, + SoftInputShowHideReason.UPDATE_CANDIDATES_VIEW_VISIBILITY, + SoftInputShowHideReason.CONTROLS_CHANGED, + SoftInputShowHideReason.DISPLAY_CONFIGURATION_CHANGED, + SoftInputShowHideReason.DISPLAY_INSETS_CHANGED, + SoftInputShowHideReason.DISPLAY_CONTROLS_CHANGED, + SoftInputShowHideReason.UNBIND_CURRENT_METHOD, + SoftInputShowHideReason.HIDE_SOFT_INPUT_ON_ANIMATION_STATE_CHANGED, + SoftInputShowHideReason.HIDE_SOFT_INPUT_REQUEST_HIDE_WITH_CONTROL, + SoftInputShowHideReason.SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT, + SoftInputShowHideReason.SHOW_SOFT_INPUT_IMM_DEPRECATION, }) public @interface SoftInputShowHideReason { + /** Default, undefined reason. */ + int NOT_SET = ImeProtoEnums.REASON_NOT_SET; + /** Show soft input by {@link android.view.inputmethod.InputMethodManager#showSoftInput}. */ int SHOW_SOFT_INPUT = ImeProtoEnums.REASON_SHOW_SOFT_INPUT; @@ -291,4 +310,91 @@ public @interface SoftInputShowHideReason { * Hide soft input when {@link InputMethodManager#hideSoftInputFromView(View, int)} gets called. */ int HIDE_SOFT_INPUT_FROM_VIEW = ImeProtoEnums.REASON_HIDE_SOFT_INPUT_FROM_VIEW; + + /** + * Show soft input by legacy (discouraged) call to + * {@link android.inputmethodservice.InputMethodService.InputMethodImpl#showSoftInput}. + */ + int SHOW_SOFT_INPUT_LEGACY_DIRECT = ImeProtoEnums.REASON_SHOW_SOFT_INPUT_LEGACY_DIRECT; + + /** + * Hide soft input by legacy (discouraged) call to + * {@link android.inputmethodservice.InputMethodService.InputMethodImpl#hideSoftInput}. + */ + int HIDE_SOFT_INPUT_LEGACY_DIRECT = ImeProtoEnums.REASON_HIDE_SOFT_INPUT_LEGACY_DIRECT; + + /** + * Show soft input by legacy (discouraged) call to + * {@link android.inputmethodservice.InputMethodService#showWindow}. + */ + int SHOW_WINDOW_LEGACY_DIRECT = ImeProtoEnums.REASON_SHOW_WINDOW_LEGACY_DIRECT; + + /** + * Hide soft input by legacy (discouraged) call to + * {@link android.inputmethodservice.InputMethodService#hideWindow}. + */ + int HIDE_WINDOW_LEGACY_DIRECT = ImeProtoEnums.REASON_HIDE_WINDOW_LEGACY_DIRECT; + + /** + * Show / Hide soft input by + * {@link android.inputmethodservice.InputMethodService#resetStateForNewConfiguration}. + */ + int RESET_NEW_CONFIGURATION = ImeProtoEnums.REASON_RESET_NEW_CONFIGURATION; + + /** + * Show / Hide soft input by + * {@link android.inputmethodservice.InputMethodService#updateCandidatesVisibility}. + */ + int UPDATE_CANDIDATES_VIEW_VISIBILITY = ImeProtoEnums.REASON_UPDATE_CANDIDATES_VIEW_VISIBILITY; + + /** + * Show / Hide soft input by {@link android.view.InsetsController#onControlsChanged}. + */ + int CONTROLS_CHANGED = ImeProtoEnums.REASON_CONTROLS_CHANGED; + + /** + * Show soft input by + * {@link com.android.wm.shell.common.DisplayImeController#onDisplayConfigurationChanged}. + */ + int DISPLAY_CONFIGURATION_CHANGED = ImeProtoEnums.REASON_DISPLAY_CONFIGURATION_CHANGED; + + /** + * Show soft input by + * {@link com.android.wm.shell.common.DisplayImeController.PerDisplay#insetsChanged}. + */ + int DISPLAY_INSETS_CHANGED = ImeProtoEnums.REASON_DISPLAY_INSETS_CHANGED; + + /** + * Show / Hide soft input by + * {@link com.android.wm.shell.common.DisplayImeController.PerDisplay#insetsControlChanged}. + */ + int DISPLAY_CONTROLS_CHANGED = ImeProtoEnums.REASON_DISPLAY_CONTROLS_CHANGED; + + /** Hide soft input by + * {@link com.android.server.inputmethod.InputMethodManagerService#onUnbindCurrentMethodByReset}. + */ + int UNBIND_CURRENT_METHOD = ImeProtoEnums.REASON_UNBIND_CURRENT_METHOD; + + /** Hide soft input by {@link android.view.ImeInsetsSourceConsumer#onAnimationStateChanged}. */ + int HIDE_SOFT_INPUT_ON_ANIMATION_STATE_CHANGED = + ImeProtoEnums.REASON_HIDE_SOFT_INPUT_ON_ANIMATION_STATE_CHANGED; + + /** Hide soft input when we already have a {@link android.view.InsetsSourceControl} by + * {@link android.view.ImeInsetsSourceConsumer#requestHide}. + */ + int HIDE_SOFT_INPUT_REQUEST_HIDE_WITH_CONTROL = + ImeProtoEnums.REASON_HIDE_SOFT_INPUT_REQUEST_HIDE_WITH_CONTROL; + + /** + * Show soft input by + * {@link android.inputmethodservice.InputMethodService#onToggleSoftInput(int, int)}. + */ + int SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT = + ImeProtoEnums.REASON_SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT; + + /** + * Show soft input by the deprecated + * {@link InputMethodManager#showSoftInputFromInputMethod(IBinder, int)}. + */ + int SHOW_SOFT_INPUT_IMM_DEPRECATION = ImeProtoEnums.REASON_SHOW_SOFT_INPUT_IMM_DEPRECATION; } diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 1f4503a69428..dc3b5a8846cf 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -65,12 +65,21 @@ interface IInputMethodManager { InputMethodSubtype getLastInputMethodSubtype(int userId); boolean showSoftInput(in IInputMethodClient client, @nullable IBinder windowToken, - in @nullable ImeTracker.Token statsToken, int flags, int lastClickToolType, + in ImeTracker.Token statsToken, int flags, int lastClickToolType, in @nullable ResultReceiver resultReceiver, int reason); boolean hideSoftInput(in IInputMethodClient client, @nullable IBinder windowToken, - in @nullable ImeTracker.Token statsToken, int flags, + in ImeTracker.Token statsToken, int flags, in @nullable ResultReceiver resultReceiver, int reason); + /** + * A test API for CTS to request hiding the current soft input window, with the request origin + * on the server side. + */ + @EnforcePermission("TEST_INPUT_METHOD") + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " + + "android.Manifest.permission.TEST_INPUT_METHOD)") + void hideSoftInputFromServerForTest(); + // TODO(b/293640003): Remove method once Flags.useZeroJankProxy() is enabled. // If windowToken is null, this just does startInput(). Otherwise this reports that a window // has gained focus, and if 'editorInfo' is non-null then also does startInput. diff --git a/core/tests/coretests/src/android/os/PowerManagerTest.java b/core/tests/coretests/src/android/os/PowerManagerTest.java index cb281ff8a6a7..b9a12ad57c33 100644 --- a/core/tests/coretests/src/android/os/PowerManagerTest.java +++ b/core/tests/coretests/src/android/os/PowerManagerTest.java @@ -32,6 +32,7 @@ import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.RavenwoodFlagsValueProvider; import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.InstrumentationRegistry; @@ -86,7 +87,8 @@ public class PowerManagerTest { // Required for RequiresFlagsEnabled and RequiresFlagsDisabled annotations to take effect. @Rule - public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isUnderRavenwood() ? null + public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isOnRavenwood() + ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule() : DeviceFlagsValueProvider.createCheckFlagsRule(); /** diff --git a/core/tests/coretests/src/android/os/WorkDurationUnitTest.java b/core/tests/coretests/src/android/os/WorkDurationUnitTest.java index c70da6e94385..fcdc5905ef88 100644 --- a/core/tests/coretests/src/android/os/WorkDurationUnitTest.java +++ b/core/tests/coretests/src/android/os/WorkDurationUnitTest.java @@ -22,6 +22,7 @@ import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.RavenwoodFlagsValueProvider; import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.runner.AndroidJUnit4; @@ -40,7 +41,8 @@ public class WorkDurationUnitTest { // Required for RequiresFlagsEnabled and RequiresFlagsDisabled annotations to take effect. @Rule - public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isUnderRavenwood() ? null + public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isOnRavenwood() + ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule() : DeviceFlagsValueProvider.createCheckFlagsRule(); @Before diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java index 8c93fbbc6b47..48ba52621f64 100644 --- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java @@ -24,8 +24,10 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; +import static org.mockito.AdditionalMatchers.and; +import static org.mockito.AdditionalMatchers.not; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -36,6 +38,7 @@ import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.view.WindowManager.BadTokenException; import android.view.WindowManager.LayoutParams; +import android.view.inputmethod.ImeTracker; import android.widget.TextView; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -96,12 +99,12 @@ public class ImeInsetsSourceConsumerTest { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { // test if setVisibility can show IME mImeConsumer.onWindowFocusGained(true); - mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */); + mController.show(WindowInsets.Type.ime(), true /* fromIme */, ImeTracker.Token.empty()); mController.cancelExistingAnimations(); assertTrue((mController.getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0); // test if setVisibility can hide IME - mController.hide(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */); + mController.hide(WindowInsets.Type.ime(), true /* fromIme */, ImeTracker.Token.empty()); mController.cancelExistingAnimations(); assertFalse((mController.getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0); }); @@ -114,8 +117,9 @@ public class ImeInsetsSourceConsumerTest { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { // Request IME visible before control is available. + final var statsToken = ImeTracker.Token.empty(); mImeConsumer.onWindowFocusGained(true); - mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */); + mController.show(WindowInsets.Type.ime(), true /* fromIme */, statsToken); // set control and verify visibility is applied. InsetsSourceControl control = new InsetsSourceControl(ID_IME, @@ -124,10 +128,10 @@ public class ImeInsetsSourceConsumerTest { // IME show animation should be triggered when control becomes available. verify(mController).applyAnimation( eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */, - any() /* statsToken */); + eq(statsToken)); verify(mController, never()).applyAnimation( eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(true) /* fromIme */, - any() /* statsToken */); + eq(statsToken)); }); } @@ -152,9 +156,9 @@ public class ImeInsetsSourceConsumerTest { // Request IME visible before control is available. mImeConsumer.onWindowFocusGained(hasWindowFocus); final boolean imeVisible = hasWindowFocus && hasViewFocus; + final var statsToken = ImeTracker.Token.empty(); if (imeVisible) { - mController.show(WindowInsets.Type.ime(), true /* fromIme */, - null /* statsToken */); + mController.show(WindowInsets.Type.ime(), true /* fromIme */, statsToken); } // set control and verify visibility is applied. @@ -168,23 +172,25 @@ public class ImeInsetsSourceConsumerTest { // and expect skip animation state after getAndClearSkipAnimationOnce invoked. mController.onControlsChanged(new InsetsSourceControl[]{ control }); verify(control).getAndClearSkipAnimationOnce(); + // This ends up creating a new request when we gain control, + // so the statsToken won't match. verify(mController).applyAnimation(eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(false) /* fromIme */, - eq(expectSkipAnim) /* skipAnim */, eq(null) /* statsToken */); + eq(expectSkipAnim) /* skipAnim */, and(not(eq(statsToken)), notNull())); } // If previously hasViewFocus is false, verify when requesting the IME visible next // time will not skip animation. if (!hasViewFocus) { - mController.show(WindowInsets.Type.ime(), true /* fromIme */, - null /* statsToken */); + final var statsTokenNext = ImeTracker.Token.empty(); + mController.show(WindowInsets.Type.ime(), true /* fromIme */, statsTokenNext); mController.onControlsChanged(new InsetsSourceControl[]{ control }); // Verify IME show animation should be triggered when control becomes available and // the animation will be skipped by getAndClearSkipAnimationOnce invoked. verify(control).getAndClearSkipAnimationOnce(); verify(mController).applyAnimation(eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */, - eq(false) /* skipAnim */, eq(null) /* statsToken */); + eq(false) /* skipAnim */, eq(statsTokenNext)); } }); } diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index 1568174e1955..316e191eecbd 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -256,7 +256,7 @@ public class InsetsControllerTest { mController.setSystemDrivenInsetsAnimationLoggingListener(loggingListener); 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(ime(), true /* fromIme */, ImeTracker.Token.empty()); // When using the animation thread, this must not invoke onReady() mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw(); }); @@ -273,14 +273,14 @@ public class InsetsControllerTest { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 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(ime(), true /* fromIme */, ImeTracker.Token.empty()); mController.show(all()); // quickly jump to final state by cancelling it. mController.cancelExistingAnimations(); - final @InsetsType int types = navigationBars() | statusBars() | ime(); + @InsetsType final int types = navigationBars() | statusBars() | ime(); assertEquals(types, mController.getRequestedVisibleTypes() & types); - mController.hide(ime(), true /* fromIme */, null /* statsToken */); + mController.hide(ime(), true /* fromIme */, ImeTracker.Token.empty()); mController.hide(all()); mController.cancelExistingAnimations(); assertEquals(0, mController.getRequestedVisibleTypes() & types); @@ -295,10 +295,10 @@ public class InsetsControllerTest { mController.onControlsChanged(new InsetsSourceControl[] { ime }); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true); - mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */); + mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty()); mController.cancelExistingAnimations(); assertTrue(isRequestedVisible(mController, ime())); - mController.hide(ime(), true /* fromIme */, null /* statsToken */); + mController.hide(ime(), true /* fromIme */, ImeTracker.Token.empty()); mController.cancelExistingAnimations(); assertFalse(isRequestedVisible(mController, ime())); mController.getSourceConsumer(ID_IME, ime()).onWindowFocusLost(); @@ -465,7 +465,7 @@ public class InsetsControllerTest { assertFalse(mController.getState().peekSource(ID_IME).isVisible()); // Pretend IME is calling - mController.show(ime(), true /* fromIme */, null /* statsToken */); + mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty()); // Gaining control shortly after mController.onControlsChanged(createSingletonControl(ID_IME, ime())); @@ -489,7 +489,7 @@ public class InsetsControllerTest { mController.onControlsChanged(createSingletonControl(ID_IME, ime())); // Pretend IME is calling - mController.show(ime(), true /* fromIme */, null /* statsToken */); + mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty()); assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ime())); mController.cancelExistingAnimations(); @@ -567,7 +567,7 @@ public class InsetsControllerTest { verify(listener, never()).onReady(any(), anyInt()); // Pretend that IME is calling. - mController.show(ime(), true /* fromIme */, null /* statsToken */); + mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty()); // Ready gets deferred until next predraw mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw(); @@ -651,7 +651,7 @@ public class InsetsControllerTest { mController.onControlsChanged(createSingletonControl(ID_IME, ime())); // Pretend IME is calling - mController.show(ime(), true /* fromIme */, null /* statsToken */); + mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty()); InsetsState copy = new InsetsState(mController.getState(), true /* copySources */); copy.peekSource(ID_IME).setFrame(0, 1, 2, 3); @@ -851,7 +851,7 @@ public class InsetsControllerTest { // Showing invisible ime should only causes insets change once. clearInvocations(mTestHost); - mController.show(ime(), true /* fromIme */, null /* statsToken */); + mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty()); verify(mTestHost, times(1)).notifyInsetsChanged(); // Sending the same insets state should not cause insets change. @@ -918,7 +918,7 @@ public class InsetsControllerTest { assertNull(imeInsetsConsumer.getControl()); // Verify IME requested visibility should be updated to IME consumer from controller. - mController.show(ime(), true /* fromIme */, null /* statsToken */); + mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty()); assertTrue(isRequestedVisible(mController, ime())); mController.hide(ime()); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index 2f2da8c53db0..b53b9c519cb6 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -387,7 +387,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { // Sets the dim area when the two TaskFragments are adjacent. final boolean dimOnTask = !isStacked - && splitAttributes.getWindowAttributes().getDimArea() == DIM_AREA_ON_TASK + && splitAttributes.getWindowAttributes().getDimAreaBehavior() == DIM_AREA_ON_TASK && Flags.fullscreenDimFlag(); setTaskFragmentDimOnTask(wct, primaryContainer.getTaskFragmentToken(), dimOnTask); setTaskFragmentDimOnTask(wct, secondaryContainer.getTaskFragmentToken(), dimOnTask); @@ -590,7 +590,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { final boolean isFillParent = relativeBounds.isEmpty(); final boolean isIsolatedNavigated = !isFillParent && container.isOverlay(); final boolean dimOnTask = !isFillParent - && attributes.getWindowAttributes().getDimArea() == DIM_AREA_ON_TASK + && attributes.getWindowAttributes().getDimAreaBehavior() == DIM_AREA_ON_TASK && Flags.fullscreenDimFlag(); final IBinder fragmentToken = container.getTaskFragmentToken(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java index 2ea43162d225..ad01d0fa311a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java @@ -20,12 +20,12 @@ import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_CANCEL; import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_END; import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_START; import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY; -import static android.view.inputmethod.ImeTracker.TOKEN_NONE; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.res.Configuration; @@ -51,6 +51,7 @@ import android.view.inputmethod.InputMethodManagerGlobal; import androidx.annotation.VisibleForTesting; +import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.wm.shell.sysui.ShellInit; import java.util.ArrayList; @@ -122,7 +123,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } if (mDisplayController.getDisplayLayout(displayId).rotation() != pd.mRotation && isImeShowing(displayId)) { - pd.startAnimation(true, false /* forceRestart */, null /* statsToken */); + pd.startAnimation(true, false /* forceRestart */, + SoftInputShowHideReason.DISPLAY_CONFIGURATION_CHANGED); } } @@ -257,7 +259,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged mInsetsState.set(insetsState, true /* copySources */); if (mImeShowing && !Objects.equals(oldFrame, newFrame) && newSourceVisible) { if (DEBUG) Slog.d(TAG, "insetsChanged when IME showing, restart animation"); - startAnimation(mImeShowing, true /* forceRestart */, null /* statsToken */); + startAnimation(mImeShowing, true /* forceRestart */, + SoftInputShowHideReason.DISPLAY_INSETS_CHANGED); } } @@ -291,7 +294,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged final boolean positionChanged = !imeSourceControl.getSurfacePosition().equals(lastSurfacePosition); if (positionChanged) { - startAnimation(mImeShowing, true /* forceRestart */, null /* statsToken */); + startAnimation(mImeShowing, true /* forceRestart */, + SoftInputShowHideReason.DISPLAY_CONTROLS_CHANGED); } } else { if (!haveSameLeash(mImeSourceControl, imeSourceControl)) { @@ -384,7 +388,20 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } private void startAnimation(final boolean show, final boolean forceRestart, - @Nullable ImeTracker.Token statsToken) { + @SoftInputShowHideReason int reason) { + final var imeSource = mInsetsState.peekSource(InsetsSource.ID_IME); + if (imeSource == null || mImeSourceControl == null) { + return; + } + final var statsToken = ImeTracker.forLogging().onStart( + show ? ImeTracker.TYPE_SHOW : ImeTracker.TYPE_HIDE, ImeTracker.ORIGIN_WM_SHELL, + reason, false /* fromUser */); + + startAnimation(show, forceRestart, statsToken); + } + + private void startAnimation(final boolean show, final boolean forceRestart, + @NonNull final ImeTracker.Token statsToken) { final InsetsSource imeSource = mInsetsState.peekSource(InsetsSource.ID_IME); if (imeSource == null || mImeSourceControl == null) { ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE); @@ -458,7 +475,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE); mAnimation.addListener(new AnimatorListenerAdapter() { private boolean mCancelled = false; - @Nullable + @NonNull private final ImeTracker.Token mStatsToken = statsToken; @Override @@ -484,7 +501,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } if (DEBUG_IME_VISIBILITY) { EventLog.writeEvent(IMF_IME_REMOTE_ANIM_START, - statsToken != null ? statsToken.getTag() : TOKEN_NONE, + mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE, mDisplayId, mAnimationDirection, alpha, startY , endY, Objects.toString(mImeSourceControl.getLeash()), Objects.toString(mImeSourceControl.getInsetsHint()), @@ -500,7 +517,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged mCancelled = true; if (DEBUG_IME_VISIBILITY) { EventLog.writeEvent(IMF_IME_REMOTE_ANIM_CANCEL, - statsToken != null ? statsToken.getTag() : TOKEN_NONE, mDisplayId, + mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE, + mDisplayId, Objects.toString(mImeSourceControl.getInsetsHint())); } } @@ -528,7 +546,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } if (DEBUG_IME_VISIBILITY) { EventLog.writeEvent(IMF_IME_REMOTE_ANIM_END, - statsToken != null ? statsToken.getTag() : TOKEN_NONE, + mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE, mDisplayId, mAnimationDirection, endY, Objects.toString(mImeSourceControl.getLeash()), Objects.toString(mImeSourceControl.getInsetsHint()), diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java index 9bdda14cf00b..ca06024a9adb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java @@ -277,8 +277,7 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan * * @param types {@link InsetsType} to show * @param fromIme true if this request originated from IME (InputMethodService). - * @param statsToken the token tracking the current IME show request - * or {@code null} otherwise. + * @param statsToken the token tracking the current IME request or {@code null} otherwise. */ default void showInsets(@InsetsType int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {} @@ -288,8 +287,7 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan * * @param types {@link InsetsType} to hide * @param fromIme true if this request originated from IME (InputMethodService). - * @param statsToken the token tracking the current IME hide request - * or {@code null} otherwise. + * @param statsToken the token tracking the current IME request or {@code null} otherwise. */ default void hideInsets(@InsetsType int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java index 01e2f988fbfc..2c0aa12f22d2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java @@ -38,6 +38,7 @@ import android.view.InsetsSource; import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.SurfaceControl; +import android.view.inputmethod.ImeTracker; import androidx.test.filters.SmallTest; @@ -51,6 +52,12 @@ import org.mockito.MockitoAnnotations; import java.util.concurrent.Executor; +/** + * Tests for the display IME controller. + * + * <p> Build/Install/Run: + * atest WMShellUnitTests:DisplayImeControllerTest + */ @SmallTest public class DisplayImeControllerTest extends ShellTestCase { @@ -99,13 +106,13 @@ public class DisplayImeControllerTest extends ShellTestCase { @Test public void showInsets_schedulesNoWorkOnExecutor() { - mPerDisplay.showInsets(ime(), true /* fromIme */, null /* statsToken */); + mPerDisplay.showInsets(ime(), true /* fromIme */, ImeTracker.Token.empty()); verifyZeroInteractions(mExecutor); } @Test public void hideInsets_schedulesNoWorkOnExecutor() { - mPerDisplay.hideInsets(ime(), true /* fromIme */, null /* statsToken */); + mPerDisplay.hideInsets(ime(), true /* fromIme */, ImeTracker.Token.empty()); verifyZeroInteractions(mExecutor); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java index 956f1cd419c2..669e433ba386 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java @@ -50,6 +50,12 @@ import org.mockito.MockitoAnnotations; import java.util.List; +/** + * Tests for the display insets controller. + * + * <p> Build/Install/Run: + * atest WMShellUnitTests:DisplayInsetsControllerTest + */ @SmallTest public class DisplayInsetsControllerTest extends ShellTestCase { @@ -114,9 +120,9 @@ public class DisplayInsetsControllerTest extends ShellTestCase { mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsChanged(null); mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsControlChanged(null, null); mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).showInsets(0, false, - null /* statsToken */); + ImeTracker.Token.empty()); mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).hideInsets(0, false, - null /* statsToken */); + ImeTracker.Token.empty()); mExecutor.flushAll(); assertTrue(defaultListener.topFocusedWindowChangedCount == 1); @@ -136,9 +142,9 @@ public class DisplayInsetsControllerTest extends ShellTestCase { mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsChanged(null); mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsControlChanged(null, null); mInsetsControllersByDisplayId.get(SECOND_DISPLAY).showInsets(0, false, - null /* statsToken */); + ImeTracker.Token.empty()); mInsetsControllersByDisplayId.get(SECOND_DISPLAY).hideInsets(0, false, - null /* statsToken */); + ImeTracker.Token.empty()); mExecutor.flushAll(); assertTrue(defaultListener.topFocusedWindowChangedCount == 1); diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityAnnotations.java b/packages/Connectivity/framework/src/android/net/ConnectivityAnnotations.java deleted file mode 100644 index eb1faa0aa25c..000000000000 --- a/packages/Connectivity/framework/src/android/net/ConnectivityAnnotations.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net; - -import android.annotation.IntDef; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Type annotations for constants used in the connectivity API surface. - * - * The annotations are maintained in a separate class so that it can be built as - * a separate library that other modules can build against, as Typedef should not - * be exposed as SystemApi. - * - * @hide - */ -public final class ConnectivityAnnotations { - private ConnectivityAnnotations() {} - - @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, value = { - ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER, - ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY, - ConnectivityManager.MULTIPATH_PREFERENCE_PERFORMANCE, - }) - public @interface MultipathPreference {} - - @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = false, value = { - ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED, - ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED, - ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED, - }) - public @interface RestrictBackgroundStatus {} -} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 61c3ce7f6988..c2c82b35317b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -1764,40 +1764,4 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> boolean getUnpairing() { return mUnpairing; } - - ListenableFuture<Void> syncProfileForMemberDevice() { - return ThreadUtils.getBackgroundExecutor() - .submit( - () -> { - List<Pair<LocalBluetoothProfile, Boolean>> toSync = - Stream.of( - mProfileManager.getA2dpProfile(), - mProfileManager.getHeadsetProfile(), - mProfileManager.getHearingAidProfile(), - mProfileManager.getLeAudioProfile(), - mProfileManager.getLeAudioBroadcastAssistantProfile()) - .filter(Objects::nonNull) - .map(profile -> new Pair<>(profile, profile.isEnabled(mDevice))) - .toList(); - - for (var t : toSync) { - LocalBluetoothProfile profile = t.first; - boolean enabledForMain = t.second; - - for (var member : mMemberDevices) { - BluetoothDevice btDevice = member.getDevice(); - - if (enabledForMain != profile.isEnabled(btDevice)) { - Log.d(TAG, "Syncing profile " + profile + " to " - + enabledForMain + " for member device " - + btDevice.getAnonymizedAddress() + " of main device " - + mDevice.getAnonymizedAddress()); - profile.setEnabled(btDevice, enabledForMain); - } - } - } - return null; - } - ); - } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java index 32eec7e709af..4e52c77f27b4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java @@ -363,7 +363,6 @@ public class CachedBluetoothDeviceManager { if (profileId == BluetoothProfile.HEADSET || profileId == BluetoothProfile.A2DP || profileId == BluetoothProfile.LE_AUDIO - || profileId == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT || profileId == BluetoothProfile.CSIP_SET_COORDINATOR) { return mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(cachedDevice, state); diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java index e67ec48d3401..a49314aae1b3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java @@ -379,7 +379,6 @@ public class CsipDeviceManager { if (hasChanged) { log("addMemberDevicesIntoMainDevice: After changed, CachedBluetoothDevice list: " + mCachedDevices); - preferredMainDevice.syncProfileForMemberDevice(); } return hasChanged; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java index ca47efdc5df3..1069b715d946 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java @@ -346,11 +346,15 @@ public class HearingAidDeviceManager { } else { long hiSyncId = asha.getHiSyncId(cachedDevice.getDevice()); if (isValidHiSyncId(hiSyncId)) { - final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder() + final HearingAidInfo info = new HearingAidInfo.Builder() .setAshaDeviceSide(asha.getDeviceSide(cachedDevice.getDevice())) .setAshaDeviceMode(asha.getDeviceMode(cachedDevice.getDevice())) - .setHiSyncId(hiSyncId); - return infoBuilder.build(); + .setHiSyncId(hiSyncId) + .build(); + if (DEBUG) { + Log.d(TAG, "generateHearingAidInfo, " + cachedDevice + ", info=" + info); + } + return info; } } @@ -358,15 +362,20 @@ public class HearingAidDeviceManager { final LeAudioProfile leAudioProfile = profileManager.getLeAudioProfile(); if (hapClientProfile == null || leAudioProfile == null) { Log.w(TAG, "HapClientProfile or LeAudioProfile is not supported on this device"); - } else { + } else if (cachedDevice.getProfiles().stream().anyMatch( + p -> p instanceof HapClientProfile)) { int audioLocation = leAudioProfile.getAudioLocation(cachedDevice.getDevice()); int hearingAidType = hapClientProfile.getHearingAidType(cachedDevice.getDevice()); if (audioLocation != BluetoothLeAudio.AUDIO_LOCATION_INVALID && hearingAidType != HapClientProfile.HearingAidType.TYPE_INVALID) { - final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder() + final HearingAidInfo info = new HearingAidInfo.Builder() .setLeAudioLocation(audioLocation) - .setHapDeviceType(hearingAidType); - return infoBuilder.build(); + .setHapDeviceType(hearingAidType) + .build(); + if (DEBUG) { + Log.d(TAG, "generateHearingAidInfo, " + cachedDevice + ", info=" + info); + } + return info; } } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt index 7d614f0e4d39..65b73caeada7 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt @@ -15,6 +15,7 @@ */ package com.android.settingslib.wifi +import android.content.ComponentName import android.content.Context import android.content.Intent import android.graphics.drawable.Drawable @@ -22,14 +23,23 @@ import android.icu.text.MessageFormat import android.net.wifi.ScanResult import android.net.wifi.WifiConfiguration import android.net.wifi.WifiConfiguration.NetworkSelectionStatus +import android.net.wifi.WifiManager import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo import android.os.Bundle import android.os.SystemClock import android.util.Log import androidx.annotation.VisibleForTesting +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope import com.android.settingslib.R import com.android.settingslib.flags.Flags.newStatusBarIcons import java.util.Locale +import kotlin.coroutines.resume +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.asExecutor +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withContext open class WifiUtils { @@ -146,7 +156,6 @@ open class WifiUtils { } } - @JvmStatic fun buildLoggingSummary(accessPoint: AccessPoint, config: WifiConfiguration?): String { val summary = StringBuilder() @@ -458,5 +467,40 @@ open class WifiUtils { arguments["count"] = connectedDevices return msgFormat.format(arguments) } + + @JvmStatic + fun checkWepAllowed( + context: Context, + lifecycleOwner: LifecycleOwner, + ssid: String, + onAllowed: () -> Unit, + ) { + lifecycleOwner.lifecycleScope.launch { + val wifiManager = context.getSystemService(WifiManager::class.java) ?: return@launch + if (wifiManager.queryWepAllowed()) { + onAllowed() + } else { + val intent = Intent(Intent.ACTION_MAIN).apply { + component = ComponentName( + "com.android.settings", + "com.android.settings.network.WepNetworkDialogActivity" + ) + putExtra(SSID, ssid) + } + context.startActivity(intent) + } + } + } + + private suspend fun WifiManager.queryWepAllowed(): Boolean = + withContext(Dispatchers.Default) { + suspendCancellableCoroutine { continuation -> + queryWepAllowed(Dispatchers.Default.asExecutor()) { + continuation.resume(it) + } + } + } + + const val SSID = "ssid" } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java index 461ecf5d3c84..5996dbb322fc 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java @@ -18,9 +18,7 @@ package com.android.settingslib.bluetooth; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -58,8 +56,6 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.shadow.api.Shadow; -import java.util.concurrent.ExecutionException; - @RunWith(RobolectricTestRunner.class) @Config(shadows = {ShadowBluetoothAdapter.class}) public class CachedBluetoothDeviceTest { @@ -1823,52 +1819,6 @@ public class CachedBluetoothDeviceTest { assertThat(mCachedDevice.isConnectedHearingAidDevice()).isFalse(); } - @Test - public void syncProfileForMemberDevice_hasDiff_shouldSync() - throws ExecutionException, InterruptedException { - mCachedDevice.addMemberDevice(mSubCachedDevice); - when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile); - when(mProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile); - when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile); - - when(mA2dpProfile.isEnabled(mDevice)).thenReturn(true); - when(mHearingAidProfile.isEnabled(mDevice)).thenReturn(true); - when(mLeAudioProfile.isEnabled(mDevice)).thenReturn(true); - - when(mA2dpProfile.isEnabled(mSubDevice)).thenReturn(true); - when(mHearingAidProfile.isEnabled(mSubDevice)).thenReturn(false); - when(mLeAudioProfile.isEnabled(mSubDevice)).thenReturn(false); - - mCachedDevice.syncProfileForMemberDevice().get(); - - verify(mA2dpProfile, never()).setEnabled(any(BluetoothDevice.class), anyBoolean()); - verify(mHearingAidProfile).setEnabled(any(BluetoothDevice.class), eq(true)); - verify(mLeAudioProfile).setEnabled(any(BluetoothDevice.class), eq(true)); - } - - @Test - public void syncProfileForMemberDevice_noDiff_shouldNotSync() - throws ExecutionException, InterruptedException { - mCachedDevice.addMemberDevice(mSubCachedDevice); - when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile); - when(mProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile); - when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile); - - when(mA2dpProfile.isEnabled(mDevice)).thenReturn(false); - when(mHearingAidProfile.isEnabled(mDevice)).thenReturn(false); - when(mLeAudioProfile.isEnabled(mDevice)).thenReturn(true); - - when(mA2dpProfile.isEnabled(mSubDevice)).thenReturn(false); - when(mHearingAidProfile.isEnabled(mSubDevice)).thenReturn(false); - when(mLeAudioProfile.isEnabled(mSubDevice)).thenReturn(true); - - mCachedDevice.syncProfileForMemberDevice().get(); - - verify(mA2dpProfile, never()).setEnabled(any(BluetoothDevice.class), anyBoolean()); - verify(mHearingAidProfile, never()).setEnabled(any(BluetoothDevice.class), anyBoolean()); - verify(mLeAudioProfile, never()).setEnabled(any(BluetoothDevice.class), anyBoolean()); - } - private HearingAidInfo getLeftAshaHearingAidInfo() { return new HearingAidInfo.Builder() .setAshaDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT) diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt deleted file mode 100644 index 76931a2b4d41..000000000000 --- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright (C) 2022 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.compose - -import android.content.Context -import android.graphics.Point -import android.view.View -import android.view.WindowInsets -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.lifecycle.LifecycleOwner -import com.android.compose.theme.LocalAndroidColorScheme -import com.android.compose.theme.PlatformTheme -import com.android.compose.ui.platform.DensityAwareComposeView -import com.android.internal.policy.ScreenDecorationsUtils -import com.android.systemui.bouncer.ui.BouncerDialogFactory -import com.android.systemui.bouncer.ui.composable.BouncerContent -import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel -import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation -import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout -import com.android.systemui.common.ui.compose.windowinsets.ScreenDecorProvider -import com.android.systemui.communal.ui.compose.CommunalContainer -import com.android.systemui.communal.ui.compose.CommunalHub -import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel -import com.android.systemui.communal.ui.viewmodel.CommunalViewModel -import com.android.systemui.communal.widgets.WidgetConfigurator -import com.android.systemui.keyboard.stickykeys.ui.view.createStickyKeyIndicatorView -import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel -import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint -import com.android.systemui.keyguard.ui.composable.LockscreenContent -import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint -import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel -import com.android.systemui.people.ui.compose.PeopleScreen -import com.android.systemui.people.ui.viewmodel.PeopleViewModel -import com.android.systemui.qs.footer.ui.compose.FooterActions -import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel -import com.android.systemui.scene.shared.model.Scene -import com.android.systemui.scene.shared.model.SceneDataSourceDelegator -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.scene.ui.composable.ComposableScene -import com.android.systemui.scene.ui.composable.SceneContainer -import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel -import com.android.systemui.volume.panel.ui.composable.VolumePanelRoot -import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn - -/** The Compose facade, when Compose is available. */ -object ComposeFacade : BaseComposeFacade { - override fun isComposeAvailable(): Boolean = true - - override fun composeInitializer(): ComposeInitializer = ComposeInitializerImpl - - override fun setPeopleSpaceActivityContent( - activity: ComponentActivity, - viewModel: PeopleViewModel, - onResult: (PeopleViewModel.Result) -> Unit, - ) { - activity.setContent { PlatformTheme { PeopleScreen(viewModel, onResult) } } - } - - override fun setCommunalEditWidgetActivityContent( - activity: ComponentActivity, - viewModel: BaseCommunalViewModel, - widgetConfigurator: WidgetConfigurator, - onOpenWidgetPicker: () -> Unit, - onEditDone: () -> Unit, - ) { - activity.setContent { - PlatformTheme { - Box( - modifier = - Modifier.fillMaxSize() - .background(LocalAndroidColorScheme.current.outlineVariant), - ) { - CommunalHub( - viewModel = viewModel, - onOpenWidgetPicker = onOpenWidgetPicker, - widgetConfigurator = widgetConfigurator, - onEditDone = onEditDone, - ) - } - } - } - } - - override fun setVolumePanelActivityContent( - activity: ComponentActivity, - viewModel: VolumePanelViewModel, - onDismiss: () -> Unit, - ) { - activity.setContent { - VolumePanelRoot( - viewModel = viewModel, - onDismiss = onDismiss, - ) - } - } - - override fun createFooterActionsView( - context: Context, - viewModel: FooterActionsViewModel, - qsVisibilityLifecycleOwner: LifecycleOwner, - ): View { - return DensityAwareComposeView(context).apply { - setContent { PlatformTheme { FooterActions(viewModel, qsVisibilityLifecycleOwner) } } - } - } - - override fun createSceneContainerView( - scope: CoroutineScope, - context: Context, - viewModel: SceneContainerViewModel, - windowInsets: StateFlow<WindowInsets?>, - sceneByKey: Map<SceneKey, Scene>, - dataSourceDelegator: SceneDataSourceDelegator, - ): View { - return ComposeView(context).apply { - setContent { - PlatformTheme { - ScreenDecorProvider( - displayCutout = displayCutoutFromWindowInsets(scope, context, windowInsets), - screenCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context) - ) { - SceneContainer( - viewModel = viewModel, - sceneByKey = - sceneByKey.mapValues { (_, scene) -> scene as ComposableScene }, - dataSourceDelegator = dataSourceDelegator, - ) - } - } - } - } - } - - override fun createStickyKeysIndicatorContent( - context: Context, - viewModel: StickyKeysIndicatorViewModel - ): View { - return createStickyKeyIndicatorView(context, viewModel) - } - - override fun createCommunalView( - context: Context, - viewModel: BaseCommunalViewModel, - ): View { - return ComposeView(context).apply { - setContent { PlatformTheme { CommunalHub(viewModel = viewModel) } } - } - } - - override fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View { - return ComposeView(context).apply { - setContent { PlatformTheme { CommunalContainer(viewModel = viewModel) } } - } - } - - // TODO(b/298525212): remove once Compose exposes window inset bounds. - private fun displayCutoutFromWindowInsets( - scope: CoroutineScope, - context: Context, - windowInsets: StateFlow<WindowInsets?>, - ): StateFlow<DisplayCutout> = - windowInsets - .map { - val boundingRect = it?.displayCutout?.boundingRectTop - val width = boundingRect?.let { boundingRect.right - boundingRect.left } ?: 0 - val left = boundingRect?.left?.toDp(context) ?: 0.dp - val top = boundingRect?.top?.toDp(context) ?: 0.dp - val right = boundingRect?.right?.toDp(context) ?: 0.dp - val bottom = boundingRect?.bottom?.toDp(context) ?: 0.dp - val location = - when { - width <= 0f -> CutoutLocation.NONE - left <= 0.dp -> CutoutLocation.LEFT - right >= getDisplayWidth(context) -> CutoutLocation.RIGHT - else -> CutoutLocation.CENTER - } - DisplayCutout( - left, - top, - right, - bottom, - location, - ) - } - .stateIn(scope, SharingStarted.WhileSubscribed(), DisplayCutout()) - - // TODO(b/298525212): remove once Compose exposes window inset bounds. - private fun getDisplayWidth(context: Context): Dp { - val point = Point() - checkNotNull(context.display).getRealSize(point) - return point.x.dp - } - - // TODO(b/298525212): remove once Compose exposes window inset bounds. - private fun Int.toDp(context: Context): Dp { - return (this.toFloat() / context.resources.displayMetrics.density).dp - } - - override fun createBouncer( - context: Context, - viewModel: BouncerViewModel, - dialogFactory: BouncerDialogFactory, - ): View { - return ComposeView(context).apply { - setContent { PlatformTheme { BouncerContent(viewModel, dialogFactory) } } - } - } - - override fun createLockscreen( - context: Context, - viewModel: LockscreenContentViewModel, - blueprints: Set<@JvmSuppressWildcards LockscreenSceneBlueprint>, - ): View { - val sceneBlueprints = - blueprints.mapNotNull { it as? ComposableLockscreenSceneBlueprint }.toSet() - return ComposeView(context).apply { - setContent { - LockscreenContent(viewModel = viewModel, blueprints = sceneBlueprints) - .Content(modifier = Modifier.fillMaxSize()) - } - } - } -} diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt deleted file mode 100644 index 1674591c30b5..000000000000 --- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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.compose - -import android.view.View -import androidx.lifecycle.findViewTreeLifecycleOwner -import androidx.lifecycle.setViewTreeLifecycleOwner -import androidx.lifecycle.Lifecycle -import androidx.savedstate.SavedStateRegistryController -import androidx.savedstate.SavedStateRegistryOwner -import com.android.compose.animation.ViewTreeSavedStateRegistryOwner -import com.android.systemui.lifecycle.ViewLifecycleOwner - -internal object ComposeInitializerImpl : ComposeInitializer { - override fun onAttachedToWindow(root: View) { - if (root.findViewTreeLifecycleOwner() != null) { - error("root $root already has a LifecycleOwner") - } - - val parent = root.parent - if (parent is View && parent.id != android.R.id.content) { - error( - "ComposeInitializer.onAttachedToWindow(View) must be called on the content child." + - "Outside of activities and dialogs, this is usually the top-most View of a " + - "window." - ) - } - - // The lifecycle owner, which is STARTED when [root] is visible and RESUMED when [root] is - // both visible and focused. - val lifecycleOwner = ViewLifecycleOwner(root) - - // We create a trivial implementation of [SavedStateRegistryOwner] that does not do any save - // or restore because SystemUI process is always running and top-level windows using this - // initializer are created once, when the process is started. - val savedStateRegistryOwner = - object : SavedStateRegistryOwner { - private val savedStateRegistryController = - SavedStateRegistryController.create(this).apply { performRestore(null) } - - override val savedStateRegistry = savedStateRegistryController.savedStateRegistry - - override val lifecycle: Lifecycle - get() = lifecycleOwner.lifecycle - } - - // We must call [ViewLifecycleOwner.onCreate] after creating the [SavedStateRegistryOwner] - // because `onCreate` might move the lifecycle state to STARTED which will make - // [SavedStateRegistryController.performRestore] throw. - lifecycleOwner.onCreate() - - // Set the owners on the root. They will be reused by any ComposeView inside the root - // hierarchy. - root.setViewTreeLifecycleOwner(lifecycleOwner) - ViewTreeSavedStateRegistryOwner.set(root, savedStateRegistryOwner) - } - - override fun onDetachedFromWindow(root: View) { - (root.findViewTreeLifecycleOwner() as ViewLifecycleOwner).onDestroy() - root.setViewTreeLifecycleOwner(null) - ViewTreeSavedStateRegistryOwner.set(root, null) - } -} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt index 4c972e9195e9..a3371d3d24f4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt @@ -42,6 +42,25 @@ class AodToGoneTransitionViewModelTest : SysuiTestCase() { val underTest = kosmos.aodToGoneTransitionViewModel @Test + fun lockscreenAlpha() = + testScope.runTest { + val viewState = ViewStateAccessor(alpha = { 0.5f }) + val alpha by collectValues(underTest.lockscreenAlpha(viewState)) + + repository.sendTransitionSteps( + from = KeyguardState.AOD, + to = KeyguardState.GONE, + testScope + ) + + assertThat(alpha[0]).isEqualTo(0.5f) + // Fades out just prior to halfway + assertThat(alpha[1]).isEqualTo(0f) + // Must finish at 0 + assertThat(alpha[2]).isEqualTo(0f) + } + + @Test fun deviceEntryParentViewHides() = testScope.runTest { val deviceEntryParentViewAlpha by collectValues(underTest.deviceEntryParentViewAlpha) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModelTest.kt index 7e937db842ff..79671b885c56 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModelTest.kt @@ -51,6 +51,25 @@ class DozingToGoneTransitionViewModelTest : SysuiTestCase() { } @Test + fun lockscreenAlpha() = + testScope.runTest { + val viewState = ViewStateAccessor(alpha = { 0.6f }) + val alpha by collectValues(underTest.lockscreenAlpha(viewState)) + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.DOZING, + to = KeyguardState.GONE, + testScope + ) + + assertThat(alpha[0]).isEqualTo(0.6f) + // Fades out just prior to halfway + assertThat(alpha[1]).isEqualTo(0f) + // Must finish at 0 + assertThat(alpha[2]).isEqualTo(0f) + } + + @Test fun deviceEntryParentViewDisappear() = testScope.runTest { val values by collectValues(underTest.deviceEntryParentViewAlpha) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt index 503fd34ce2c2..8e15b5d5657f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt @@ -328,4 +328,42 @@ class KeyguardRootViewModelTest : SysuiTestCase() { shadeRepository.setQsExpansion(0.5f) assertThat(alpha).isEqualTo(0f) } + + @Test + fun alpha_idleOnOccluded_isZero() = + testScope.runTest { + val alpha by collectLastValue(underTest.alpha(viewState)) + assertThat(alpha).isEqualTo(1f) + + // Go to OCCLUDED state + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.OCCLUDED, + testScope = testScope, + ) + assertThat(alpha).isEqualTo(0f) + + // Try pulling down shade and ensure the value doesn't change + shadeRepository.setQsExpansion(0.5f) + assertThat(alpha).isEqualTo(0f) + } + + @Test + fun alpha_idleOnGone_isZero() = + testScope.runTest { + val alpha by collectLastValue(underTest.alpha(viewState)) + assertThat(alpha).isEqualTo(1f) + + // Go to GONE state + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + testScope = testScope, + ) + assertThat(alpha).isEqualTo(0f) + + // Try pulling down shade and ensure the value doesn't change + shadeRepository.setQsExpansion(0.5f) + assertThat(alpha).isEqualTo(0f) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt index c01f1c71fdd3..8aa0e3fc4d23 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt @@ -35,10 +35,10 @@ import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.plugins.ActivityStarter.OnDismissAction import com.android.systemui.settings.UserTracker import com.android.systemui.shade.ShadeController -import com.android.systemui.shade.ShadeViewController import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.shade.data.repository.ShadeAnimationRepository import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.SysuiStatusBarStateController @@ -76,7 +76,7 @@ class ActivityStarterImplTest : SysuiTestCase() { @Mock private lateinit var biometricUnlockController: BiometricUnlockController @Mock private lateinit var keyguardViewMediator: KeyguardViewMediator @Mock private lateinit var shadeController: ShadeController - @Mock private lateinit var shadeViewController: ShadeViewController + @Mock private lateinit var commandQueue: CommandQueue @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager @Mock private lateinit var mActivityTransitionAnimator: ActivityTransitionAnimator @Mock private lateinit var lockScreenUserManager: NotificationLockscreenUserManager @@ -105,7 +105,7 @@ class ActivityStarterImplTest : SysuiTestCase() { Lazy { biometricUnlockController }, Lazy { keyguardViewMediator }, Lazy { shadeController }, - Lazy { shadeViewController }, + commandQueue, shadeAnimationInteractor, Lazy { statusBarKeyguardViewManager }, Lazy { notifShadeWindowController }, diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt index 36d3ed52b655..f1a0e5e3539c 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt @@ -11,7 +11,6 @@ import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags import com.android.systemui.bouncer.ui.BouncerDialogFactory import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel -import com.android.systemui.compose.ComposeFacade import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.Flags.COMPOSE_BOUNCER_ENABLED import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel @@ -60,11 +59,7 @@ constructor( private val composeBouncerDependencies: Lazy<ComposeBouncerDependencies>, ) { fun bind(view: ViewGroup) { - if ( - COMPOSE_BOUNCER_ENABLED && - composeBouncerFlags.isOnlyComposeBouncerEnabled() && - ComposeFacade.isComposeAvailable() - ) { + if (COMPOSE_BOUNCER_ENABLED && composeBouncerFlags.isOnlyComposeBouncerEnabled()) { val deps = composeBouncerDependencies.get() ComposeBouncerViewBinder.bind( view, diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt index 7b053956091e..179fa874db79 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt @@ -1,15 +1,17 @@ package com.android.systemui.bouncer.ui.binder import android.view.ViewGroup +import androidx.compose.ui.platform.ComposeView import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle +import com.android.compose.theme.PlatformTheme import com.android.keyguard.ViewMediatorCallback import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.bouncer.ui.BouncerDialogFactory +import com.android.systemui.bouncer.ui.composable.BouncerContent import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel -import com.android.systemui.compose.ComposeFacade import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.user.domain.interactor.SelectedUserInteractor import kotlinx.coroutines.flow.collectLatest @@ -27,12 +29,11 @@ object ComposeBouncerViewBinder { viewMediatorCallback: ViewMediatorCallback?, ) { view.addView( - ComposeFacade.createBouncer( - view.context, - viewModel, - dialogFactory, - ) + ComposeView(view.context).apply { + setContent { PlatformTheme { BouncerContent(viewModel, dialogFactory) } } + } ) + view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { launch { diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt index a5a390d7683b..48b3e4c3616d 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt @@ -25,14 +25,21 @@ import android.util.Log import android.view.IWindowManager import android.view.WindowInsets import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.ui.Modifier +import com.android.compose.theme.LocalAndroidColorScheme +import com.android.compose.theme.PlatformTheme import com.android.internal.logging.UiEventLogger import com.android.systemui.communal.shared.log.CommunalUiEvent import com.android.systemui.communal.shared.model.CommunalSceneKey +import com.android.systemui.communal.ui.compose.CommunalHub import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent -import com.android.systemui.compose.ComposeFacade.setCommunalEditWidgetActivityContent import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger import com.android.systemui.log.dagger.CommunalLog @@ -110,56 +117,68 @@ constructor( val preselectedKey = intent.getStringExtra(EXTRA_PRESELECTED_KEY) communalViewModel.setSelectedKey(preselectedKey) - setCommunalEditWidgetActivityContent( - activity = this, - viewModel = communalViewModel, - widgetConfigurator = widgetConfigurator, - onOpenWidgetPicker = { - val intent = - Intent(Intent.ACTION_MAIN).also { it.addCategory(Intent.CATEGORY_HOME) } - packageManager - .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) - ?.activityInfo - ?.packageName - ?.let { packageName -> - try { - addWidgetActivityLauncher.launch( - Intent(Intent.ACTION_PICK).apply { - setPackage(packageName) - putExtra( - EXTRA_DESIRED_WIDGET_WIDTH, - resources.getDimensionPixelSize( - R.dimen.communal_widget_picker_desired_width - ) - ) - putExtra( - EXTRA_DESIRED_WIDGET_HEIGHT, - resources.getDimensionPixelSize( - R.dimen.communal_widget_picker_desired_height - ) - ) - putExtra( - AppWidgetManager.EXTRA_CATEGORY_FILTER, - communalViewModel.getCommunalWidgetCategories - ) - } + setContent { + PlatformTheme { + Box( + modifier = + Modifier.fillMaxSize() + .background(LocalAndroidColorScheme.current.outlineVariant), + ) { + CommunalHub( + viewModel = communalViewModel, + onOpenWidgetPicker = ::onOpenWidgetPicker, + widgetConfigurator = widgetConfigurator, + onEditDone = ::onEditDone, + ) + } + } + } + } + + private fun onOpenWidgetPicker() { + val intent = Intent(Intent.ACTION_MAIN).also { it.addCategory(Intent.CATEGORY_HOME) } + packageManager + .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) + ?.activityInfo + ?.packageName + ?.let { packageName -> + try { + addWidgetActivityLauncher.launch( + Intent(Intent.ACTION_PICK).apply { + setPackage(packageName) + putExtra( + EXTRA_DESIRED_WIDGET_WIDTH, + resources.getDimensionPixelSize( + R.dimen.communal_widget_picker_desired_width + ) + ) + putExtra( + EXTRA_DESIRED_WIDGET_HEIGHT, + resources.getDimensionPixelSize( + R.dimen.communal_widget_picker_desired_height + ) + ) + putExtra( + AppWidgetManager.EXTRA_CATEGORY_FILTER, + communalViewModel.getCommunalWidgetCategories ) - } catch (e: Exception) { - Log.e(TAG, "Failed to launch widget picker activity", e) } - } - ?: run { Log.e(TAG, "Couldn't resolve launcher package name") } - }, - onEditDone = { - try { - communalViewModel.onSceneChanged(CommunalSceneKey.Communal) - checkNotNull(windowManagerService).lockNow(/* options */ null) - finish() - } catch (e: RemoteException) { - Log.e(TAG, "Couldn't lock the device as WindowManager is dead.") + ) + } catch (e: Exception) { + Log.e(TAG, "Failed to launch widget picker activity", e) } } - ) + ?: run { Log.e(TAG, "Couldn't resolve launcher package name") } + } + + private fun onEditDone() { + try { + communalViewModel.onSceneChanged(CommunalSceneKey.Communal) + checkNotNull(windowManagerService).lockNow(/* options */ null) + finish() + } catch (e: RemoteException) { + Log.e(TAG, "Couldn't lock the device as WindowManager is dead.") + } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt deleted file mode 100644 index a0aaa906802a..000000000000 --- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) 2022 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.compose - -import android.content.Context -import android.view.View -import android.view.WindowInsets -import androidx.activity.ComponentActivity -import androidx.lifecycle.LifecycleOwner -import com.android.systemui.bouncer.ui.BouncerDialogFactory -import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel -import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel -import com.android.systemui.communal.ui.viewmodel.CommunalViewModel -import com.android.systemui.communal.widgets.WidgetConfigurator -import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel -import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint -import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel -import com.android.systemui.people.ui.viewmodel.PeopleViewModel -import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel -import com.android.systemui.scene.shared.model.Scene -import com.android.systemui.scene.shared.model.SceneDataSourceDelegator -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel -import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.StateFlow - -/** - * A facade to interact with Compose, when it is available. - * - * You should access this facade by calling the static methods on - * [com.android.systemui.compose.ComposeFacade] directly. - */ -interface BaseComposeFacade { - /** - * Whether Compose is currently available. This function should be checked before calling any - * other functions on this facade. - * - * This value will never change at runtime. - */ - fun isComposeAvailable(): Boolean - - /** - * Return the [ComposeInitializer] to make Compose usable in windows outside normal activities. - */ - fun composeInitializer(): ComposeInitializer - - /** Bind the content of [activity] to [viewModel]. */ - fun setPeopleSpaceActivityContent( - activity: ComponentActivity, - viewModel: PeopleViewModel, - onResult: (PeopleViewModel.Result) -> Unit, - ) - - /** Bind the content of [activity] to [viewModel]. */ - fun setCommunalEditWidgetActivityContent( - activity: ComponentActivity, - viewModel: BaseCommunalViewModel, - widgetConfigurator: WidgetConfigurator, - onOpenWidgetPicker: () -> Unit, - onEditDone: () -> Unit, - ) - - fun setVolumePanelActivityContent( - activity: ComponentActivity, - viewModel: VolumePanelViewModel, - onDismiss: () -> Unit, - ) - - /** Create a [View] to represent [viewModel] on screen. */ - fun createFooterActionsView( - context: Context, - viewModel: FooterActionsViewModel, - qsVisibilityLifecycleOwner: LifecycleOwner, - ): View - - /** Create a [View] to represent [viewModel] on screen. */ - fun createSceneContainerView( - scope: CoroutineScope, - context: Context, - viewModel: SceneContainerViewModel, - windowInsets: StateFlow<WindowInsets?>, - sceneByKey: Map<SceneKey, Scene>, - dataSourceDelegator: SceneDataSourceDelegator, - ): View - - /** Creates sticky key indicator content presenting provided [viewModel] */ - fun createStickyKeysIndicatorContent( - context: Context, - viewModel: StickyKeysIndicatorViewModel - ): View - - /** Create a [View] to represent [viewModel] on screen. */ - fun createCommunalView( - context: Context, - viewModel: BaseCommunalViewModel, - ): View - - /** Create a [View] to represent the [BouncerViewModel]. */ - fun createBouncer( - context: Context, - viewModel: BouncerViewModel, - dialogFactory: BouncerDialogFactory, - ): View - - /** Creates a container that hosts the communal UI and handles gesture transitions. */ - fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View - - /** Creates a [View] that represents the Lockscreen. */ - fun createLockscreen( - context: Context, - viewModel: LockscreenContentViewModel, - blueprints: Set<@JvmSuppressWildcards LockscreenSceneBlueprint>, - ): View -} diff --git a/packages/SystemUI/src/com/android/systemui/compose/ComposeInitializer.kt b/packages/SystemUI/src/com/android/systemui/compose/ComposeInitializer.kt index 90dc3a00daa2..813e0e025bf5 100644 --- a/packages/SystemUI/src/com/android/systemui/compose/ComposeInitializer.kt +++ b/packages/SystemUI/src/com/android/systemui/compose/ComposeInitializer.kt @@ -17,6 +17,13 @@ package com.android.systemui.compose import android.view.View +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.findViewTreeLifecycleOwner +import androidx.lifecycle.setViewTreeLifecycleOwner +import androidx.savedstate.SavedStateRegistryController +import androidx.savedstate.SavedStateRegistryOwner +import com.android.compose.animation.ViewTreeSavedStateRegistryOwner +import com.android.systemui.lifecycle.ViewLifecycleOwner /** * An initializer to use Compose outside of an Activity, e.g. inside a window added directly using @@ -39,10 +46,55 @@ import android.view.View * } * ``` */ -interface ComposeInitializer { +object ComposeInitializer { /** Function to be called on your window root view's [View.onAttachedToWindow] function. */ - fun onAttachedToWindow(root: View) + fun onAttachedToWindow(root: View) { + if (root.findViewTreeLifecycleOwner() != null) { + error("root $root already has a LifecycleOwner") + } + + val parent = root.parent + if (parent is View && parent.id != android.R.id.content) { + error( + "ComposeInitializer.onAttachedToWindow(View) must be called on the content child." + + "Outside of activities and dialogs, this is usually the top-most View of a " + + "window." + ) + } + + // The lifecycle owner, which is STARTED when [root] is visible and RESUMED when [root] is + // both visible and focused. + val lifecycleOwner = ViewLifecycleOwner(root) + + // We create a trivial implementation of [SavedStateRegistryOwner] that does not do any save + // or restore because SystemUI process is always running and top-level windows using this + // initializer are created once, when the process is started. + val savedStateRegistryOwner = + object : SavedStateRegistryOwner { + private val savedStateRegistryController = + SavedStateRegistryController.create(this).apply { performRestore(null) } + + override val savedStateRegistry = savedStateRegistryController.savedStateRegistry + + override val lifecycle: Lifecycle + get() = lifecycleOwner.lifecycle + } + + // We must call [ViewLifecycleOwner.onCreate] after creating the [SavedStateRegistryOwner] + // because `onCreate` might move the lifecycle state to STARTED which will make + // [SavedStateRegistryController.performRestore] throw. + lifecycleOwner.onCreate() + + // Set the owners on the root. They will be reused by any ComposeView inside the root + // hierarchy. + root.setViewTreeLifecycleOwner(lifecycleOwner) + ViewTreeSavedStateRegistryOwner.set(root, savedStateRegistryOwner) + } /** Function to be called on your window root view's [View.onDetachedFromWindow] function. */ - fun onDetachedFromWindow(root: View) + fun onDetachedFromWindow(root: View) { + (root.findViewTreeLifecycleOwner() as ViewLifecycleOwner).onDestroy() + root.setViewTreeLifecycleOwner(null) + ViewTreeSavedStateRegistryOwner.set(root, null) + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeyDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeyDialogFactory.kt index 3ed58a7fe5ae..f9084e5a6191 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeyDialogFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeyDialogFactory.kt @@ -26,9 +26,9 @@ import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE import android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL import androidx.activity.ComponentDialog -import com.android.systemui.compose.ComposeFacade import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyboard.stickykeys.ui.view.createStickyKeyIndicatorView import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel import com.android.systemui.res.R import javax.inject.Inject @@ -48,7 +48,7 @@ constructor( return ComponentDialog(context, R.style.Theme_SystemUI_Dialog).apply { // because we're requesting window feature it must be called before setting content window?.setStickyKeyWindowAttributes() - setContentView(ComposeFacade.createStickyKeysIndicatorContent(context, viewModel)) + setContentView(createStickyKeyIndicatorView(context, viewModel)) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt index 842fd04bfcc5..78c4e77cad74 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt @@ -17,15 +17,13 @@ package com.android.systemui.keyboard.stickykeys.ui import android.app.Dialog -import android.util.Log -import com.android.systemui.compose.ComposeFacade import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyboard.stickykeys.StickyKeysLogger import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel +import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -import javax.inject.Inject @SysUISingleton class StickyKeysIndicatorCoordinator @@ -40,11 +38,6 @@ constructor( private var dialog: Dialog? = null fun startListening() { - // this check needs to be moved to PhysicalKeyboardCoreStartable - if (!ComposeFacade.isComposeAvailable()) { - Log.e("StickyKeysIndicatorCoordinator", "Compose is required for this UI") - return - } applicationScope.launch { viewModel.indicatorContent.collect { stickyKeys -> stickyKeysLogger.logNewUiState(stickyKeys) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index 301942f6242b..106fdf1fbcbe 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -20,6 +20,9 @@ package com.android.systemui.keyguard import android.content.Context import android.view.LayoutInflater import android.view.View +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.ComposeView import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet.BOTTOM import androidx.constraintlayout.widget.ConstraintSet.END @@ -35,7 +38,6 @@ import com.android.keyguard.dagger.KeyguardStatusViewComponent import com.android.systemui.CoreStartable import com.android.systemui.Flags.keyguardBottomAreaRefactor import com.android.systemui.common.ui.ConfigurationState -import com.android.systemui.compose.ComposeFacade import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor @@ -45,6 +47,8 @@ import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder +import com.android.systemui.keyguard.ui.composable.LockscreenContent +import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea import com.android.systemui.keyguard.ui.view.KeyguardRootView import com.android.systemui.keyguard.ui.view.layout.KeyguardBlueprintCommandListener @@ -132,7 +136,7 @@ constructor( if (!SceneContainerFlag.isEnabled) { if (ComposeLockscreen.isEnabled) { val composeView = - ComposeFacade.createLockscreen( + createLockscreen( context = context, viewModel = lockscreenContentViewModel, blueprints = lockscreenSceneBlueprintsLazy.get(), @@ -207,6 +211,21 @@ constructor( ) } + private fun createLockscreen( + context: Context, + viewModel: LockscreenContentViewModel, + blueprints: Set<@JvmSuppressWildcards LockscreenSceneBlueprint>, + ): View { + val sceneBlueprints = + blueprints.mapNotNull { it as? ComposableLockscreenSceneBlueprint }.toSet() + return ComposeView(context).apply { + setContent { + LockscreenContent(viewModel = viewModel, blueprints = sceneBlueprints) + .Content(modifier = Modifier.fillMaxSize()) + } + } + } + /** * Temporary, to allow NotificationPanelViewController to use the same instance while code is * migrated: b/288242803 diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt index e0b5c0e1f4c6..617982f8532c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt @@ -27,12 +27,14 @@ import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled +import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.util.kotlin.Utils.Companion.sample import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.launch @SysUISingleton @@ -45,6 +47,7 @@ constructor( @Background bgDispatcher: CoroutineDispatcher, @Main mainDispatcher: CoroutineDispatcher, private val keyguardInteractor: KeyguardInteractor, + private val powerInteractor: PowerInteractor, ) : TransitionInteractor( fromState = KeyguardState.AOD, @@ -68,15 +71,16 @@ constructor( */ private fun listenForAodToOccluded() { scope.launch { - keyguardInteractor.isKeyguardOccluded.sample(startedKeyguardState, ::Pair).collect { - (isOccluded, startedKeyguardState) -> - if (isOccluded && startedKeyguardState == KeyguardState.AOD) { - startTransitionTo( - toState = KeyguardState.OCCLUDED, - modeOnCanceled = TransitionModeOnCanceled.RESET - ) + keyguardInteractor.isKeyguardOccluded + .sample(startedKeyguardTransitionStep, ::Pair) + .collect { (isOccluded, lastStartedStep) -> + if (isOccluded && lastStartedStep.to == KeyguardState.AOD) { + startTransitionTo( + toState = KeyguardState.OCCLUDED, + modeOnCanceled = TransitionModeOnCanceled.RESET + ) + } } - } } } @@ -85,15 +89,18 @@ constructor( keyguardInteractor .dozeTransitionTo(DozeStateModel.FINISH) .sample( + keyguardInteractor.isKeyguardShowing, startedKeyguardTransitionStep, keyguardInteractor.isKeyguardOccluded, keyguardInteractor.biometricUnlockState, ) - .collect { (_, lastStartedStep, occluded, biometricUnlockState) -> + .collect { (_, isKeyguardShowing, lastStartedStep, occluded, biometricUnlockState) + -> if ( lastStartedStep.to == KeyguardState.AOD && !occluded && - !isWakeAndUnlock(biometricUnlockState) + !isWakeAndUnlock(biometricUnlockState) && + isKeyguardShowing ) { val modeOnCanceled = if (lastStartedStep.from == KeyguardState.LOCKSCREEN) { @@ -134,13 +141,31 @@ constructor( } scope.launch { - keyguardInteractor.biometricUnlockState.sample(finishedKeyguardState, ::Pair).collect { - (biometricUnlockState, keyguardState) -> - KeyguardWmStateRefactor.assertInLegacyMode() - if (keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)) { - startTransitionTo(KeyguardState.GONE) + powerInteractor.isAwake + .debounce(50L) + .sample( + keyguardInteractor.biometricUnlockState, + startedKeyguardTransitionStep, + keyguardInteractor.isKeyguardShowing, + keyguardInteractor.isKeyguardDismissible, + ) + .collect { + ( + isAwake, + biometricUnlockState, + lastStartedTransitionStep, + isKeyguardShowing, + isKeyguardDismissible) -> + KeyguardWmStateRefactor.assertInLegacyMode() + if ( + isAwake && + lastStartedTransitionStep.to == KeyguardState.AOD && + (isWakeAndUnlock(biometricUnlockState) || + (!isKeyguardShowing && isKeyguardDismissible)) + ) { + startTransitionTo(KeyguardState.GONE) + } } - } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt index 54d5908e9fa4..baa865d22f70 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt @@ -26,13 +26,12 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor -import com.android.systemui.util.kotlin.Utils.Companion.toQuad -import com.android.systemui.util.kotlin.sample +import com.android.systemui.util.kotlin.Utils.Companion.sample import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.launch @SysUISingleton @@ -56,50 +55,47 @@ constructor( ) { override fun start() { - listenForDozingToLockscreenHubOrOccluded() - listenForDozingToGone() + listenForDozingToAny() listenForTransitionToCamera(scope, keyguardInteractor) } - private fun listenForDozingToLockscreenHubOrOccluded() { + private fun listenForDozingToAny() { scope.launch { powerInteractor.isAwake + .debounce(50L) .sample( - combine( - startedKeyguardTransitionStep, - keyguardInteractor.isKeyguardOccluded, - communalInteractor.isIdleOnCommunal, - ::Triple - ), - ::toQuad + keyguardInteractor.biometricUnlockState, + startedKeyguardTransitionStep, + keyguardInteractor.isKeyguardOccluded, + communalInteractor.isIdleOnCommunal, + keyguardInteractor.isKeyguardShowing, + keyguardInteractor.isKeyguardDismissible, ) - .collect { (isAwake, lastStartedTransition, occluded, isIdleOnCommunal) -> - if (isAwake && lastStartedTransition.to == KeyguardState.DOZING) { - startTransitionTo( - if (occluded) { - KeyguardState.OCCLUDED - } else if (isIdleOnCommunal) { - KeyguardState.GLANCEABLE_HUB - } else { - KeyguardState.LOCKSCREEN - } - ) - } - } - } - } - - private fun listenForDozingToGone() { - scope.launch { - keyguardInteractor.biometricUnlockState - .sample(startedKeyguardTransitionStep, ::Pair) - .collect { (biometricUnlockState, lastStartedTransition) -> - if ( - lastStartedTransition.to == KeyguardState.DOZING && - isWakeAndUnlock(biometricUnlockState) - ) { - startTransitionTo(KeyguardState.GONE) + .collect { + ( + isAwake, + biometricUnlockState, + lastStartedTransition, + occluded, + isIdleOnCommunal, + isKeyguardShowing, + isKeyguardDismissible) -> + if (!(isAwake && lastStartedTransition.to == KeyguardState.DOZING)) { + return@collect } + startTransitionTo( + if (isWakeAndUnlock(biometricUnlockState)) { + KeyguardState.GONE + } else if (isKeyguardDismissible && !isKeyguardShowing) { + KeyguardState.GONE + } else if (occluded) { + KeyguardState.OCCLUDED + } else if (isIdleOnCommunal) { + KeyguardState.GLANCEABLE_HUB + } else { + KeyguardState.LOCKSCREEN + } + ) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt index a03fa38ec850..d81f1f14158c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt @@ -21,6 +21,8 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.core.LogLevel.VERBOSE import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -37,6 +39,8 @@ constructor( private val keyguardInteractor: KeyguardInteractor, private val logger: KeyguardLogger, private val powerInteractor: PowerInteractor, + private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel, + private val shadeInteractor: ShadeInteractor, ) { fun start() { @@ -47,6 +51,30 @@ constructor( } scope.launch { + sharedNotificationContainerViewModel + .getMaxNotifications { height, useExtraShelfSpace -> height.toInt() } + .collect { logger.log(TAG, VERBOSE, "Notif: max height in px", it) } + } + + scope.launch { + sharedNotificationContainerViewModel.isOnLockscreen.collect { + logger.log(TAG, VERBOSE, "Notif: isOnLockscreen", it) + } + } + + scope.launch { + shadeInteractor.isUserInteracting.collect { + logger.log(TAG, VERBOSE, "Shade: isUserInteracting", it) + } + } + + scope.launch { + sharedNotificationContainerViewModel.isOnLockscreenWithoutShade.collect { + logger.log(TAG, VERBOSE, "Notif: isOnLockscreenWithoutShade", it) + } + } + + scope.launch { keyguardInteractor.primaryBouncerShowing.collect { logger.log(TAG, VERBOSE, "Primary bouncer showing", it) } @@ -75,6 +103,18 @@ constructor( } scope.launch { + keyguardInteractor.isKeyguardDismissible.collect { + logger.log(TAG, VERBOSE, "isDismissible", it) + } + } + + scope.launch { + keyguardInteractor.isKeyguardShowing.collect { + logger.log(TAG, VERBOSE, "isShowing", it) + } + } + + scope.launch { keyguardInteractor.dozeTransitionModel.collect { logger.log(TAG, VERBOSE, "Doze transition", it) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt index 7f0b483919b3..601fbfaf1b64 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt @@ -17,7 +17,6 @@ package com.android.systemui.keyguard.shared import com.android.systemui.Flags -import com.android.systemui.compose.ComposeFacade import com.android.systemui.flags.FlagToken import com.android.systemui.flags.RefactorFlagUtils @@ -34,7 +33,7 @@ object ComposeLockscreen { /** Is the refactor enabled */ @JvmStatic inline val isEnabled - get() = Flags.composeLockscreen() && ComposeFacade.isComposeAvailable() + get() = Flags.composeLockscreen() /** * Called to ensure code is only run when the flag is enabled. This protects users from the diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt index b92a9a08987a..a9eec18319c3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt @@ -16,13 +16,16 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.util.MathUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow /** Breaks down AOD->GONE transition into discrete steps for corresponding views to consume. */ @ExperimentalCoroutinesApi @@ -40,5 +43,15 @@ constructor( to = KeyguardState.GONE, ) + fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> { + var startAlpha = 1f + return transitionAnimation.sharedFlow( + duration = 200.milliseconds, + onStart = { startAlpha = viewState.alpha() }, + onStep = { MathUtils.lerp(startAlpha, 0f, it) }, + onFinish = { 0f }, + ) + } + override val deviceEntryParentViewAlpha = transitionAnimation.immediatelyTransitionTo(0f) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt index fca1604946e1..8851a51f15b0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt @@ -16,12 +16,14 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.util.MathUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor.Companion.TO_GONE_DURATION import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -41,6 +43,16 @@ constructor( to = KeyguardState.GONE, ) + fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> { + var startAlpha = 1f + return transitionAnimation.sharedFlow( + duration = 200.milliseconds, + onStart = { startAlpha = viewState.alpha() }, + onStep = { MathUtils.lerp(startAlpha, 0f, it) }, + onFinish = { 0f }, + ) + } + override val deviceEntryParentViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index bdcaf0951c5b..38d5e0f74b28 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -31,6 +31,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING import com.android.systemui.keyguard.shared.model.TransitionState.STARTED import com.android.systemui.keyguard.ui.StateToValue @@ -70,7 +71,9 @@ constructor( private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor, private val alternateBouncerToGoneTransitionViewModel: AlternateBouncerToGoneTransitionViewModel, + private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel, private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, + private val dozingToGoneTransitionViewModel: DozingToGoneTransitionViewModel, private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel, private val glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel, @@ -120,6 +123,27 @@ constructor( } .distinctUntilChanged() + /** + * Keyguard should not show while the communal hub is fully visible. This check is added since + * at the moment, closing the notification shade will cause the keyguard alpha to be set back to + * 1. Also ensure keyguard is never visible when GONE. + */ + private val hideKeyguard: Flow<Boolean> = + combine( + communalInteractor.isIdleOnCommunal, + keyguardTransitionInteractor + .transitionValue(GONE) + .map { it == 1f } + .onStart { emit(false) }, + keyguardTransitionInteractor + .transitionValue(OCCLUDED) + .map { it == 1f } + .onStart { emit(false) }, + ) { isIdleOnCommunal, isGone, isOccluded -> + isIdleOnCommunal || isGone || isOccluded + } + .distinctUntilChanged() + /** Last point that the root view was tapped */ val lastRootViewTapPosition: Flow<Point?> = keyguardInteractor.lastRootViewTapPosition @@ -136,19 +160,16 @@ constructor( /** An observable for the alpha level for the entire keyguard root view. */ fun alpha(viewState: ViewStateAccessor): Flow<Float> { return combine( - communalInteractor.isIdleOnCommunal, - keyguardTransitionInteractor - .transitionValue(GONE) - .map { it == 1f } - .onStart { emit(false) } - .distinctUntilChanged(), + hideKeyguard, // The transitions are mutually exclusive, so they are safe to merge to get the last // value emitted by any of them. Do not add flows that cannot make this guarantee. merge( alphaOnShadeExpansion, keyguardInteractor.dismissAlpha.filterNotNull(), alternateBouncerToGoneTransitionViewModel.lockscreenAlpha, + aodToGoneTransitionViewModel.lockscreenAlpha(viewState), aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState), + dozingToGoneTransitionViewModel.lockscreenAlpha(viewState), dozingToLockscreenTransitionViewModel.lockscreenAlpha, glanceableHubToLockscreenTransitionViewModel.keyguardAlpha, goneToAodTransitionViewModel.enterFromTopAnimationAlpha, @@ -167,12 +188,8 @@ constructor( primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha, ) .onStart { emit(1f) } - ) { isIdleOnCommunal, gone, alpha -> - if (isIdleOnCommunal || gone) { - // Keyguard should not show while the communal hub is fully visible. This check - // is added since at the moment, closing the notification shade will cause the - // keyguard alpha to be set back to 1. Also ensure keyguard is never visible - // when GONE. + ) { hideKeyguard, alpha -> + if (hideKeyguard) { 0f } else { alpha diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 8b7c85b65824..f2013bec19c7 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -546,7 +546,7 @@ public class LogModule { @SysUISingleton @KeyguardLog public static LogBuffer provideKeyguardLogBuffer(LogBufferFactory factory) { - return factory.create("KeyguardLog", 250); + return factory.create("KeyguardLog", 500); } /** diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt index f1cade7512e2..0b19bab5c7c5 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt @@ -24,12 +24,14 @@ import android.media.projection.ReviewGrantedConsentResult import android.os.RemoteException import android.os.ServiceManager import android.util.Log +import android.window.WindowContainerToken +import javax.inject.Inject /** * Helper class that handles the media projection service related actions. It simplifies invoking * the MediaProjectionManagerService and updating the permission consent. */ -class MediaProjectionServiceHelper { +class MediaProjectionServiceHelper @Inject constructor() { companion object { private const val TAG = "MediaProjectionServiceHelper" private val service = @@ -90,4 +92,16 @@ class MediaProjectionServiceHelper { } } } + + /** Updates the projected task to the task that has a matching [WindowContainerToken]. */ + fun updateTaskRecordingSession(token: WindowContainerToken): Boolean { + return try { + true + // TODO: actually call the service once it is implemented + // service.updateTaskRecordingSession(token) + } catch (e: RemoteException) { + Log.e(TAG, "Unable to updateTaskRecordingSession", e) + false + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt index 9938f11e5d4c..cfbcaf91b791 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt @@ -16,11 +16,11 @@ package com.android.systemui.mediaprojection.taskswitcher.data.model -import android.app.TaskInfo +import android.app.ActivityManager.RunningTaskInfo /** Represents the state of media projection. */ sealed interface MediaProjectionState { object NotProjecting : MediaProjectionState object EntireScreen : MediaProjectionState - data class SingleTask(val task: TaskInfo) : MediaProjectionState + data class SingleTask(val task: RunningTaskInfo) : MediaProjectionState } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt index 492d482459d6..4ff54d4eae65 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt @@ -17,10 +17,14 @@ package com.android.systemui.mediaprojection.taskswitcher.data.repository import android.app.ActivityManager.RunningTaskInfo +import android.app.ActivityOptions +import android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED import android.app.ActivityTaskManager +import android.app.IActivityTaskManager import android.app.TaskStackListener import android.os.IBinder import android.util.Log +import android.view.Display import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton @@ -40,11 +44,24 @@ import kotlinx.coroutines.withContext class ActivityTaskManagerTasksRepository @Inject constructor( - private val activityTaskManager: ActivityTaskManager, + private val activityTaskManager: IActivityTaskManager, @Application private val applicationScope: CoroutineScope, @Background private val backgroundDispatcher: CoroutineDispatcher, ) : TasksRepository { + override suspend fun launchRecentTask(taskInfo: RunningTaskInfo) { + withContext(backgroundDispatcher) { + val activityOptions = ActivityOptions.makeBasic() + activityOptions.pendingIntentBackgroundActivityStartMode = + MODE_BACKGROUND_ACTIVITY_START_ALLOWED + activityOptions.launchDisplayId = taskInfo.displayId + activityTaskManager.startActivityFromRecents( + taskInfo.taskId, + activityOptions.toBundle() + ) + } + } + override suspend fun findRunningTaskFromWindowContainerToken( windowContainerToken: IBinder ): RunningTaskInfo? = @@ -53,7 +70,14 @@ constructor( } private suspend fun getRunningTasks(): List<RunningTaskInfo> = - withContext(backgroundDispatcher) { activityTaskManager.getTasks(Integer.MAX_VALUE) } + withContext(backgroundDispatcher) { + activityTaskManager.getTasks( + /* maxNum = */ Integer.MAX_VALUE, + /* filterForVisibleRecents = */ false, + /* keepIntentExtra = */ false, + /* displayId = */ Display.INVALID_DISPLAY + ) + } override val foregroundTask: Flow<RunningTaskInfo> = conflatedCallbackFlow { diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt index 6480a47e8ea2..74d19921c706 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt @@ -16,6 +16,7 @@ package com.android.systemui.mediaprojection.taskswitcher.data.repository +import android.app.ActivityManager.RunningTaskInfo import android.media.projection.MediaProjectionInfo import android.media.projection.MediaProjectionManager import android.os.Handler @@ -26,15 +27,19 @@ import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLoggin import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.mediaprojection.MediaProjectionServiceHelper import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext @SysUISingleton class MediaProjectionManagerRepository @@ -43,9 +48,21 @@ constructor( private val mediaProjectionManager: MediaProjectionManager, @Main private val handler: Handler, @Application private val applicationScope: CoroutineScope, + @Background private val backgroundDispatcher: CoroutineDispatcher, private val tasksRepository: TasksRepository, + private val mediaProjectionServiceHelper: MediaProjectionServiceHelper, ) : MediaProjectionRepository { + override suspend fun switchProjectedTask(task: RunningTaskInfo) { + withContext(backgroundDispatcher) { + if (mediaProjectionServiceHelper.updateTaskRecordingSession(task.token)) { + Log.d(TAG, "Successfully switched projected task") + } else { + Log.d(TAG, "Failed to switch projected task") + } + } + } + override val mediaProjectionState: Flow<MediaProjectionState> = conflatedCallbackFlow { val callback = @@ -82,7 +99,9 @@ constructor( } val matchingTask = tasksRepository.findRunningTaskFromWindowContainerToken( - checkNotNull(session.tokenToRecord)) ?: return MediaProjectionState.EntireScreen + checkNotNull(session.tokenToRecord) + ) + ?: return MediaProjectionState.EntireScreen return MediaProjectionState.SingleTask(matchingTask) } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt index 5bec6925babe..e495466008ce 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt @@ -16,12 +16,16 @@ package com.android.systemui.mediaprojection.taskswitcher.data.repository +import android.app.ActivityManager.RunningTaskInfo import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState import kotlinx.coroutines.flow.Flow /** Represents a repository to retrieve and change data related to media projection. */ interface MediaProjectionRepository { + /** Switches the task that should be projected. */ + suspend fun switchProjectedTask(task: RunningTaskInfo) + /** Represents the current [MediaProjectionState]. */ val mediaProjectionState: Flow<MediaProjectionState> } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt deleted file mode 100644 index 544eb6b99d4f..000000000000 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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.mediaprojection.taskswitcher.data.repository - -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState -import javax.inject.Inject -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.emptyFlow - -/** - * No-op implementation of [MediaProjectionRepository] that does nothing. Currently used as a - * placeholder, while the real implementation is not completed. - */ -@SysUISingleton -class NoOpMediaProjectionRepository @Inject constructor() : MediaProjectionRepository { - - override val mediaProjectionState: Flow<MediaProjectionState> = emptyFlow() -} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt index 6a535e4ecc50..9ef42b4de45c 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt @@ -23,6 +23,8 @@ import kotlinx.coroutines.flow.Flow /** Repository responsible for retrieving data related to running tasks. */ interface TasksRepository { + suspend fun launchRecentTask(taskInfo: RunningTaskInfo) + /** * Tries to find a [RunningTaskInfo] with a matching window container token. Returns `null` when * no matching task was found. diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt index fc5cf7d75bdf..eb9e6a5de057 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt @@ -16,6 +16,7 @@ package com.android.systemui.mediaprojection.taskswitcher.domain.interactor +import android.app.ActivityManager.RunningTaskInfo import android.app.TaskInfo import android.content.Intent import android.util.Log @@ -37,10 +38,18 @@ import kotlinx.coroutines.flow.map class TaskSwitchInteractor @Inject constructor( - mediaProjectionRepository: MediaProjectionRepository, + private val mediaProjectionRepository: MediaProjectionRepository, private val tasksRepository: TasksRepository, ) { + suspend fun switchProjectedTask(task: RunningTaskInfo) { + mediaProjectionRepository.switchProjectedTask(task) + } + + suspend fun goBackToTask(task: RunningTaskInfo) { + tasksRepository.launchRecentTask(task) + } + /** * Emits a stream of changes to the state of task switching, in the context of media projection. */ diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt index cd1258ed6aa8..caabc64efae1 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt @@ -16,7 +16,7 @@ package com.android.systemui.mediaprojection.taskswitcher.domain.model -import android.app.TaskInfo +import android.app.ActivityManager.RunningTaskInfo /** Represents tha state of task switching in the context of single task media projection. */ sealed interface TaskSwitchState { @@ -25,6 +25,8 @@ sealed interface TaskSwitchState { /** The foreground task is the same as the task that is currently being projected. */ object TaskUnchanged : TaskSwitchState /** The foreground task is a different one to the task it currently being projected. */ - data class TaskSwitched(val projectedTask: TaskInfo, val foregroundTask: TaskInfo) : - TaskSwitchState + data class TaskSwitched( + val projectedTask: RunningTaskInfo, + val foregroundTask: RunningTaskInfo + ) : TaskSwitchState } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt index 7840da960a83..dab7439f0f0c 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt @@ -16,23 +16,25 @@ package com.android.systemui.mediaprojection.taskswitcher.ui +import android.app.ActivityManager.RunningTaskInfo import android.app.Notification -import android.app.NotificationChannel import android.app.NotificationManager +import android.app.PendingIntent import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.Parcelable import android.util.Log -import com.android.systemui.res.R +import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState.NotShowing import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState.Showing import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel +import com.android.systemui.res.R import com.android.systemui.util.NotificationChannels import javax.inject.Inject -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.launch /** Coordinator responsible for showing/hiding the task switcher notification. */ @@ -43,32 +45,54 @@ constructor( private val context: Context, private val notificationManager: NotificationManager, @Application private val applicationScope: CoroutineScope, - @Main private val mainDispatcher: CoroutineDispatcher, private val viewModel: TaskSwitcherNotificationViewModel, + private val broadcastDispatcher: BroadcastDispatcher, ) { + fun start() { applicationScope.launch { - viewModel.uiState.flowOn(mainDispatcher).collect { uiState -> - Log.d(TAG, "uiState -> $uiState") - when (uiState) { - is Showing -> showNotification() - is NotShowing -> hideNotification() + launch { + viewModel.uiState.collect { uiState -> + Log.d(TAG, "uiState -> $uiState") + when (uiState) { + is Showing -> showNotification(uiState) + is NotShowing -> hideNotification() + } } } + launch { + broadcastDispatcher + .broadcastFlow(IntentFilter(SWITCH_ACTION)) { intent, _ -> + intent.requireParcelableExtra<RunningTaskInfo>(EXTRA_ACTION_TASK) + } + .collect { task: RunningTaskInfo -> + Log.d(TAG, "Switch action triggered: $task") + viewModel.onSwitchTaskClicked(task) + } + } + launch { + broadcastDispatcher + .broadcastFlow(IntentFilter(GO_BACK_ACTION)) { intent, _ -> + intent.requireParcelableExtra<RunningTaskInfo>(EXTRA_ACTION_TASK) + } + .collect { task -> + Log.d(TAG, "Go back action triggered: $task") + viewModel.onGoBackToTaskClicked(task) + } + } } } - private fun showNotification() { - notificationManager.notify(TAG, NOTIFICATION_ID, createNotification()) + private fun showNotification(uiState: Showing) { + notificationManager.notify(TAG, NOTIFICATION_ID, createNotification(uiState)) } - private fun createNotification(): Notification { - // TODO(b/286201261): implement actions + private fun createNotification(uiState: Showing): Notification { val actionSwitch = Notification.Action.Builder( /* icon = */ null, context.getString(R.string.media_projection_task_switcher_action_switch), - /* intent = */ null + createActionPendingIntent(action = SWITCH_ACTION, task = uiState.foregroundTask) ) .build() @@ -76,34 +100,40 @@ constructor( Notification.Action.Builder( /* icon = */ null, context.getString(R.string.media_projection_task_switcher_action_back), - /* intent = */ null + createActionPendingIntent(action = GO_BACK_ACTION, task = uiState.projectedTask) ) .build() - - val channel = - NotificationChannel( - NotificationChannels.HINTS, - context.getString(R.string.media_projection_task_switcher_notification_channel), - NotificationManager.IMPORTANCE_HIGH - ) - notificationManager.createNotificationChannel(channel) - return Notification.Builder(context, channel.id) + return Notification.Builder(context, NotificationChannels.ALERTS) .setSmallIcon(R.drawable.qs_screen_record_icon_on) .setAutoCancel(true) .setContentText(context.getString(R.string.media_projection_task_switcher_text)) .addAction(actionSwitch) .addAction(actionBack) - .setPriority(Notification.PRIORITY_HIGH) - .setDefaults(Notification.DEFAULT_VIBRATE) .build() } private fun hideNotification() { - notificationManager.cancel(NOTIFICATION_ID) + notificationManager.cancel(TAG, NOTIFICATION_ID) } + private fun createActionPendingIntent(action: String, task: RunningTaskInfo) = + PendingIntent.getBroadcast( + context, + /* requestCode= */ 0, + Intent(action).apply { putExtra(EXTRA_ACTION_TASK, task) }, + /* flags= */ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + ) + companion object { private const val TAG = "TaskSwitchNotifCoord" private const val NOTIFICATION_ID = 5566 + + private const val EXTRA_ACTION_TASK = "extra_task" + + private const val SWITCH_ACTION = "com.android.systemui.mediaprojection.SWITCH_TASK" + private const val GO_BACK_ACTION = "com.android.systemui.mediaprojection.GO_BACK" } } + +private fun <T : Parcelable> Intent.requireParcelableExtra(key: String) = + getParcelableExtra<T>(key)!! diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt index 21aee72d17ae..f307761a1875 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt @@ -16,7 +16,7 @@ package com.android.systemui.mediaprojection.taskswitcher.ui.model -import android.app.TaskInfo +import android.app.ActivityManager.RunningTaskInfo /** Represents the UI state for the task switcher notification. */ sealed interface TaskSwitcherNotificationUiState { @@ -24,7 +24,7 @@ sealed interface TaskSwitcherNotificationUiState { object NotShowing : TaskSwitcherNotificationUiState /** The notification should be shown. */ data class Showing( - val projectedTask: TaskInfo, - val foregroundTask: TaskInfo, + val projectedTask: RunningTaskInfo, + val foregroundTask: RunningTaskInfo, ) : TaskSwitcherNotificationUiState } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt index d9754d4429d4..cc8cc5165e4f 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt @@ -16,15 +16,24 @@ package com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel +import android.app.ActivityManager.RunningTaskInfo import android.util.Log +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext -class TaskSwitcherNotificationViewModel @Inject constructor(interactor: TaskSwitchInteractor) { +class TaskSwitcherNotificationViewModel +@Inject +constructor( + private val interactor: TaskSwitchInteractor, + @Background private val backgroundDispatcher: CoroutineDispatcher, +) { val uiState: Flow<TaskSwitcherNotificationUiState> = interactor.taskSwitchChanges.map { taskSwitchChange -> @@ -43,6 +52,13 @@ class TaskSwitcherNotificationViewModel @Inject constructor(interactor: TaskSwit } } + suspend fun onSwitchTaskClicked(task: RunningTaskInfo) { + interactor.switchProjectedTask(task) + } + + suspend fun onGoBackToTaskClicked(task: RunningTaskInfo) = + withContext(backgroundDispatcher) { interactor.goBackToTask(task) } + companion object { private const val TAG = "TaskSwitchNotifVM" } diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt index 5b7eb454597c..deb0fed0ffc8 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt @@ -20,14 +20,15 @@ import android.appwidget.AppWidgetManager import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle -import com.android.systemui.compose.ComposeFacade.isComposeAvailable -import com.android.systemui.compose.ComposeFacade.setPeopleSpaceActivityContent +import com.android.compose.theme.PlatformTheme import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.people.ui.compose.PeopleScreen import com.android.systemui.people.ui.view.PeopleViewBinder import com.android.systemui.people.ui.view.PeopleViewBinder.bind import com.android.systemui.people.ui.viewmodel.PeopleViewModel @@ -65,13 +66,11 @@ constructor( } // Set the content of the activity, using either the View or Compose implementation. - if (featureFlags.isEnabled(Flags.COMPOSE_PEOPLE_SPACE) && isComposeAvailable()) { + if (featureFlags.isEnabled(Flags.COMPOSE_PEOPLE_SPACE)) { Log.d(TAG, "Using the Compose implementation of the PeopleSpaceActivity") - setPeopleSpaceActivityContent( - activity = this, - viewModel, - onResult = { finishActivity(it) }, - ) + setContent { + PlatformTheme { PeopleScreen(viewModel, onResult = { finishActivity(it) }) } + } } else { Log.d(TAG, "Using the View implementation of the PeopleSpaceActivity") val view = PeopleViewBinder.create(this) diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java index 741336277119..a000d63a2ee3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java @@ -47,7 +47,6 @@ import com.android.app.animation.Interpolators; import com.android.keyguard.BouncerPanelExpansionCalculator; import com.android.systemui.Dumpable; import com.android.systemui.animation.ShadeInterpolation; -import com.android.systemui.compose.ComposeFacade; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; @@ -301,8 +300,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl private void bindFooterActionsView(View root) { LinearLayout footerActionsView = root.findViewById(R.id.qs_footer_actions); - if (!mFeatureFlags.isEnabled(Flags.COMPOSE_QS_FOOTER_ACTIONS) - || !ComposeFacade.INSTANCE.isComposeAvailable()) { + if (!mFeatureFlags.isEnabled(Flags.COMPOSE_QS_FOOTER_ACTIONS)) { Log.d(TAG, "Binding the View implementation of the QS footer actions"); mFooterActionsView = footerActionsView; mFooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel, @@ -312,7 +310,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl // Compose is available, so let's use the Compose implementation of the footer actions. Log.d(TAG, "Binding the Compose implementation of the QS footer actions"); - View composeView = ComposeFacade.INSTANCE.createFooterActionsView(root.getContext(), + View composeView = QSUtils.createFooterActionsView(root.getContext(), mQSFooterActionsViewModel, mListeningAndVisibilityLifecycleOwner); mFooterActionsView = composeView; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt b/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt index e42264f24e92..15c3f271469d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt @@ -1,7 +1,13 @@ package com.android.systemui.qs import android.content.Context +import android.view.View +import androidx.lifecycle.LifecycleOwner +import com.android.compose.theme.PlatformTheme +import com.android.compose.ui.platform.DensityAwareComposeView import com.android.internal.policy.SystemBarUtils +import com.android.systemui.qs.footer.ui.compose.FooterActions +import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.util.LargeScreenUtils.shouldUseLargeScreenShadeHeader object QSUtils { @@ -21,4 +27,15 @@ object QSUtils { SystemBarUtils.getQuickQsOffsetHeight(context) } } -}
\ No newline at end of file + + @JvmStatic + fun createFooterActionsView( + context: Context, + viewModel: FooterActionsViewModel, + qsVisibilityLifecycleOwner: LifecycleOwner, + ): View { + return DensityAwareComposeView(context).apply { + setContent { PlatformTheme { FooterActions(viewModel, qsVisibilityLifecycleOwner) } } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt index 8408c51c86dc..1808d98cd692 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt @@ -24,7 +24,6 @@ import com.android.systemui.Flags.FLAG_SCENE_CONTAINER import com.android.systemui.Flags.keyguardBottomAreaRefactor import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.Flags.sceneContainer -import com.android.systemui.compose.ComposeFacade import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FlagToken import com.android.systemui.flags.Flags.SCENE_CONTAINER_ENABLED @@ -47,9 +46,8 @@ object SceneContainerFlag { keyguardBottomAreaRefactor() && migrateClocksToBlueprint() && ComposeLockscreen.isEnabled && - MediaInSceneContainerFlag.isEnabled && - // NOTE: Changes should also be made in getSecondaryFlags and @EnableSceneContainer - ComposeFacade.isComposeAvailable() + MediaInSceneContainerFlag.isEnabled + // NOTE: Changes should also be made in getSecondaryFlags and @EnableSceneContainer /** * The main static flag, SCENE_CONTAINER_ENABLED. This is an explicit static flag check that @@ -74,11 +72,7 @@ object SceneContainerFlag { /** The full set of requirements for SceneContainer */ inline fun getAllRequirements(): Sequence<FlagToken> { - val composeRequirement = - FlagToken("ComposeFacade.isComposeAvailable()", ComposeFacade.isComposeAvailable()) - return sequenceOf(getMainStaticFlag(), getMainAconfigFlag()) + - getSecondaryFlags() + - composeRequirement + return sequenceOf(getMainStaticFlag(), getMainAconfigFlag()) + getSecondaryFlags() } /** Return all dependencies of this flag in pairs where [Pair.first] depends on [Pair.second] */ diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt index ee76c0582b9d..f2697b4e1c1e 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt @@ -16,17 +16,26 @@ package com.android.systemui.scene.ui.view +import android.content.Context +import android.graphics.Point import android.view.View import android.view.ViewGroup import android.view.WindowInsets import androidx.activity.OnBackPressedDispatcher import androidx.activity.OnBackPressedDispatcherOwner import androidx.activity.setViewTreeOnBackPressedDispatcherOwner +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle -import com.android.systemui.compose.ComposeFacade +import com.android.compose.theme.PlatformTheme +import com.android.internal.policy.ScreenDecorationsUtils +import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation +import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout +import com.android.systemui.common.ui.compose.windowinsets.ScreenDecorProvider import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlags @@ -34,10 +43,16 @@ import com.android.systemui.scene.shared.model.Scene import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.SceneDataSourceDelegator import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.ui.composable.ComposableScene +import com.android.systemui.scene.ui.composable.SceneContainer import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch object SceneWindowRootViewBinder { @@ -83,7 +98,7 @@ object SceneWindowRootViewBinder { ) view.addView( - ComposeFacade.createSceneContainerView( + createSceneContainerView( scope = this, context = view.context, viewModel = viewModel, @@ -120,4 +135,74 @@ object SceneWindowRootViewBinder { } } } + + private fun createSceneContainerView( + scope: CoroutineScope, + context: Context, + viewModel: SceneContainerViewModel, + windowInsets: StateFlow<WindowInsets?>, + sceneByKey: Map<SceneKey, Scene>, + dataSourceDelegator: SceneDataSourceDelegator, + ): View { + return ComposeView(context).apply { + setContent { + PlatformTheme { + ScreenDecorProvider( + displayCutout = displayCutoutFromWindowInsets(scope, context, windowInsets), + screenCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context) + ) { + SceneContainer( + viewModel = viewModel, + sceneByKey = + sceneByKey.mapValues { (_, scene) -> scene as ComposableScene }, + dataSourceDelegator = dataSourceDelegator, + ) + } + } + } + } + } + + // TODO(b/298525212): remove once Compose exposes window inset bounds. + private fun displayCutoutFromWindowInsets( + scope: CoroutineScope, + context: Context, + windowInsets: StateFlow<WindowInsets?>, + ): StateFlow<DisplayCutout> = + windowInsets + .map { + val boundingRect = it?.displayCutout?.boundingRectTop + val width = boundingRect?.let { boundingRect.right - boundingRect.left } ?: 0 + val left = boundingRect?.left?.toDp(context) ?: 0.dp + val top = boundingRect?.top?.toDp(context) ?: 0.dp + val right = boundingRect?.right?.toDp(context) ?: 0.dp + val bottom = boundingRect?.bottom?.toDp(context) ?: 0.dp + val location = + when { + width <= 0f -> CutoutLocation.NONE + left <= 0.dp -> CutoutLocation.LEFT + right >= getDisplayWidth(context) -> CutoutLocation.RIGHT + else -> CutoutLocation.CENTER + } + DisplayCutout( + left, + top, + right, + bottom, + location, + ) + } + .stateIn(scope, SharingStarted.WhileSubscribed(), DisplayCutout()) + + // TODO(b/298525212): remove once Compose exposes window inset bounds. + private fun getDisplayWidth(context: Context): Dp { + val point = Point() + checkNotNull(context.display).getRealSize(point) + return point.x.dp + } + + // TODO(b/298525212): remove once Compose exposes window inset bounds. + private fun Int.toDp(context: Context): Dp { + return (this.toFloat() / context.resources.displayMetrics.density).dp + } } 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 4c2c97981702..22645c4532f6 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 @@ -25,7 +25,7 @@ import android.view.View import android.view.WindowInsets import android.widget.FrameLayout import androidx.core.view.updateMargins -import com.android.systemui.compose.ComposeFacade +import com.android.systemui.compose.ComposeInitializer import com.android.systemui.res.R /** A view that can serve as the root of the main SysUI window. */ @@ -45,16 +45,16 @@ open class WindowRootView( override fun onAttachedToWindow() { super.onAttachedToWindow() - if (ComposeFacade.isComposeAvailable() && isRoot()) { - ComposeFacade.composeInitializer().onAttachedToWindow(this) + if (isRoot()) { + ComposeInitializer.onAttachedToWindow(this) } } override fun onDetachedFromWindow() { super.onDetachedFromWindow() - if (ComposeFacade.isComposeAvailable() && isRoot()) { - ComposeFacade.composeInitializer().onDetachedFromWindow(this) + if (isRoot()) { + ComposeInitializer.onDetachedFromWindow(this) } } diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java index 6af9b739da52..e92630fc67a2 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java @@ -55,15 +55,15 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.util.settings.SecureSettings; -import java.util.concurrent.Executor; - import dagger.assisted.Assisted; import dagger.assisted.AssistedFactory; import dagger.assisted.AssistedInject; +import java.util.concurrent.Executor; + public class BrightnessController implements ToggleSlider.Listener, MirroredBrightnessController { private static final String TAG = "CentralSurfaces.BrightnessController"; - private static final int SLIDER_ANIMATION_DURATION = 3000; + private static final int SLIDER_ANIMATION_DURATION = 1000; private static final int MSG_UPDATE_SLIDER = 1; private static final int MSG_ATTACH_LISTENER = 2; diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt index df845f559f2e..d3869baf16a2 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt @@ -23,11 +23,12 @@ import android.view.GestureDetector import android.view.MotionEvent import android.view.View import android.view.ViewGroup +import androidx.compose.ui.platform.ComposeView +import com.android.compose.theme.PlatformTheme import com.android.internal.annotations.VisibleForTesting import com.android.systemui.communal.domain.interactor.CommunalInteractor +import com.android.systemui.communal.ui.compose.CommunalContainer import com.android.systemui.communal.ui.viewmodel.CommunalViewModel -import com.android.systemui.compose.ComposeFacade.createCommunalContainer -import com.android.systemui.compose.ComposeFacade.isComposeAvailable import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.res.R @@ -35,7 +36,6 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.util.kotlin.collectFlow import javax.inject.Inject import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf /** * Controller that's responsible for the glanceable hub container view and its touch handling. @@ -107,8 +107,7 @@ constructor( private var shadeShowing = false /** Returns a flow that tracks whether communal hub is available. */ - fun communalAvailable(): Flow<Boolean> = - if (isComposeAvailable()) communalInteractor.isCommunalAvailable else flowOf(false) + fun communalAvailable(): Flow<Boolean> = communalInteractor.isCommunalAvailable /** * Creates the container view containing the glanceable hub UI. @@ -118,7 +117,11 @@ constructor( fun initView( context: Context, ): View { - return initView(createCommunalContainer(context, communalViewModel)) + return initView( + ComposeView(context).apply { + setContent { PlatformTheme { CommunalContainer(viewModel = communalViewModel) } } + } + ) } /** Override for testing. */ diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 7bcb1da5c2bf..1876f4739c87 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -111,8 +111,6 @@ import com.android.keyguard.dagger.KeyguardUserSwitcherComponent; import com.android.systemui.DejankUtils; import com.android.systemui.Dumpable; import com.android.systemui.Gefingerpoken; -import com.android.systemui.animation.ActivityTransitionAnimator; -import com.android.systemui.animation.TransitionAnimator; import com.android.systemui.biometrics.AuthController; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants; @@ -219,7 +217,6 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; import com.android.systemui.statusbar.phone.TapAgainViewController; import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; -import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController; @@ -259,10 +256,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private static final boolean DEBUG_DRAWABLE = false; /** The parallax amount of the quick settings translation when dragging down the panel. */ public static final float QS_PARALLAX_AMOUNT = 0.175f; - private static final long ANIMATION_DELAY_ICON_FADE_IN = - ActivityTransitionAnimator.TIMINGS.getTotalDuration() - - CollapsedStatusBarFragment.FADE_IN_DURATION - - CollapsedStatusBarFragment.FADE_IN_DELAY - 48; private static final int NO_FIXED_DURATION = -1; private static final long SHADE_OPEN_SPRING_OUT_DURATION = 350L; private static final long SHADE_OPEN_SPRING_BACK_DURATION = 400L; @@ -463,7 +456,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump */ private float mLinearDarkAmount; private boolean mPulsing; - private boolean mHideIconsDuringLaunchAnimation = true; private int mStackScrollerMeasuringPass; /** Non-null if a heads-up notification's position is being tracked. */ @Nullable @@ -3159,10 +3151,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return mUnlockedScreenOffAnimationController.isAnimationPlaying(); } - @Override public boolean shouldHideStatusBarIconsWhenExpanded() { if (isLaunchingActivity()) { - return mHideIconsDuringLaunchAnimation; + return false; } if (mHeadsUpAppearanceController != null && mHeadsUpAppearanceController.shouldBeVisible()) { @@ -3260,18 +3251,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } @Override - public void applyLaunchAnimationProgress(float linearProgress) { - boolean hideIcons = TransitionAnimator.getProgress(ActivityTransitionAnimator.TIMINGS, - linearProgress, ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f; - if (hideIcons != mHideIconsDuringLaunchAnimation) { - mHideIconsDuringLaunchAnimation = hideIcons; - if (!hideIcons) { - mCommandQueue.recomputeDisableFlags(mDisplayId, true /* animate */); - } - } - } - - @Override public void performHapticFeedback(int constant) { mVibratorHelper.performHapticFeedback(mView, constant); } @@ -3482,7 +3461,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump ipw.print("mInterpolatedDarkAmount="); ipw.println(mInterpolatedDarkAmount); ipw.print("mLinearDarkAmount="); ipw.println(mLinearDarkAmount); ipw.print("mPulsing="); ipw.println(mPulsing); - ipw.print("mHideIconsDuringLaunchAnimation="); ipw.println(mHideIconsDuringLaunchAnimation); ipw.print("mStackScrollerMeasuringPass="); ipw.println(mStackScrollerMeasuringPass); ipw.print("mPanelAlpha="); ipw.println(mPanelAlpha); ipw.print("mBottomAreaShadeAlpha="); ipw.println(mBottomAreaShadeAlpha); @@ -4174,7 +4152,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return mShadeRepository.getLegacyIsClosing().getValue(); } - @Override public void collapseWithDuration(int animationDuration) { mFixedDuration = animationDuration; collapse(false /* delayed */, 1.0f /* speedUpFactor */); diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java index ec4b23a56483..0a57b64b1ecf 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java @@ -78,6 +78,14 @@ public interface ShadeController extends CoreStartable { */ void animateCollapseShade(int flags, boolean force, boolean delayed, float speedUpFactor); + /** + * Collapses the shade with an animation duration in milliseconds. + * + * @deprecated use animateCollapseShade with a speed up factor instead + */ + @Deprecated + void collapseWithDuration(int animationDuration); + /** Expand the shade with an animation. */ void animateExpandShade(); diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt index 08a0c934702d..093690ffb881 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt @@ -33,6 +33,7 @@ open class ShadeControllerEmptyImpl @Inject constructor() : ShadeController { delayed: Boolean, speedUpFactor: Float ) {} + override fun collapseWithDuration(animationDuration: Int) {} override fun animateExpandShade() {} override fun animateExpandQs() {} override fun postAnimateCollapseShade() {} diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java index e6555f2e0993..d99d607879cc 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java @@ -147,6 +147,11 @@ public final class ShadeControllerImpl extends BaseShadeControllerImpl { } @Override + public void collapseWithDuration(int animationDuration) { + mNpvc.get().collapseWithDuration(animationDuration); + } + + @Override protected void expandToNotifications() { getNpvc().expandToNotifications(); } @@ -221,7 +226,6 @@ public final class ShadeControllerImpl extends BaseShadeControllerImpl { } } - @Override public void collapseShade(boolean animate) { if (animate) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt index 6a2a6a417f5a..efd9ce0d08b2 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt @@ -96,7 +96,7 @@ constructor( } override fun instantCollapseShade() { - // TODO(b/315921512) add support for instant transition + // TODO(b/325602936) add support for instant transition sceneInteractor.changeScene( getCollapseDestinationScene(), "hide shade", @@ -133,6 +133,11 @@ constructor( } } + override fun collapseWithDuration(animationDuration: Int) { + // TODO(b/300258424) inline this. The only caller uses the default duration. + animateCollapseShade() + } + private fun animateCollapseShadeInternal() { sceneInteractor.changeScene( getCollapseDestinationScene(), diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt index 44c6a82d93ca..1f4a0f1f1b91 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt @@ -58,9 +58,6 @@ interface ShadeViewController { /** Collapses the shade. */ fun collapse(animate: Boolean, delayed: Boolean, speedUpFactor: Float) - /** Collapses the shade with an animation duration in milliseconds. */ - fun collapseWithDuration(animationDuration: Int) - /** Collapses the shade instantly without animation. */ fun instantCollapse() @@ -102,9 +99,6 @@ interface ShadeViewController { /** Returns the StatusBarState. */ val barState: Int - /** Sets the amount of progress in the status bar launch animation. */ - fun applyLaunchAnimationProgress(linearProgress: Float) - /** Sets the alpha value of the shade to a value between 0 and 255. */ fun setAlpha(alpha: Int, animate: Boolean) diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt index 7a181f106514..d756f026e21d 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt @@ -37,7 +37,6 @@ class ShadeViewControllerEmptyImpl @Inject constructor() : override val isShadeFullyExpanded: Boolean = false override fun collapse(delayed: Boolean, speedUpFactor: Float) {} override fun collapse(animate: Boolean, delayed: Boolean, speedUpFactor: Float) {} - override fun collapseWithDuration(animationDuration: Int) {} override fun instantCollapse() {} override fun animateCollapseQs(fullyCollapse: Boolean) {} override fun canBeCollapsed(): Boolean = false @@ -55,7 +54,6 @@ class ShadeViewControllerEmptyImpl @Inject constructor() : override fun dozeTimeTick() {} override fun resetViews(animate: Boolean) {} override val barState: Int = 0 - override fun applyLaunchAnimationProgress(linearProgress: Float) {} override fun closeUserSwitcherIfOpen(): Boolean { return false } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt index 3a0f03f70e1c..f68141a4181c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt @@ -19,11 +19,13 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dump.DumpManager import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor +import com.android.systemui.util.kotlin.FlowDumperImpl import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @@ -35,10 +37,11 @@ import kotlinx.coroutines.flow.distinctUntilChanged class NotificationStackAppearanceViewModel @Inject constructor( + dumpManager: DumpManager, stackAppearanceInteractor: NotificationStackAppearanceInteractor, shadeInteractor: ShadeInteractor, sceneInteractor: SceneInteractor, -) { +) : FlowDumperImpl(dumpManager) { /** * The expansion fraction of the notification stack. It should go from 0 to 1 when transitioning * from Gone to Shade scenes, and remain at 1 when in Lockscreen or Shade scenes and while @@ -72,10 +75,12 @@ constructor( } } .distinctUntilChanged() + .dumpWhileCollecting("expandFraction") /** The bounds of the notification stack in the current scene. */ - val stackBounds: Flow<NotificationContainerBounds> = stackAppearanceInteractor.stackBounds + val stackBounds: Flow<NotificationContainerBounds> = + stackAppearanceInteractor.stackBounds.dumpValue("stackBounds") /** The y-coordinate in px of top of the contents of the notification stack. */ - val contentTop: StateFlow<Float> = stackAppearanceInteractor.contentTop + val contentTop: StateFlow<Float> = stackAppearanceInteractor.contentTop.dumpValue("contentTop") } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index b4c88c5f9e79..f5237938ebfa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -23,6 +23,7 @@ import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState @@ -60,6 +61,7 @@ import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTran import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor +import com.android.systemui.util.kotlin.FlowDumperImpl import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -88,6 +90,7 @@ class SharedNotificationContainerViewModel @Inject constructor( private val interactor: SharedNotificationContainerInteractor, + dumpManager: DumpManager, @Application applicationScope: CoroutineScope, private val keyguardInteractor: KeyguardInteractor, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, @@ -116,7 +119,7 @@ constructor( private val primaryBouncerToLockscreenTransitionViewModel: PrimaryBouncerToLockscreenTransitionViewModel, private val aodBurnInViewModel: AodBurnInViewModel, -) { +) : FlowDumperImpl(dumpManager) { private val statesForConstrainedNotifications: Set<KeyguardState> = setOf(AOD, LOCKSCREEN, DOZING, ALTERNATE_BOUNCER, PRIMARY_BOUNCER) @@ -126,6 +129,7 @@ constructor( .map { it.transitionState == STARTED || it.transitionState == RUNNING } .distinctUntilChanged() .onStart { emit(false) } + .dumpWhileCollecting("lockscreenToGlanceableHubRunning") private val glanceableHubToLockscreenRunning = keyguardTransitionInteractor @@ -133,6 +137,7 @@ constructor( .map { it.transitionState == STARTED || it.transitionState == RUNNING } .distinctUntilChanged() .onStart { emit(false) } + .dumpWhileCollecting("glanceableHubToLockscreenRunning") /** * Shade locked is a legacy concept, but necessary to mimic current functionality. Listen for @@ -148,8 +153,10 @@ constructor( isShadeLocked && (isQsExpanded || isShadeExpanded) } .distinctUntilChanged() + .dumpWhileCollecting("isShadeLocked") - val shadeCollapseFadeInComplete = MutableStateFlow(false) + private val shadeCollapseFadeInComplete = MutableStateFlow(false) + .dumpValue("shadeCollapseFadeInComplete") val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> = interactor.configurationBasedDimensions @@ -171,6 +178,7 @@ constructor( ) } .distinctUntilChanged() + .dumpWhileCollecting("configurationBasedDimensions") /** If the user is visually on one of the unoccluded lockscreen states. */ val isOnLockscreen: Flow<Boolean> = @@ -186,6 +194,7 @@ constructor( constrainedNotificationState || transitioningToOrFromLockscreen } .distinctUntilChanged() + .dumpWhileCollecting("isOnLockscreen") /** Are we purely on the keyguard without the shade/qs? */ val isOnLockscreenWithoutShade: Flow<Boolean> = @@ -204,6 +213,7 @@ constructor( started = SharingStarted.Eagerly, initialValue = false, ) + .dumpValue("isOnLockscreenWithoutShade") /** Are we purely on the glanceable hub without the shade/qs? */ val isOnGlanceableHubWithoutShade: Flow<Boolean> = @@ -222,6 +232,7 @@ constructor( started = SharingStarted.WhileSubscribed(), initialValue = false, ) + .dumpWhileCollecting("isOnGlanceableHubWithoutShade") /** * Fade in if the user swipes the shade back up, not if collapsed by going to AOD. This is @@ -284,6 +295,7 @@ constructor( started = SharingStarted.WhileSubscribed(), initialValue = false, ) + .dumpWhileCollecting("shadeCollapseFadeIn") /** * The container occupies the entire screen, and must be positioned relative to other elements. @@ -322,6 +334,7 @@ constructor( started = SharingStarted.Lazily, initialValue = NotificationContainerBounds(), ) + .dumpValue("bounds") /** * Ensure view is visible when the shade/qs are expanded. Also, as QS is expanding, fade out @@ -345,6 +358,7 @@ constructor( } } .onStart { emit(0f) } + .dumpWhileCollecting("alphaForShadeAndQsExpansion") private val alphaWhenGoneAndShadeState: Flow<Float> = combineTransform( @@ -357,6 +371,7 @@ constructor( emit(1f) } } + .dumpWhileCollecting("alphaWhenGoneAndShadeState") fun expansionAlpha(viewState: ViewStateAccessor): Flow<Float> { // All transition view models are mututally exclusive, and safe to merge @@ -389,7 +404,9 @@ constructor( isOnLockscreenWithoutShade, shadeCollapseFadeIn, alphaForShadeAndQsExpansion, - keyguardInteractor.dismissAlpha, + keyguardInteractor.dismissAlpha.dumpWhileCollecting( + "keyguardInteractor.keyguardAlpha" + ), ) { isOnLockscreenWithoutShade, shadeCollapseFadeIn, @@ -405,6 +422,7 @@ constructor( }, ) .distinctUntilChanged() + .dumpWhileCollecting("expansionAlpha") } /** @@ -438,6 +456,7 @@ constructor( } } } + .dumpWhileCollecting("glanceableHubAlpha") /** * Under certain scenarios, such as swiping up on the lockscreen, the container will need to be @@ -458,6 +477,7 @@ constructor( 0f } } + .dumpWhileCollecting("translationY") } /** @@ -469,6 +489,7 @@ constructor( lockscreenToGlanceableHubTransitionViewModel.notificationTranslationX, glanceableHubToLockscreenTransitionViewModel.notificationTranslationX, ) + .dumpWhileCollecting("translationX") /** * When on keyguard, there is limited space to display notifications so calculate how many could @@ -510,6 +531,7 @@ constructor( } } .distinctUntilChanged() + .dumpWhileCollecting("maxNotifications") } fun notificationStackChanged() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt index 270b94b1893e..23a080b7b931 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt @@ -45,8 +45,8 @@ import com.android.systemui.plugins.ActivityStarter.OnDismissAction import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.shade.ShadeController -import com.android.systemui.shade.ShadeViewController import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.SysuiStatusBarStateController @@ -71,7 +71,7 @@ constructor( private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>, private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>, private val shadeControllerLazy: Lazy<ShadeController>, - private val shadeViewControllerLazy: Lazy<ShadeViewController>, + private val commandQueue: CommandQueue, private val shadeAnimationInteractor: ShadeAnimationInteractor, private val statusBarKeyguardViewManagerLazy: Lazy<StatusBarKeyguardViewManager>, private val notifShadeWindowControllerLazy: Lazy<NotificationShadeWindowController>, @@ -853,10 +853,11 @@ constructor( if (dismissShade) { return StatusBarTransitionAnimatorController( animationController, - shadeViewControllerLazy.get(), shadeAnimationInteractor, shadeControllerLazy.get(), notifShadeWindowControllerLazy.get(), + commandQueue, + displayId, isLaunchForActivity ) } 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 5610ed926f70..b5ab4e3eb462 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -63,6 +63,7 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor; +import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationClickNotifier; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationPresenter; @@ -105,6 +106,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit private final NotificationVisibilityProvider mVisibilityProvider; private final HeadsUpManager mHeadsUpManager; private final ActivityStarter mActivityStarter; + private final CommandQueue mCommandQueue; private final NotificationClickNotifier mClickNotifier; private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private final KeyguardManager mKeyguardManager; @@ -143,6 +145,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit NotificationVisibilityProvider visibilityProvider, HeadsUpManager headsUpManager, ActivityStarter activityStarter, + CommandQueue commandQueue, NotificationClickNotifier clickNotifier, StatusBarKeyguardViewManager statusBarKeyguardViewManager, KeyguardManager keyguardManager, @@ -175,6 +178,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit mVisibilityProvider = visibilityProvider; mHeadsUpManager = headsUpManager; mActivityStarter = activityStarter; + mCommandQueue = commandQueue; mClickNotifier = clickNotifier; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mKeyguardManager = keyguardManager; @@ -444,10 +448,11 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit ActivityTransitionAnimator.Controller animationController = new StatusBarTransitionAnimatorController( mNotificationAnimationProvider.getAnimatorController(row, null), - mShadeViewController, mShadeAnimationInteractor, mShadeController, mNotificationShadeWindowController, + mCommandQueue, + mDisplayId, isActivityIntent); mActivityTransitionAnimator.startPendingIntentWithAnimation( animationController, @@ -486,10 +491,11 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit ActivityTransitionAnimator.Controller animationController = new StatusBarTransitionAnimatorController( mNotificationAnimationProvider.getAnimatorController(row), - mShadeViewController, mShadeAnimationInteractor, mShadeController, mNotificationShadeWindowController, + mCommandQueue, + mDisplayId, true /* isActivityIntent */); mActivityTransitionAnimator.startIntentWithAnimation( @@ -537,10 +543,11 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit viewController == null ? null : new StatusBarTransitionAnimatorController( viewController, - mShadeViewController, mShadeAnimationInteractor, mShadeController, mNotificationShadeWindowController, + mCommandQueue, + mDisplayId, true /* isActivityIntent */); mActivityTransitionAnimator.startIntentWithAnimation( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt index 7e907d80d277..705a11df83fc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt @@ -3,10 +3,13 @@ package com.android.systemui.statusbar.phone import android.view.View import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.animation.TransitionAnimator +import com.android.systemui.animation.TransitionAnimator.Companion.getProgress +import com.android.systemui.dagger.qualifiers.DisplayId import com.android.systemui.shade.ShadeController -import com.android.systemui.shade.ShadeViewController import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.NotificationShadeWindowController +import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment /** * A [ActivityTransitionAnimator.Controller] that takes care of collapsing the status bar at the @@ -14,12 +17,15 @@ import com.android.systemui.statusbar.NotificationShadeWindowController */ class StatusBarTransitionAnimatorController( private val delegate: ActivityTransitionAnimator.Controller, - private val shadeViewController: ShadeViewController, private val shadeAnimationInteractor: ShadeAnimationInteractor, private val shadeController: ShadeController, private val notificationShadeWindowController: NotificationShadeWindowController, + private val commandQueue: CommandQueue, + @DisplayId private val displayId: Int, private val isLaunchForActivity: Boolean = true ) : ActivityTransitionAnimator.Controller by delegate { + private var hideIconsDuringLaunchAnimation: Boolean = true + // Always sync the opening window with the shade, given that we draw a hole punch in the shade // of the same size and position as the opening app to make it visible. override val openingWindowSyncView: View? @@ -38,7 +44,7 @@ class StatusBarTransitionAnimatorController( delegate.onTransitionAnimationStart(isExpandingFullyAbove) shadeAnimationInteractor.setIsLaunchingActivity(true) if (!isExpandingFullyAbove) { - shadeViewController.collapseWithDuration( + shadeController.collapseWithDuration( ActivityTransitionAnimator.TIMINGS.totalDuration.toInt() ) } @@ -56,7 +62,19 @@ class StatusBarTransitionAnimatorController( linearProgress: Float ) { delegate.onTransitionAnimationProgress(state, progress, linearProgress) - shadeViewController.applyLaunchAnimationProgress(linearProgress) + val hideIcons = + getProgress( + ActivityTransitionAnimator.TIMINGS, + linearProgress, + ANIMATION_DELAY_ICON_FADE_IN, + 100 + ) == 0.0f + if (hideIcons != hideIconsDuringLaunchAnimation) { + hideIconsDuringLaunchAnimation = hideIcons + if (!hideIcons) { + commandQueue.recomputeDisableFlags(displayId, true /* animate */) + } + } } override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) { @@ -64,4 +82,12 @@ class StatusBarTransitionAnimatorController( shadeAnimationInteractor.setIsLaunchingActivity(false) shadeController.onLaunchAnimationCancelled(isLaunchForActivity) } + + companion object { + val ANIMATION_DELAY_ICON_FADE_IN = + (ActivityTransitionAnimator.TIMINGS.totalDuration - + CollapsedStatusBarFragment.FADE_IN_DURATION - + CollapsedStatusBarFragment.FADE_IN_DELAY - + 48) + } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.kt index d90a9c75deec..485f4b5cbfd7 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.kt @@ -17,23 +17,18 @@ package com.android.systemui.volume.panel.shared.flag import com.android.systemui.Flags -import com.android.systemui.compose.ComposeFacade import com.android.systemui.flags.RefactorFlagUtils import javax.inject.Inject /** Provides a flag to check for the new Compose based Volume Panel availability. */ class VolumePanelFlag @Inject constructor() { - /** - * Returns true when the new Volume Panel is available and false the otherwise. The new panel - * can only be available when [ComposeFacade.isComposeAvailable] is true. - */ + /** Returns true when the new Volume Panel is available and false the otherwise. */ fun canUseNewVolumePanel(): Boolean { - return ComposeFacade.isComposeAvailable() && Flags.newVolumePanel() + return Flags.newVolumePanel() } fun assertNewVolumePanel() { - require(ComposeFacade.isComposeAvailable()) RefactorFlagUtils.assertInNewMode(Flags.newVolumePanel(), Flags.FLAG_NEW_VOLUME_PANEL) } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt index 53e1b8b5bb70..d430e65770fd 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt @@ -18,11 +18,12 @@ package com.android.systemui.volume.panel.ui.activity import android.os.Bundle import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.viewModels -import com.android.systemui.compose.ComposeFacade import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.volume.panel.shared.flag.VolumePanelFlag +import com.android.systemui.volume.panel.ui.composable.VolumePanelRoot import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel import javax.inject.Inject import javax.inject.Provider @@ -44,7 +45,7 @@ constructor( volumePanelFlag.assertNewVolumePanel() - ComposeFacade.setVolumePanelActivityContent(this, viewModel) { finish() } + setContent { VolumePanelRoot(viewModel = viewModel, onDismiss = ::finish) } } override fun onContentChanged() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/compose/ComposeInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/compose/ComposeInitializerTest.kt index 3e6cc3bb4f6b..03e4f9ae1685 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/compose/ComposeInitializerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/compose/ComposeInitializerTest.kt @@ -32,10 +32,6 @@ import org.junit.runner.RunWith class ComposeInitializerTest : SysuiTestCase() { @Test fun testCanAddComposeViewInInitializedWindow() { - if (!ComposeFacade.isComposeAvailable()) { - return - } - val root = TestWindowRoot(context) try { runOnMainThreadAndWaitForIdleSync { ViewUtils.attachView(root) } @@ -55,12 +51,12 @@ class ComposeInitializerTest : SysuiTestCase() { class TestWindowRoot(context: Context) : FrameLayout(context) { override fun onAttachedToWindow() { super.onAttachedToWindow() - ComposeFacade.composeInitializer().onAttachedToWindow(this) + ComposeInitializer.onAttachedToWindow(this) } override fun onDetachedFromWindow() { super.onDetachedFromWindow() - ComposeFacade.composeInitializer().onDetachedFromWindow(this) + ComposeInitializer.onDetachedFromWindow(this) } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt index a992956f5121..59d8fc3d3fd0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt @@ -19,7 +19,6 @@ package com.android.systemui.keyboard.stickykeys.ui import android.app.Dialog import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.compose.ComposeFacade import com.android.systemui.keyboard.data.repository.FakeStickyKeysRepository import com.android.systemui.keyboard.data.repository.keyboardRepository import com.android.systemui.keyboard.stickykeys.StickyKeysLogger @@ -34,7 +33,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent -import org.junit.Assume import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -54,19 +52,22 @@ class StickyKeysIndicatorCoordinatorTest : SysuiTestCase() { @Before fun setup() { - Assume.assumeTrue(ComposeFacade.isComposeAvailable()) val dialogFactory = mock<StickyKeyDialogFactory>() whenever(dialogFactory.create(any())).thenReturn(dialog) val keyboardRepository = Kosmos().keyboardRepository - val viewModel = StickyKeysIndicatorViewModel( + val viewModel = + StickyKeysIndicatorViewModel( stickyKeysRepository, keyboardRepository, - testScope.backgroundScope) - coordinator = StickyKeysIndicatorCoordinator( + testScope.backgroundScope + ) + coordinator = + StickyKeysIndicatorCoordinator( testScope.backgroundScope, dialogFactory, viewModel, - mock<StickyKeysLogger>()) + mock<StickyKeysLogger>() + ) coordinator.startListening() keyboardRepository.setIsAnyKeyboardConnected(true) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index 69cd173f4253..72a890d6a5e5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -169,6 +169,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { keyguardInteractor = keyguardInteractor, transitionRepository = transitionRepository, transitionInteractor = transitionInteractor, + powerInteractor = powerInteractor, ) .apply { start() } @@ -574,8 +575,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { runCurrent() // WHEN the device begins to wake + keyguardRepository.setKeyguardShowing(true) powerInteractor.setAwakeForTest() - runCurrent() + advanceTimeBy(60L) assertThat(transitionRepository) .startedTransition( @@ -630,15 +632,42 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test - fun dozingToGone() = + fun dozingToGoneWithUnlock() = testScope.runTest { // GIVEN a prior transition has run to DOZING runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DOZING) + runCurrent() // WHEN biometrics succeeds with wake and unlock mode + powerInteractor.setAwakeForTest() keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK) + advanceTimeBy(60L) + + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.GONE, + from = KeyguardState.DOZING, + ownerName = "FromDozingTransitionInteractor", + animatorAssertion = { it.isNotNull() } + ) + + coroutineContext.cancelChildren() + } + + /** This handles security method NONE and screen off with lock timeout */ + @Test + fun dozingToGoneWithKeyguardNotShowing() = + testScope.runTest { + // GIVEN a prior transition has run to DOZING + runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DOZING) runCurrent() + // WHEN the device wakes up without a keyguard + keyguardRepository.setKeyguardShowing(false) + keyguardRepository.setKeyguardDismissible(true) + powerInteractor.setAwakeForTest() + advanceTimeBy(60L) + assertThat(transitionRepository) .startedTransition( to = KeyguardState.GONE, @@ -666,8 +695,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { runCurrent() // WHEN the device begins to wake + keyguardRepository.setKeyguardShowing(true) powerInteractor.setAwakeForTest() - runCurrent() + advanceTimeBy(60L) assertThat(transitionRepository) .startedTransition( @@ -1335,8 +1365,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // WHEN the keyguard is occluded and device wakes up keyguardRepository.setKeyguardOccluded(true) + keyguardRepository.setKeyguardShowing(true) powerInteractor.setAwakeForTest() - runCurrent() + advanceTimeBy(60L) // THEN a transition to OCCLUDED should occur assertThat(transitionRepository) diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt index 83932b0a6133..dbfab64b004a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt @@ -49,6 +49,19 @@ class ActivityTaskManagerTasksRepositoryTest : SysuiTestCase() { ) @Test + fun launchRecentTask_taskIsMovedToForeground() = + testScope.runTest { + val currentForegroundTask by collectLastValue(repo.foregroundTask) + val newForegroundTask = createTask(taskId = 1) + val backgroundTask = createTask(taskId = 2) + fakeActivityTaskManager.addRunningTasks(backgroundTask, newForegroundTask) + + repo.launchRecentTask(newForegroundTask) + + assertThat(currentForegroundTask).isEqualTo(newForegroundTask) + } + + @Test fun findRunningTaskFromWindowContainerToken_noMatch_returnsNull() { fakeActivityTaskManager.addRunningTasks(createTask(taskId = 1), createTask(taskId = 2)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt index 1c4870bc32b1..920e5ee94cca 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt @@ -17,7 +17,7 @@ package com.android.systemui.mediaprojection.taskswitcher.data.repository import android.app.ActivityManager.RunningTaskInfo -import android.app.ActivityTaskManager +import android.app.IActivityTaskManager import android.app.TaskStackListener import android.content.Intent import android.window.IWindowContainerToken @@ -31,7 +31,7 @@ class FakeActivityTaskManager { private val runningTasks = mutableListOf<RunningTaskInfo>() private val taskTaskListeners = mutableListOf<TaskStackListener>() - val activityTaskManager = mock<ActivityTaskManager>() + val activityTaskManager = mock<IActivityTaskManager>() init { whenever(activityTaskManager.registerTaskStackListener(any())).thenAnswer { @@ -42,10 +42,20 @@ class FakeActivityTaskManager { taskTaskListeners -= it.arguments[0] as TaskStackListener return@thenAnswer Unit } - whenever(activityTaskManager.getTasks(any())).thenAnswer { + whenever(activityTaskManager.getTasks(any(), any(), any(), any())).thenAnswer { val maxNumTasks = it.arguments[0] as Int return@thenAnswer runningTasks.take(maxNumTasks) } + whenever(activityTaskManager.startActivityFromRecents(any(), any())).thenAnswer { + val taskId = it.arguments[0] as Int + val runningTask = runningTasks.find { runningTask -> runningTask.taskId == taskId } + if (runningTask != null) { + moveTaskToForeground(runningTask) + return@thenAnswer 0 + } else { + return@thenAnswer -1 + } + } } fun moveTaskToForeground(task: RunningTaskInfo) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt index 44c411fdb1d1..28393e837b93 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt @@ -22,6 +22,8 @@ import android.os.Binder import android.os.IBinder import android.os.UserHandle import android.view.ContentRecordingSession +import android.window.WindowContainerToken +import com.android.systemui.mediaprojection.MediaProjectionServiceHelper import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -29,6 +31,7 @@ import com.android.systemui.util.mockito.whenever class FakeMediaProjectionManager { val mediaProjectionManager = mock<MediaProjectionManager>() + val helper = mock<MediaProjectionServiceHelper>() private val callbacks = mutableListOf<MediaProjectionManager.Callback>() @@ -41,6 +44,11 @@ class FakeMediaProjectionManager { callbacks -= it.arguments[0] as MediaProjectionManager.Callback return@thenAnswer Unit } + whenever(helper.updateTaskRecordingSession(any())).thenAnswer { + val token = it.arguments[0] as WindowContainerToken + dispatchOnSessionSet(session = createSingleTaskSession(token.asBinder())) + return@thenAnswer true + } } fun dispatchOnStart(info: MediaProjectionInfo = DEFAULT_INFO) { @@ -61,6 +69,7 @@ class FakeMediaProjectionManager { companion object { fun createDisplaySession(): ContentRecordingSession = ContentRecordingSession.createDisplaySession(/* displayToMirror = */ 123) + fun createSingleTaskSession(token: IBinder = Binder()): ContentRecordingSession = ContentRecordingSession.createTaskSession(token) diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt index 7bd97ce2670c..fdd434acdc9f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt @@ -28,9 +28,8 @@ import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeAct import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createToken import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @@ -40,7 +39,7 @@ import org.junit.runner.RunWith @SmallTest class MediaProjectionManagerRepositoryTest : SysuiTestCase() { - private val dispatcher = StandardTestDispatcher() + private val dispatcher = UnconfinedTestDispatcher() private val testScope = TestScope(dispatcher) private val fakeMediaProjectionManager = FakeMediaProjectionManager() @@ -58,14 +57,27 @@ class MediaProjectionManagerRepositoryTest : SysuiTestCase() { mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager, handler = Handler.getMain(), applicationScope = testScope.backgroundScope, - tasksRepository = tasksRepo + tasksRepository = tasksRepo, + backgroundDispatcher = dispatcher, + mediaProjectionServiceHelper = fakeMediaProjectionManager.helper ) @Test + fun switchProjectedTask_stateIsUpdatedWithNewTask() = + testScope.runTest { + val task = createTask(taskId = 1) + val state by collectLastValue(repo.mediaProjectionState) + + fakeActivityTaskManager.addRunningTasks(task) + repo.switchProjectedTask(task) + + assertThat(state).isEqualTo(MediaProjectionState.SingleTask(task)) + } + + @Test fun mediaProjectionState_onStart_emitsNotProjecting() = testScope.runTest { val state by collectLastValue(repo.mediaProjectionState) - runCurrent() fakeMediaProjectionManager.dispatchOnStart() @@ -76,7 +88,6 @@ class MediaProjectionManagerRepositoryTest : SysuiTestCase() { fun mediaProjectionState_onStop_emitsNotProjecting() = testScope.runTest { val state by collectLastValue(repo.mediaProjectionState) - runCurrent() fakeMediaProjectionManager.dispatchOnStop() @@ -87,7 +98,6 @@ class MediaProjectionManagerRepositoryTest : SysuiTestCase() { fun mediaProjectionState_onSessionSet_sessionNull_emitsNotProjecting() = testScope.runTest { val state by collectLastValue(repo.mediaProjectionState) - runCurrent() fakeMediaProjectionManager.dispatchOnSessionSet(session = null) @@ -98,7 +108,6 @@ class MediaProjectionManagerRepositoryTest : SysuiTestCase() { fun mediaProjectionState_onSessionSet_contentToRecordDisplay_emitsEntireScreen() = testScope.runTest { val state by collectLastValue(repo.mediaProjectionState) - runCurrent() fakeMediaProjectionManager.dispatchOnSessionSet( session = ContentRecordingSession.createDisplaySession(/* displayToMirror= */ 123) @@ -111,7 +120,6 @@ class MediaProjectionManagerRepositoryTest : SysuiTestCase() { fun mediaProjectionState_sessionSet_taskWithToken_noMatchingRunningTask_emitsEntireScreen() = testScope.runTest { val state by collectLastValue(repo.mediaProjectionState) - runCurrent() val taskWindowContainerToken = Binder() fakeMediaProjectionManager.dispatchOnSessionSet( @@ -128,7 +136,6 @@ class MediaProjectionManagerRepositoryTest : SysuiTestCase() { val task = createTask(taskId = 1, token = token) fakeActivityTaskManager.addRunningTasks(task) val state by collectLastValue(repo.mediaProjectionState) - runCurrent() fakeMediaProjectionManager.dispatchOnSessionSet( session = ContentRecordingSession.createTaskSession(token.asBinder()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt index b2ebe1bcbc8b..dfb688bbde4b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt @@ -61,6 +61,8 @@ class TaskSwitchInteractorTest : SysuiTestCase() { handler = Handler.getMain(), applicationScope = testScope.backgroundScope, tasksRepository = tasksRepo, + backgroundDispatcher = dispatcher, + mediaProjectionServiceHelper = fakeMediaProjectionManager.helper, ) private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo) @@ -118,6 +120,40 @@ class TaskSwitchInteractorTest : SysuiTestCase() { } @Test + fun taskSwitchChanges_projectingTask_foregroundTaskDifferent_thenSwitched_emitsUnchanged() = + testScope.runTest { + val projectedTask = createTask(taskId = 0) + val foregroundTask = createTask(taskId = 1) + val taskSwitchState by collectLastValue(interactor.taskSwitchChanges) + + fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask) + fakeMediaProjectionManager.dispatchOnSessionSet( + session = createSingleTaskSession(token = projectedTask.token.asBinder()) + ) + fakeActivityTaskManager.moveTaskToForeground(foregroundTask) + interactor.switchProjectedTask(foregroundTask) + + assertThat(taskSwitchState).isEqualTo(TaskSwitchState.TaskUnchanged) + } + + @Test + fun taskSwitchChanges_projectingTask_foregroundTaskDifferent_thenWentBack_emitsUnchanged() = + testScope.runTest { + val projectedTask = createTask(taskId = 0) + val foregroundTask = createTask(taskId = 1) + val taskSwitchState by collectLastValue(interactor.taskSwitchChanges) + + fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask) + fakeMediaProjectionManager.dispatchOnSessionSet( + session = createSingleTaskSession(token = projectedTask.token.asBinder()) + ) + fakeActivityTaskManager.moveTaskToForeground(foregroundTask) + interactor.goBackToTask(projectedTask) + + assertThat(taskSwitchState).isEqualTo(TaskSwitchState.TaskUnchanged) + } + + @Test fun taskSwitchChanges_projectingTask_foregroundTaskLauncher_emitsTaskUnchanged() = testScope.runTest { val projectedTask = createTask(taskId = 0) diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt index d0c6d7cddc46..c4e939339fa1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt @@ -21,14 +21,15 @@ import android.app.NotificationManager import android.os.Handler import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager +import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel +import com.android.systemui.res.R import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.mock @@ -42,6 +43,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor +import org.mockito.Mockito.never import org.mockito.Mockito.verify @OptIn(ExperimentalCoroutinesApi::class) @@ -49,7 +51,7 @@ import org.mockito.Mockito.verify @SmallTest class TaskSwitcherNotificationCoordinatorTest : SysuiTestCase() { - private val notificationManager: NotificationManager = mock() + private val notificationManager = mock<NotificationManager>() private val dispatcher = UnconfinedTestDispatcher() private val testScope = TestScope(dispatcher) @@ -70,22 +72,26 @@ class TaskSwitcherNotificationCoordinatorTest : SysuiTestCase() { handler = Handler.getMain(), applicationScope = testScope.backgroundScope, tasksRepository = tasksRepo, + backgroundDispatcher = dispatcher, + mediaProjectionServiceHelper = fakeMediaProjectionManager.helper, ) private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo) - private val viewModel = TaskSwitcherNotificationViewModel(interactor) - - private val coordinator = - TaskSwitcherNotificationCoordinator( - context, - notificationManager, - testScope.backgroundScope, - dispatcher, - viewModel - ) + private val viewModel = + TaskSwitcherNotificationViewModel(interactor, backgroundDispatcher = dispatcher) + + private lateinit var coordinator: TaskSwitcherNotificationCoordinator @Before fun setup() { + coordinator = + TaskSwitcherNotificationCoordinator( + context, + notificationManager, + testScope.backgroundScope, + viewModel, + fakeBroadcastDispatcher, + ) coordinator.start() } @@ -105,7 +111,7 @@ class TaskSwitcherNotificationCoordinatorTest : SysuiTestCase() { testScope.runTest { fakeMediaProjectionManager.dispatchOnStop() - verify(notificationManager).cancel(any()) + verify(notificationManager).cancel(any(), any()) } } @@ -114,7 +120,7 @@ class TaskSwitcherNotificationCoordinatorTest : SysuiTestCase() { testScope.runTest { fakeMediaProjectionManager.dispatchOnStop() val idCancel = argumentCaptor<Int>() - verify(notificationManager).cancel(idCancel.capture()) + verify(notificationManager).cancel(any(), idCancel.capture()) switchTask() val idNotify = argumentCaptor<Int>() @@ -124,9 +130,55 @@ class TaskSwitcherNotificationCoordinatorTest : SysuiTestCase() { } } + @Test + fun switchTaskAction_hidesNotification() = + testScope.runTest { + switchTask() + val notification = argumentCaptor<Notification>() + verify(notificationManager).notify(any(), any(), notification.capture()) + verify(notificationManager, never()).cancel(any(), any()) + + val action = findSwitchAction(notification.value) + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + action.actionIntent.intent + ) + + verify(notificationManager).cancel(any(), any()) + } + + @Test + fun goBackAction_hidesNotification() = + testScope.runTest { + switchTask() + val notification = argumentCaptor<Notification>() + verify(notificationManager).notify(any(), any(), notification.capture()) + verify(notificationManager, never()).cancel(any(), any()) + + val action = findGoBackAction(notification.value) + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + action.actionIntent.intent + ) + + verify(notificationManager).cancel(any(), any()) + } + + private fun findSwitchAction(notification: Notification): Notification.Action { + return notification.actions.first { + it.title == context.getString(R.string.media_projection_task_switcher_action_switch) + } + } + + private fun findGoBackAction(notification: Notification): Notification.Action { + return notification.actions.first { + it.title == context.getString(R.string.media_projection_task_switcher_action_back) + } + } + private fun switchTask() { - val projectedTask = FakeActivityTaskManager.createTask(taskId = 1) - val foregroundTask = FakeActivityTaskManager.createTask(taskId = 2) + val projectedTask = createTask(taskId = 1) + val foregroundTask = createTask(taskId = 2) fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask) fakeMediaProjectionManager.dispatchOnSessionSet( session = diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt index 7d38de4bc2d3..5dadf21a46b9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt @@ -63,11 +63,14 @@ class TaskSwitcherNotificationViewModelTest : SysuiTestCase() { handler = Handler.getMain(), applicationScope = testScope.backgroundScope, tasksRepository = tasksRepo, + backgroundDispatcher = dispatcher, + mediaProjectionServiceHelper = fakeMediaProjectionManager.helper, ) private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo) - private val viewModel = TaskSwitcherNotificationViewModel(interactor) + private val viewModel = + TaskSwitcherNotificationViewModel(interactor, backgroundDispatcher = dispatcher) @Test fun uiState_notProjecting_emitsNotShowing() = @@ -135,6 +138,40 @@ class TaskSwitcherNotificationViewModelTest : SysuiTestCase() { } @Test + fun uiState_projectingTask_foregroundTaskChanged_thenTaskSwitched_emitsNotShowing() = + testScope.runTest { + val projectedTask = createTask(taskId = 1) + val foregroundTask = createTask(taskId = 2) + val uiState by collectLastValue(viewModel.uiState) + + fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask) + fakeMediaProjectionManager.dispatchOnSessionSet( + session = createSingleTaskSession(projectedTask.token.asBinder()) + ) + fakeActivityTaskManager.moveTaskToForeground(foregroundTask) + viewModel.onSwitchTaskClicked(foregroundTask) + + assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing) + } + + @Test + fun uiState_projectingTask_foregroundTaskChanged_thenGoBack_emitsNotShowing() = + testScope.runTest { + val projectedTask = createTask(taskId = 1) + val foregroundTask = createTask(taskId = 2) + val uiState by collectLastValue(viewModel.uiState) + + fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask) + fakeMediaProjectionManager.dispatchOnSessionSet( + session = createSingleTaskSession(projectedTask.token.asBinder()) + ) + fakeActivityTaskManager.moveTaskToForeground(foregroundTask) + viewModel.onGoBackToTaskClicked(projectedTask) + + assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing) + } + + @Test fun uiState_projectingTask_foregroundTaskChanged_same_emitsNotShowing() = testScope.runTest { val projectedTask = createTask(taskId = 1) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt index 665fc750c742..96574e245d2d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt @@ -35,7 +35,6 @@ import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.setCommunalAvailable import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.communal.ui.viewmodel.CommunalViewModel -import com.android.systemui.compose.ComposeFacade import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testDispatcher @@ -52,9 +51,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.test.UnconfinedTestDispatcher import org.junit.After import org.junit.Assert.assertThrows -import org.junit.Assume.assumeTrue import org.junit.Before -import org.junit.BeforeClass import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock @@ -305,13 +302,5 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, CONTAINER_HEIGHT.toFloat(), 0) private val MOVE_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) private val UP_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0) - - @BeforeClass - @JvmStatic - fun beforeClass() { - // Glanceable hub requires Compose, no point running any of these tests if compose isn't - // enabled. - assumeTrue(ComposeFacade.isComposeAvailable()) - } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index b426d1de0b00..960fd59b4f10 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.shade +import org.mockito.Mockito.`when` as whenever import android.content.Context import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper @@ -33,7 +34,6 @@ import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.bouncer.ui.binder.BouncerViewBinder import com.android.systemui.classifier.FalsingCollectorFake -import com.android.systemui.compose.ComposeFacade.isComposeAvailable import com.android.systemui.dock.DockManager import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FakeFeatureFlagsClassic @@ -88,7 +88,6 @@ import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @@ -511,10 +510,6 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Test @Ignore("b/321332798") fun setsUpCommunalHubLayout_whenFlagEnabled() { - if (!isComposeAvailable()) { - return - } - whenever(mGlanceableHubContainerController.communalAvailable()) .thenReturn(MutableStateFlow(true)) @@ -537,10 +532,6 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Test fun doesNotSetupCommunalHubLayout_whenFlagDisabled() { - if (!isComposeAvailable()) { - return - } - whenever(mGlanceableHubContainerController.communalAvailable()) .thenReturn(MutableStateFlow(false)) 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 3792d5c1d6b9..eb890c7a78d0 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 @@ -234,19 +234,14 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { } @Test - fun shadeIsExpandedOnStatusIconClick() { + fun shadeIsExpandedOnStatusIconMouseClick() { val view = createViewMock() InstrumentationRegistry.getInstrumentation().runOnMainSync { controller = createAndInitController(view) } val statusContainer = view.requireViewById<View>(R.id.system_icons) statusContainer.dispatchTouchEvent( - getMotionEventFromSource( - MotionEvent.ACTION_UP, - 0, - 0, - InputDevice.SOURCE_MOUSE - ) + getActionUpEventFromSource(InputDevice.SOURCE_MOUSE) ) verify(shadeViewController).expand(any()) } @@ -259,18 +254,13 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { } val statusContainer = view.requireViewById<View>(R.id.system_icons) val handled = statusContainer.dispatchTouchEvent( - getMotionEventFromSource( - MotionEvent.ACTION_UP, - 0, - 0, - InputDevice.SOURCE_TOUCHSCREEN - ) + getActionUpEventFromSource(InputDevice.SOURCE_TOUCHSCREEN) ) assertThat(handled).isFalse() } - private fun getMotionEventFromSource(action: Int, x: Int, y: Int, source: Int): MotionEvent { - val ev = MotionEvent.obtain(0, 0, action, x.toFloat(), y.toFloat(), 0) + private fun getActionUpEventFromSource(source: Int): MotionEvent { + val ev = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0f, 0f, 0) ev.source = source return ev } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index 41514ce3e72c..938b2f88d4b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -79,6 +79,7 @@ import com.android.systemui.shade.ShadeViewController; import com.android.systemui.shade.data.repository.FakeShadeRepository; import com.android.systemui.shade.data.repository.ShadeAnimationRepository; import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl; +import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationClickNotifier; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationPresenter; @@ -130,6 +131,8 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { @Mock private ActivityStarter mActivityStarter; @Mock + private CommandQueue mCommandQueue; + @Mock private NotificationClickNotifier mClickNotifier; @Mock private StatusBarStateController mStatusBarStateController; @@ -236,6 +239,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { mVisibilityProvider, headsUpManager, mActivityStarter, + mCommandQueue, mClickNotifier, mStatusBarKeyguardViewManager, mock(KeyguardManager.class), @@ -257,7 +261,9 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { mock(NotificationShadeWindowController.class), mActivityTransitionAnimator, new ShadeAnimationInteractorLegacyImpl( - new ShadeAnimationRepository(), new FakeShadeRepository()), + new ShadeAnimationRepository(), + new FakeShadeRepository() + ), notificationAnimationProvider, mock(LaunchFullScreenIntentProvider.class), mPowerInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt index 3faa6eb8f5f2..4e05de27bb33 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt @@ -17,7 +17,6 @@ package com.android.systemui.flags import android.util.Log -import com.android.systemui.compose.ComposeFacade import com.android.systemui.scene.shared.flag.SceneContainerFlag import org.junit.Assert import org.junit.Assume @@ -42,10 +41,6 @@ class SceneContainerRule : TestRule { null || description?.getAnnotation(EnableSceneContainer::class.java) != null if (hasAnnotation) { Assume.assumeTrue( - "Compose must be available for @EnableSceneContainer test", - ComposeFacade.isComposeAvailable() - ) - Assume.assumeTrue( "Couldn't set Flags.SCENE_CONTAINER_ENABLED for @EnableSceneContainer test", trySetSceneContainerEnabled(true) ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt index da5cd679351f..2477415cc06e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt @@ -20,6 +20,7 @@ import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepos import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope +import com.android.systemui.power.domain.interactor.powerInteractor val Kosmos.fromAodTransitionInteractor by Kosmos.Fixture { @@ -30,5 +31,6 @@ val Kosmos.fromAodTransitionInteractor by bgDispatcher = testDispatcher, mainDispatcher = testDispatcher, keyguardInteractor = keyguardInteractor, + powerInteractor = powerInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt index 8ca53e6591c0..d5d357f81b35 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt @@ -39,7 +39,9 @@ val Kosmos.keyguardRootViewModel by Fixture { keyguardTransitionInteractor = keyguardTransitionInteractor, notificationsKeyguardInteractor = notificationsKeyguardInteractor, alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel, + aodToGoneTransitionViewModel = aodToGoneTransitionViewModel, aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel, + dozingToGoneTransitionViewModel = dozingToGoneTransitionViewModel, dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel, glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel, goneToAodTransitionViewModel = goneToAodTransitionViewModel, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt index d79633ae72ba..bada2a61995d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel +import com.android.systemui.dump.dumpManager import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.scene.domain.interactor.sceneInteractor @@ -24,6 +25,7 @@ import com.android.systemui.statusbar.notification.stack.domain.interactor.notif val Kosmos.notificationStackAppearanceViewModel by Fixture { NotificationStackAppearanceViewModel( + dumpManager = dumpManager, stackAppearanceInteractor = notificationStackAppearanceInteractor, shadeInteractor = shadeInteractor, sceneInteractor = sceneInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt index 832344d7a822..106e85cc8d85 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.dump.dumpManager import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerToGoneTransitionViewModel @@ -48,6 +49,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.sharedNotificationContainerViewModel by Fixture { SharedNotificationContainerViewModel( interactor = sharedNotificationContainerInteractor, + dumpManager = dumpManager, applicationScope = applicationCoroutineScope, keyguardInteractor = keyguardInteractor, keyguardTransitionInteractor = keyguardTransitionInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt index 41c11ad61c7f..7a86c4f73a3f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt @@ -33,6 +33,7 @@ import com.android.systemui.settings.userTracker import com.android.systemui.shade.domain.interactor.shadeAnimationInteractor import com.android.systemui.shade.shadeController import com.android.systemui.shade.shadeViewController +import com.android.systemui.statusbar.commandQueue import com.android.systemui.statusbar.notification.collection.provider.launchFullScreenIntentProvider import com.android.systemui.statusbar.notification.collection.render.notificationVisibilityProvider import com.android.systemui.statusbar.notification.notificationTransitionAnimatorControllerProvider @@ -59,6 +60,7 @@ val Kosmos.statusBarNotificationActivityStarter by notificationVisibilityProvider, headsUpManager, activityStarter, + commandQueue, notificationClickNotifier, statusBarKeyguardViewManager, keyguardManager, diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java index 6ca60be25023..f31eb44f23f5 100644 --- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java +++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java @@ -39,9 +39,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; -import android.graphics.Point; import android.graphics.Rect; -import android.hardware.display.DisplayManager; import android.os.FileUtils; import android.os.ParcelFileDescriptor; import android.os.RemoteException; @@ -51,22 +49,16 @@ import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.Xml; -import android.view.Display; -import android.view.DisplayInfo; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; -import com.android.modules.utils.TypedXmlPullParser; -import com.android.modules.utils.TypedXmlSerializer; import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; -import java.util.ArrayList; import java.util.List; /** @@ -110,9 +102,6 @@ public class WallpaperBackupAgent extends BackupAgent { @VisibleForTesting static final String WALLPAPER_INFO_STAGE = "wallpaper-info-stage"; - @VisibleForTesting - static final String WALLPAPER_BACKUP_DEVICE_INFO_STAGE = "wallpaper-backup-device-info-stage"; - static final String EMPTY_SENTINEL = "empty"; static final String QUOTA_SENTINEL = "quota"; @@ -121,11 +110,6 @@ public class WallpaperBackupAgent extends BackupAgent { static final String SYSTEM_GENERATION = "system_gen"; static final String LOCK_GENERATION = "lock_gen"; - /** - * An approximate area threshold to compare device dimension similarity - */ - static final int AREA_THRESHOLD = 50; // TODO: determine appropriate threshold - // If this file exists, it means we exceeded our quota last time private File mQuotaFile; private boolean mQuotaExceeded; @@ -137,8 +121,6 @@ public class WallpaperBackupAgent extends BackupAgent { private boolean mSystemHasLiveComponent; private boolean mLockHasLiveComponent; - private DisplayManager mDisplayManager; - @Override public void onCreate() { if (DEBUG) { @@ -155,8 +137,6 @@ public class WallpaperBackupAgent extends BackupAgent { mBackupManager = new BackupManager(getBaseContext()); mEventLogger = new WallpaperEventLogger(mBackupManager, /* wallpaperAgent */ this); - - mDisplayManager = getSystemService(DisplayManager.class); } @Override @@ -195,7 +175,6 @@ public class WallpaperBackupAgent extends BackupAgent { mSystemHasLiveComponent = mWallpaperManager.getWallpaperInfo(FLAG_SYSTEM) != null; mLockHasLiveComponent = mWallpaperManager.getWallpaperInfo(FLAG_LOCK) != null; - backupDeviceInfoFile(data); backupWallpaperInfoFile(/* sysOrLockChanged= */ sysChanged || lockChanged, data); backupSystemWallpaperFile(sharedPrefs, sysChanged, sysGeneration, data); backupLockWallpaperFileIfItExists(sharedPrefs, lockChanged, lockGeneration, data); @@ -212,50 +191,6 @@ public class WallpaperBackupAgent extends BackupAgent { } } - /** - * This method backs up the device dimension information. The device data will always get - * overwritten when triggering a backup - */ - private void backupDeviceInfoFile(FullBackupDataOutput data) - throws IOException { - final File deviceInfoStage = new File(getFilesDir(), WALLPAPER_BACKUP_DEVICE_INFO_STAGE); - - // save the dimensions of the device with xml formatting - Point dimensions = getScreenDimensions(); - Point secondaryDimensions = getRealSize(getSmallerDisplay()); - - deviceInfoStage.createNewFile(); - FileOutputStream fstream = new FileOutputStream(deviceInfoStage, false); - TypedXmlSerializer out = Xml.resolveSerializer(fstream); - out.startDocument(null, true); - out.startTag(null, "dimensions"); - - out.startTag(null, "width"); - out.text(String.valueOf(dimensions.x)); - out.endTag(null, "width"); - - out.startTag(null, "height"); - out.text(String.valueOf(dimensions.y)); - out.endTag(null, "height"); - - out.startTag(null, "secondarywidth"); - out.text(String.valueOf(secondaryDimensions != null ? secondaryDimensions.x : 0)); - out.endTag(null, "secondarywidth"); - - out.startTag(null, "secondaryheight"); - out.text(String.valueOf(secondaryDimensions != null ? secondaryDimensions.y : 0)); - out.endTag(null, "secondaryheight"); - - out.endTag(null, "dimensions"); - out.endDocument(); - fstream.flush(); - FileUtils.sync(fstream); - fstream.close(); - - if (DEBUG) Slog.v(TAG, "Storing device dimension data"); - backupFile(deviceInfoStage, data); - } - private void backupWallpaperInfoFile(boolean sysOrLockChanged, FullBackupDataOutput data) throws IOException { final ParcelFileDescriptor wallpaperInfoFd = mWallpaperManager.getWallpaperInfoFile(); @@ -429,22 +364,9 @@ public class WallpaperBackupAgent extends BackupAgent { final File infoStage = new File(filesDir, WALLPAPER_INFO_STAGE); final File imageStage = new File(filesDir, SYSTEM_WALLPAPER_STAGE); final File lockImageStage = new File(filesDir, LOCK_WALLPAPER_STAGE); - final File deviceDimensionsStage = new File(filesDir, WALLPAPER_BACKUP_DEVICE_INFO_STAGE); boolean lockImageStageExists = lockImageStage.exists(); try { - // Parse the device dimensions of the source device and compare with target to - // to identify whether we need to skip the remainder of the restore process - Pair<Point, Point> sourceDeviceDimensions = parseDeviceDimensions( - deviceDimensionsStage); - - Point targetDeviceDimensions = getScreenDimensions(); - if (sourceDeviceDimensions != null - && isSourceDeviceSignificantlySmallerThanTarget(sourceDeviceDimensions.first, - targetDeviceDimensions)) { - Slog.d(TAG, "The source device is significantly smaller than target"); - } - // First parse the live component name so that we know for logging if we care about // logging errors with the image restore. ComponentName wpService = parseWallpaperComponent(infoStage, "wp"); @@ -478,7 +400,6 @@ public class WallpaperBackupAgent extends BackupAgent { infoStage.delete(); imageStage.delete(); lockImageStage.delete(); - deviceDimensionsStage.delete(); SharedPreferences prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); prefs.edit() @@ -488,66 +409,6 @@ public class WallpaperBackupAgent extends BackupAgent { } } - /** - * This method parses the given file for the backed up device dimensions - * - * @param deviceDimensions the file which holds the device dimensions - * @return the backed up device dimensions - */ - private Pair<Point, Point> parseDeviceDimensions(File deviceDimensions) { - int width = 0, height = 0, secondaryHeight = 0, secondaryWidth = 0; - try { - TypedXmlPullParser parser = Xml.resolvePullParser( - new FileInputStream(deviceDimensions)); - - while (parser.next() != XmlPullParser.END_TAG) { - if (parser.getEventType() != XmlPullParser.START_TAG) { - continue; - } - - String name = parser.getName(); - - switch (name) { - case "width": - String widthText = readText(parser); - width = Integer.valueOf(widthText); - break; - - case "height": - String textHeight = readText(parser); - height = Integer.valueOf(textHeight); - break; - - case "secondarywidth": - String secondaryWidthText = readText(parser); - secondaryWidth = Integer.valueOf(secondaryWidthText); - break; - - case "secondaryheight": - String secondaryHeightText = readText(parser); - secondaryHeight = Integer.valueOf(secondaryHeightText); - break; - default: - break; - } - } - return new Pair<>(new Point(width, height), new Point(secondaryWidth, secondaryHeight)); - - } catch (Exception e) { - return null; - } - } - - private static String readText(TypedXmlPullParser parser) - throws IOException, XmlPullParserException { - String result = ""; - if (parser.next() == XmlPullParser.TEXT) { - result = parser.getText(); - parser.nextTag(); - } - return result; - } - @VisibleForTesting void updateWallpaperComponent(ComponentName wpService, int which) throws IOException { @@ -639,7 +500,6 @@ public class WallpaperBackupAgent extends BackupAgent { mEventLogger.onLockImageWallpaperRestoreFailed(error); } } - private Rect parseCropHint(File wallpaperInfo, String sectionTag) { Rect cropHint = new Rect(); try (FileInputStream stream = new FileInputStream(wallpaperInfo)) { @@ -677,7 +537,7 @@ public class WallpaperBackupAgent extends BackupAgent { if (type != XmlPullParser.START_TAG) continue; String tag = parser.getName(); if (!sectionTag.equals(tag)) continue; - for (Pair<Integer, String> pair : List.of( + for (Pair<Integer, String> pair: List.of( new Pair<>(WallpaperManager.PORTRAIT, "Portrait"), new Pair<>(WallpaperManager.LANDSCAPE, "Landscape"), new Pair<>(WallpaperManager.SQUARE_PORTRAIT, "SquarePortrait"), @@ -831,94 +691,6 @@ public class WallpaperBackupAgent extends BackupAgent { }; } - /** - * This method retrieves the dimensions of the largest display of the device - * - * @return a @{Point} object that contains the dimensions of the largest display on the device - */ - private Point getScreenDimensions() { - Point largetDimensions = null; - int maxArea = 0; - - for (Display display : getInternalDisplays()) { - Point displaySize = getRealSize(display); - - int width = displaySize.x; - int height = displaySize.y; - int area = width * height; - - if (area > maxArea) { - maxArea = area; - largetDimensions = displaySize; - } - } - - return largetDimensions; - } - - private Point getRealSize(Display display) { - DisplayInfo displayInfo = new DisplayInfo(); - display.getDisplayInfo(displayInfo); - return new Point(displayInfo.logicalWidth, displayInfo.logicalHeight); - } - - /** - * This method returns the smaller display on a multi-display device - * - * @return Display that corresponds to the smaller display on a device or null if ther is only - * one Display on a device - */ - private Display getSmallerDisplay() { - List<Display> internalDisplays = getInternalDisplays(); - Point largestDisplaySize = getScreenDimensions(); - - // Find the first non-matching internal display - for (Display display : internalDisplays) { - Point displaySize = getRealSize(display); - if (displaySize.x != largestDisplaySize.x || displaySize.y != largestDisplaySize.y) { - return display; - } - } - - // If no smaller display found, return null, as there is only a single display - return null; - } - - /** - * This method retrieves the collection of Display objects available in the device. - * i.e. non-external displays are ignored - * - * @return list of displays corresponding to each display in the device - */ - private List<Display> getInternalDisplays() { - Display[] allDisplays = mDisplayManager.getDisplays( - DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED); - - List<Display> internalDisplays = new ArrayList<>(); - for (Display display : allDisplays) { - if (display.getType() == Display.TYPE_INTERNAL) { - internalDisplays.add(display); - } - } - return internalDisplays; - } - - /** - * This method compares the source and target dimensions, and returns true if there is a - * significant difference in area between them and the source dimensions are smaller than the - * target dimensions. - * - * @param sourceDimensions is the dimensions of the source device - * @param targetDimensions is the dimensions of the target device - */ - @VisibleForTesting - boolean isSourceDeviceSignificantlySmallerThanTarget(Point sourceDimensions, - Point targetDimensions) { - int rawAreaDelta = (targetDimensions.x * targetDimensions.y) - - (sourceDimensions.x * sourceDimensions.y); - return rawAreaDelta > AREA_THRESHOLD; - } - @VisibleForTesting boolean isDeviceInRestore() { try { diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java index 79e7bf04dadf..3ecdf3f101a5 100644 --- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java +++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java @@ -59,7 +59,6 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.graphics.Point; import android.graphics.Rect; import android.os.FileUtils; import android.os.ParcelFileDescriptor; @@ -677,7 +676,7 @@ public class WallpaperBackupAgentTest { mWallpaperBackupAgent.onRestoreFinished(); - for (String wallpaper : List.of(WALLPAPER_IMG_LOCK, WALLPAPER_IMG_SYSTEM)) { + for (String wallpaper: List.of(WALLPAPER_IMG_LOCK, WALLPAPER_IMG_SYSTEM)) { DataTypeResult result = getLoggingResult(wallpaper, mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults()); assertThat(result).isNotNull(); @@ -841,26 +840,6 @@ public class WallpaperBackupAgentTest { testParseCropHints(testMap); } - @Test - public void test_sourceDimensionsAreLargerThanTarget() { - // source device is larger than target, expecting to get false - Point sourceDimensions = new Point(2208, 1840); - Point targetDimensions = new Point(1080, 2092); - boolean isSourceSmaller = mWallpaperBackupAgent - .isSourceDeviceSignificantlySmallerThanTarget(sourceDimensions, targetDimensions); - assertThat(isSourceSmaller).isEqualTo(false); - } - - @Test - public void test_sourceDimensionsMuchSmallerThanTarget() { - // source device is smaller than target, expecting to get true - Point sourceDimensions = new Point(1080, 2092); - Point targetDimensions = new Point(2208, 1840); - boolean isSourceSmaller = mWallpaperBackupAgent - .isSourceDeviceSignificantlySmallerThanTarget(sourceDimensions, targetDimensions); - assertThat(isSourceSmaller).isEqualTo(true); - } - private void testParseCropHints(Map<Integer, Rect> testMap) throws Exception { assumeTrue(multiCrop()); mockRestoredStaticWallpaperFile(testMap); @@ -955,7 +934,7 @@ public class WallpaperBackupAgentTest { TypedXmlSerializer out = Xml.resolveSerializer(fstream); out.startDocument(null, true); out.startTag(null, "wp"); - for (Map.Entry<Integer, Rect> entry : crops.entrySet()) { + for (Map.Entry<Integer, Rect> entry: crops.entrySet()) { String orientation = switch (entry.getKey()) { case WallpaperManager.PORTRAIT -> "Portrait"; case WallpaperManager.LANDSCAPE -> "Landscape"; diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index e535f0a05a02..178102e2876d 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -86,6 +86,21 @@ java_library { jarjar_rules: ":ravenwood-services-jarjar-rules", } +// Separated out from ravenwood-junit-impl since it needs to compile +// against `module_current` +java_library { + name: "ravenwood-junit-impl-flag", + srcs: [ + "junit-flag-src/**/*.java", + ], + sdk_version: "module_current", + libs: [ + "junit", + "flag-junit", + ], + visibility: ["//visibility:public"], +} + // Carefully compiles against only test_current to support tests that // want to verify they're unbundled. The "impl" library above is what // ships inside the Ravenwood environment to actually drive any API @@ -95,10 +110,12 @@ java_library { srcs: [ "junit-src/**/*.java", "junit-stub-src/**/*.java", + "junit-flag-src/**/*.java", ], sdk_version: "test_current", libs: [ "junit", + "flag-junit", ], visibility: ["//visibility:public"], } diff --git a/ravenwood/junit-flag-src/android/platform/test/flag/junit/RavenwoodFlagsValueProvider.java b/ravenwood/junit-flag-src/android/platform/test/flag/junit/RavenwoodFlagsValueProvider.java new file mode 100644 index 000000000000..9d6277473298 --- /dev/null +++ b/ravenwood/junit-flag-src/android/platform/test/flag/junit/RavenwoodFlagsValueProvider.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.platform.test.flag.junit; + +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.IFlagsValueProvider; + +/** + * Offer to create {@link CheckFlagsRule} instances that are useful on the Ravenwood deviceless + * testing environment. + * + * At the moment, default flag values are not available on Ravenwood, so the only options offered + * here are "all-on" and "all-off" options. Tests that want to exercise specific flag states should + * use {@link android.platform.test.flag.junit.SetFlagsRule}. + */ +public class RavenwoodFlagsValueProvider { + /** + * Create a {@link CheckFlagsRule} instance where flags are in an "all-on" state. + */ + public static CheckFlagsRule createAllOnCheckFlagsRule() { + return new CheckFlagsRule(new IFlagsValueProvider() { + @Override + public boolean getBoolean(String flag) { + return true; + } + }); + } + + /** + * Create a {@link CheckFlagsRule} instance where flags are in an "all-off" state. + */ + public static CheckFlagsRule createAllOffCheckFlagsRule() { + return new CheckFlagsRule(new IFlagsValueProvider() { + @Override + public boolean getBoolean(String flag) { + return false; + } + }); + } +} diff --git a/ravenwood/test-authors.md b/ravenwood/test-authors.md index 9179a621d0df..7c0cee812996 100644 --- a/ravenwood/test-authors.md +++ b/ravenwood/test-authors.md @@ -112,6 +112,24 @@ public class MyCodeTest { This naturally composes together well with any `RavenwoodRule` that your test may need. +While `SetFlagsRule` is generally a best-practice (as it can explicitly confirm behaviors for both "on" and "off" states), you may need to write tests that use `CheckFlagsRule` (such as when writing CTS). Ravenwood currently supports `CheckFlagsRule` by offering "all-on" and "all-off" behaviors: + +``` +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.RavenwoodFlagsValueProvider; +import android.platform.test.ravenwood.RavenwoodRule; + +@RunWith(AndroidJUnit4.class) +public class MyCodeTest { + @Rule + public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isUnderRavenwood() + ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule() + : DeviceFlagsValueProvider.createCheckFlagsRule(); +``` + +Ravenwood currently doesn't have knowledge of the "default" value of any flags, so using `createAllOnCheckFlagsRule()` is recommended to verify the widest possible set of behaviors. The example code above falls back to using default values from `DeviceFlagsValueProvider` when not running on Ravenwood. + ## Strategies for migration/bivalent tests Ravenwood aims to support tests that are written in a “bivalent” way, where the same test code can be dual-compiled to run on both a real Android device and under a Ravenwood environment. diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 19dd7b7ea2f6..d4f04b5ad760 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -3713,7 +3713,7 @@ public class AudioService extends IAudioService.Stub && AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device) && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { if (DEBUG_VOL) { - Log.d(TAG, "adjustSreamVolume: postSetAvrcpAbsoluteVolumeIndex index=" + Log.d(TAG, "adjustStreamVolume: postSetAvrcpAbsoluteVolumeIndex index=" + newIndex + "stream=" + streamType); } mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(newIndex / 10); @@ -3727,7 +3727,7 @@ public class AudioService extends IAudioService.Stub && streamType == getBluetoothContextualVolumeStream() && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { if (DEBUG_VOL) { - Log.d(TAG, "adjustSreamVolume postSetLeAudioVolumeIndex index=" + Log.d(TAG, "adjustStreamVolume postSetLeAudioVolumeIndex index=" + newIndex + " stream=" + streamType); } mDeviceBroker.postSetLeAudioVolumeIndex(newIndex, @@ -3740,7 +3740,7 @@ public class AudioService extends IAudioService.Stub // the one expected by the hearing aid if (streamType == getBluetoothContextualVolumeStream()) { if (DEBUG_VOL) { - Log.d(TAG, "adjustSreamVolume postSetHearingAidVolumeIndex index=" + Log.d(TAG, "adjustStreamVolume postSetHearingAidVolumeIndex index=" + newIndex + " stream=" + streamType); } mDeviceBroker.postSetHearingAidVolumeIndex(newIndex, streamType); @@ -4722,7 +4722,7 @@ public class AudioService extends IAudioService.Stub && streamType == getBluetoothContextualVolumeStream() && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { if (DEBUG_VOL) { - Log.d(TAG, "adjustSreamVolume postSetLeAudioVolumeIndex index=" + Log.d(TAG, "setStreamVolume postSetLeAudioVolumeIndex index=" + index + " stream=" + streamType); } mDeviceBroker.postSetLeAudioVolumeIndex(index, mStreamStates[streamType].getMaxIndex(), diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java index 68b4e3fb51ba..6b8586a2e483 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java @@ -119,7 +119,7 @@ public class FaceService extends SystemService { * Receives the incoming binder calls from FaceManager. */ @VisibleForTesting final class FaceServiceWrapper extends IFaceService.Stub { - @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) + @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC) @Override public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback, @NonNull String opPackageName) { diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index fb4976d3cef2..b2a738fd352b 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -463,12 +463,6 @@ public class AutomaticBrightnessController { boolean userChangedAutoBrightnessAdjustment, int displayPolicy, boolean shouldResetShortTermModel) { mState = state; - // While dozing, the application processor may be suspended which will prevent us from - // receiving new information from the light sensor. On some devices, we may be able to - // switch to a wake-up light sensor instead but for now we will simply disable the sensor - // and hold onto the last computed screen auto brightness. We save the dozing flag for - // debugging purposes. - boolean dozing = (displayPolicy == DisplayPowerRequest.POLICY_DOZE); boolean changed = setBrightnessConfiguration(configuration, shouldResetShortTermModel); changed |= setDisplayPolicy(displayPolicy); if (userChangedAutoBrightnessAdjustment) { @@ -482,10 +476,10 @@ public class AutomaticBrightnessController { } final boolean userInitiatedChange = userChangedBrightness || userChangedAutoBrightnessAdjustment; - if (userInitiatedChange && enable && !dozing) { + if (userInitiatedChange && enable) { prepareBrightnessAdjustmentSample(); } - changed |= setLightSensorEnabled(enable && !dozing); + changed |= setLightSensorEnabled(enable); if (mIsBrightnessThrottled != mBrightnessThrottler.isThrottled()) { // Maximum brightness has changed, so recalculate display brightness. diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 76f303596bdb..2010aca72494 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -1485,8 +1485,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } // Use default brightness when dozing unless overridden. - if ((Float.isNaN(brightnessState)) - && Display.isDozeState(state)) { + if (Float.isNaN(brightnessState) && mPowerRequest.policy == POLICY_DOZE) { rawBrightnessState = mScreenBrightnessDozeConfig; brightnessState = clampScreenBrightness(rawBrightnessState); mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT); diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java index 8eaecef6e562..d1ca49b8bf79 100644 --- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java +++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java @@ -15,6 +15,8 @@ */ package com.android.server.display.brightness.strategy; +import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE; + import android.annotation.Nullable; import android.content.Context; import android.hardware.display.BrightnessConfiguration; @@ -102,8 +104,7 @@ public class AutomaticBrightnessStrategy { boolean allowAutoBrightnessWhileDozingConfig, int brightnessReason, int policy, float lastUserSetScreenBrightness, boolean userSetBrightnessChanged) { final boolean autoBrightnessEnabledInDoze = - allowAutoBrightnessWhileDozingConfig - && Display.isDozeState(targetDisplayState); + allowAutoBrightnessWhileDozingConfig && policy == POLICY_DOZE; mIsAutoBrightnessEnabled = shouldUseAutoBrightness() && (targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze) && brightnessReason != BrightnessReason.REASON_OVERRIDE diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java index f1698dd0f025..8826e3d2d345 100644 --- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java +++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java @@ -36,6 +36,7 @@ import android.os.IBinder; import android.os.ResultReceiver; import android.util.EventLog; import android.util.Slog; +import android.view.MotionEvent; import android.view.inputmethod.ImeTracker; import android.view.inputmethod.InputMethod; import android.view.inputmethod.InputMethodManager; @@ -75,7 +76,7 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { @GuardedBy("ImfLock.class") @Override - public void performShowIme(IBinder showInputToken, @Nullable ImeTracker.Token statsToken, + public void performShowIme(IBinder showInputToken, @NonNull ImeTracker.Token statsToken, @InputMethod.ShowFlags int showFlags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { final IInputMethodInvoker curMethod = mService.getCurMethodLocked(); @@ -88,7 +89,8 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not. if (curMethod.showSoftInput(showInputToken, statsToken, showFlags, resultReceiver)) { if (DEBUG_IME_VISIBILITY) { - EventLog.writeEvent(IMF_SHOW_IME, statsToken.getTag(), + EventLog.writeEvent(IMF_SHOW_IME, + statsToken != null ? statsToken.getTag() : ImeTracker.TOKEN_NONE, Objects.toString(mService.mCurFocusedWindow), InputMethodDebug.softInputDisplayReasonToString(reason), InputMethodDebug.softInputModeToString( @@ -102,7 +104,7 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { @GuardedBy("ImfLock.class") @Override - public void performHideIme(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken, + public void performHideIme(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { final IInputMethodInvoker curMethod = mService.getCurMethodLocked(); if (curMethod != null) { @@ -118,7 +120,8 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not. if (curMethod.hideSoftInput(hideInputToken, statsToken, 0, resultReceiver)) { if (DEBUG_IME_VISIBILITY) { - EventLog.writeEvent(IMF_HIDE_IME, statsToken.getTag(), + EventLog.writeEvent(IMF_HIDE_IME, + statsToken != null ? statsToken.getTag() : ImeTracker.TOKEN_NONE, Objects.toString(mService.mCurFocusedWindow), InputMethodDebug.softInputDisplayReasonToString(reason), InputMethodDebug.softInputModeToString( @@ -132,14 +135,16 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { @GuardedBy("ImfLock.class") @Override - public void applyImeVisibility(IBinder windowToken, @Nullable ImeTracker.Token statsToken, + public void applyImeVisibility(IBinder windowToken, @NonNull ImeTracker.Token statsToken, @ImeVisibilityStateComputer.VisibilityState int state) { - applyImeVisibility(windowToken, statsToken, state, -1 /* ignore reason */); + applyImeVisibility(windowToken, statsToken, state, + SoftInputShowHideReason.NOT_SET /* ignore reason */); } @GuardedBy("ImfLock.class") void applyImeVisibility(IBinder windowToken, @Nullable ImeTracker.Token statsToken, - @ImeVisibilityStateComputer.VisibilityState int state, int reason) { + @ImeVisibilityStateComputer.VisibilityState int state, + @SoftInputShowHideReason int reason) { switch (state) { case STATE_SHOW_IME: ImeTracker.forLogging().onProgress(statsToken, @@ -164,18 +169,20 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { } break; case STATE_HIDE_IME_EXPLICIT: - mService.hideCurrentInputLocked(windowToken, statsToken, 0, null, reason); + mService.hideCurrentInputLocked(windowToken, statsToken, + 0 /* flags */, null /* resultReceiver */, reason); break; case STATE_HIDE_IME_NOT_ALWAYS: mService.hideCurrentInputLocked(windowToken, statsToken, - InputMethodManager.HIDE_NOT_ALWAYS, null, reason); + InputMethodManager.HIDE_NOT_ALWAYS, null /* resultReceiver */, reason); break; case STATE_SHOW_IME_IMPLICIT: mService.showCurrentInputLocked(windowToken, statsToken, - InputMethodManager.SHOW_IMPLICIT, null, reason); + InputMethodManager.SHOW_IMPLICIT, MotionEvent.TOOL_TYPE_UNKNOWN, + null /* resultReceiver */, reason); break; case STATE_SHOW_IME_SNAPSHOT: - showImeScreenshot(windowToken, mService.getDisplayIdToShowImeLocked(), null); + showImeScreenshot(windowToken, mService.getDisplayIdToShowImeLocked()); break; case STATE_REMOVE_IME_SNAPSHOT: removeImeScreenshot(mService.getDisplayIdToShowImeLocked()); @@ -187,11 +194,10 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { @GuardedBy("ImfLock.class") @Override - public boolean showImeScreenshot(@NonNull IBinder imeTarget, int displayId, - @Nullable ImeTracker.Token statsToken) { + public boolean showImeScreenshot(@NonNull IBinder imeTarget, int displayId) { if (mImeTargetVisibilityPolicy.showImeScreenshot(imeTarget, displayId)) { mService.onShowHideSoftInputRequested(false /* show */, imeTarget, - SHOW_IME_SCREENSHOT_FROM_IMMS, statsToken); + SHOW_IME_SCREENSHOT_FROM_IMMS, null /* statsToken */); return true; } return false; @@ -202,7 +208,7 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { public boolean removeImeScreenshot(int displayId) { if (mImeTargetVisibilityPolicy.removeImeScreenshot(displayId)) { mService.onShowHideSoftInputRequested(false /* show */, mService.mCurFocusedWindow, - REMOVE_IME_SCREENSHOT_FROM_IMMS, null); + REMOVE_IME_SCREENSHOT_FROM_IMMS, null /* statsToken */); return true; } return false; diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java index 776184fb098c..a380bc1ca171 100644 --- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java +++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java @@ -201,7 +201,7 @@ final class IInputMethodInvoker { // TODO(b/192412909): Convert this back to void method @AnyThread - boolean showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken, + boolean showSoftInput(IBinder showInputToken, @NonNull ImeTracker.Token statsToken, @InputMethod.ShowFlags int flags, ResultReceiver resultReceiver) { try { mTarget.showSoftInput(showInputToken, statsToken, flags, resultReceiver); @@ -214,8 +214,8 @@ final class IInputMethodInvoker { // TODO(b/192412909): Convert this back to void method @AnyThread - boolean hideSoftInput(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken, int flags, - ResultReceiver resultReceiver) { + boolean hideSoftInput(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken, + int flags, ResultReceiver resultReceiver) { try { mTarget.hideSoftInput(hideInputToken, statsToken, flags, resultReceiver); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java index d06c31cf36f0..85ab77355c9a 100644 --- a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java +++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java @@ -75,34 +75,12 @@ public final class ImeTrackerService extends IImeTracker.Stub { @NonNull @Override - public ImeTracker.Token onRequestShow(@NonNull String tag, int uid, + public ImeTracker.Token onStart(@NonNull String tag, int uid, @ImeTracker.Type int type, @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason, boolean fromUser) { final var binder = new Binder(); final var token = new ImeTracker.Token(binder, tag); - final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_SHOW, ImeTracker.STATUS_RUN, - origin, reason, fromUser); - synchronized (mLock) { - mHistory.addEntry(binder, entry); - - // Register a delayed task to handle the case where the new entry times out. - mHandler.postDelayed(() -> { - synchronized (mLock) { - mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, - ImeTracker.PHASE_NOT_SET); - } - }, TIMEOUT_MS); - } - return token; - } - - @NonNull - @Override - public ImeTracker.Token onRequestHide(@NonNull String tag, int uid, - @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason, boolean fromUser) { - final var binder = new Binder(); - final var token = new ImeTracker.Token(binder, tag); - final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_HIDE, ImeTracker.STATUS_RUN, - origin, reason, fromUser); + final var entry = new History.Entry(tag, uid, type, ImeTracker.STATUS_RUN, origin, reason, + fromUser); synchronized (mLock) { mHistory.addEntry(binder, entry); @@ -158,7 +136,7 @@ public final class ImeTrackerService extends IImeTracker.Stub { /** * Updates the IME request tracking token with new information available in IMMS. * - * @param statsToken the token corresponding to the current IME request. + * @param statsToken the token tracking the current IME request. * @param requestWindowName the name of the window that created the IME request. */ public void onImmsUpdate(@NonNull ImeTracker.Token statsToken, @@ -223,7 +201,7 @@ public final class ImeTrackerService extends IImeTracker.Stub { * Sets the live entry corresponding to the tracking token, if it exists, as finished, * and uploads the data for metrics. * - * @param statsToken the token corresponding to the current IME request. + * @param statsToken the token tracking the current IME request. * @param status the finish status of the IME request. * @param phase the phase the IME request finished at, if it exists * (or {@link ImeTracker#PHASE_NOT_SET} otherwise). diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java index 29fa36982351..9f2b84d9bfa5 100644 --- a/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java +++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java @@ -17,7 +17,6 @@ package com.android.server.inputmethod; import android.annotation.NonNull; -import android.annotation.Nullable; import android.os.IBinder; import android.os.ResultReceiver; import android.view.inputmethod.ImeTracker; @@ -34,12 +33,12 @@ interface ImeVisibilityApplier { * Performs showing IME on top of the given window. * * @param showInputToken A token that represents the requester to show IME. - * @param statsToken A token that tracks the progress of an IME request. + * @param statsToken The token tracking the current IME request. * @param resultReceiver If non-null, this will be called back to the caller when * it has processed request to tell what it has done. * @param reason The reason for requesting to show IME. */ - default void performShowIme(IBinder showInputToken, @Nullable ImeTracker.Token statsToken, + default void performShowIme(IBinder showInputToken, @NonNull ImeTracker.Token statsToken, @InputMethod.ShowFlags int showFlags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {} @@ -47,12 +46,12 @@ interface ImeVisibilityApplier { * Performs hiding IME to the given window * * @param hideInputToken A token that represents the requester to hide IME. - * @param statsToken A token that tracks the progress of an IME request. + * @param statsToken The token tracking the current IME request. * @param resultReceiver If non-null, this will be called back to the caller when * it has processed request to tell what it has done. * @param reason The reason for requesting to hide IME. */ - default void performHideIme(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken, + default void performHideIme(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {} /** @@ -60,10 +59,10 @@ interface ImeVisibilityApplier { * according to the given visibility state. * * @param windowToken The token of a window for applying the IME visibility - * @param statsToken A token that tracks the progress of an IME request. + * @param statsToken The token tracking the current IME request. * @param state The new IME visibility state for the applier to handle */ - default void applyImeVisibility(IBinder windowToken, @Nullable ImeTracker.Token statsToken, + default void applyImeVisibility(IBinder windowToken, @NonNull ImeTracker.Token statsToken, @ImeVisibilityStateComputer.VisibilityState int state) {} /** @@ -84,11 +83,9 @@ interface ImeVisibilityApplier { * * @param windowToken The token of a window to show the IME screenshot. * @param displayId The unique id to identify the display - * @param statsToken A token that tracks the progress of an IME request. * @return {@code true} if success, {@code false} otherwise. */ - default boolean showImeScreenshot(@NonNull IBinder windowToken, int displayId, - @Nullable ImeTracker.Token statsToken) { + default boolean showImeScreenshot(@NonNull IBinder windowToken, int displayId) { return false; } diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java index 0dd48ae6c9e1..743b8e382347 100644 --- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java +++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java @@ -212,9 +212,11 @@ public final class ImeVisibilityStateComputer { boolean visibleRequested, boolean removed) { if (mCurVisibleImeInputTarget == imeInputTarget && (!visibleRequested || removed) && mCurVisibleImeLayeringOverlay != null) { - mService.onApplyImeVisibilityFromComputer(imeInputTarget, - new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT, - SoftInputShowHideReason.HIDE_WHEN_INPUT_TARGET_INVISIBLE)); + final int reason = SoftInputShowHideReason.HIDE_WHEN_INPUT_TARGET_INVISIBLE; + final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE, + ImeTracker.ORIGIN_SERVER, reason, false /* fromUser */); + mService.onApplyImeVisibilityFromComputer(imeInputTarget, statsToken, + new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT, reason)); } mCurVisibleImeInputTarget = (visibleRequested && !removed) ? imeInputTarget : null; } @@ -224,7 +226,7 @@ public final class ImeVisibilityStateComputer { /** * Called when {@link InputMethodManagerService} is processing the show IME request. * - * @param statsToken The token for tracking this show request. + * @param statsToken The token tracking the current IME request. * @return {@code true} when the show request can proceed. */ boolean onImeShowFlags(@NonNull ImeTracker.Token statsToken, @@ -250,7 +252,7 @@ public final class ImeVisibilityStateComputer { /** * Called when {@link InputMethodManagerService} is processing the hide IME request. * - * @param statsToken The token for tracking this hide request. + * @param statsToken The token tracking the current IME request. * @return {@code true} when the hide request can proceed. */ boolean canHideIme(@NonNull ImeTracker.Token statsToken, diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 3dedca9c42ba..1564b2f86218 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -578,7 +578,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return mBindingController.hasMainConnection(); } - /** The token tracking the current IME request or {@code null} otherwise. */ + /** + * The token tracking the current IME show request that is waiting for a connection to an IME, + * otherwise {@code null}. + */ @Nullable private ImeTracker.Token mCurStatsToken; @@ -1128,11 +1131,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mVisibilityStateComputer.getImePolicy().setA11yRequestNoSoftKeyboard( accessibilitySoftKeyboardSetting); if (mVisibilityStateComputer.getImePolicy().isA11yRequestNoSoftKeyboard()) { - hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, - 0 /* flags */, null /* resultReceiver */, + hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */, SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE); } else if (isShowRequestedForCurrentWindow()) { - showCurrentInputImplicitLocked(mCurFocusedWindow, + showCurrentInputLocked(mCurFocusedWindow, InputMethodManager.SHOW_IMPLICIT, SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE); } } else if (stylusHandwritingEnabledUri.equals(uri)) { @@ -1627,8 +1629,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } // Hide soft input before user switch task since switch task may block main handler a while // and delayed the hideCurrentInputLocked(). - hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */, - null /* resultReceiver */, SoftInputShowHideReason.HIDE_SWITCH_USER); + hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */, + SoftInputShowHideReason.HIDE_SWITCH_USER); final UserSwitchHandlerTask task = new UserSwitchHandlerTask(this, userId, clientToBeReset); mUserSwitchHandlerTask = task; @@ -2216,8 +2218,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub clearClientSessionLocked(client); clearClientSessionForAccessibilityLocked(client); if (mCurClient == client) { - hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */, - null /* resultReceiver */, SoftInputShowHideReason.HIDE_REMOVE_CLIENT); + hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */, + SoftInputShowHideReason.HIDE_REMOVE_CLIENT); if (mBoundToMethod) { mBoundToMethod = false; IInputMethodInvoker curMethod = getCurMethodLocked(); @@ -2291,8 +2293,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // service, that wouldn't make the attached IME token validity check in time) // As a result, we have to notify WM to apply IME visibility before clearing the // binding states in the first place. - mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, mCurStatsToken, - STATE_HIDE_IME); + final var statsToken = createStatsTokenForFocusedClient(false /* show */, + SoftInputShowHideReason.UNBIND_CURRENT_METHOD); + mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, statsToken, STATE_HIDE_IME); } } @@ -2369,10 +2372,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (isShowRequestedForCurrentWindow()) { if (DEBUG) Slog.v(TAG, "Attach new input asks to show input"); // Re-use current statsToken, if it exists. - final ImeTracker.Token statsToken = mCurStatsToken; + final var statsToken = mCurStatsToken != null ? mCurStatsToken + : createStatsTokenForFocusedClient(true /* show */, + SoftInputShowHideReason.ATTACH_NEW_INPUT); mCurStatsToken = null; showCurrentInputLocked(mCurFocusedWindow, statsToken, - mVisibilityStateComputer.getShowFlags(), + mVisibilityStateComputer.getShowFlags(), MotionEvent.TOOL_TYPE_UNKNOWN, null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT); } @@ -2464,8 +2469,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } if (mVisibilityStateComputer.getImePolicy().isImeHiddenByDisplayPolicy()) { - hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */, - null /* resultReceiver */, + hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */, SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE); return InputBindResult.NO_IME; } @@ -3385,8 +3389,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public boolean showSoftInput(IInputMethodClient client, IBinder windowToken, - @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, - int lastClickTooType, ResultReceiver resultReceiver, + @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, + int lastClickToolType, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showSoftInput"); int uid = Binder.getCallingUid(); @@ -3402,7 +3406,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final long ident = Binder.clearCallingIdentity(); try { if (DEBUG) Slog.v(TAG, "Client requesting input be shown"); - return showCurrentInputLocked(windowToken, statsToken, flags, lastClickTooType, + return showCurrentInputLocked(windowToken, statsToken, flags, lastClickToolType, resultReceiver, reason); } finally { Binder.restoreCallingIdentity(ident); @@ -3644,24 +3648,18 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @GuardedBy("ImfLock.class") - boolean showCurrentInputLocked(IBinder windowToken, @Nullable ImeTracker.Token statsToken, - @InputMethodManager.ShowFlags int flags, ResultReceiver resultReceiver, - @SoftInputShowHideReason int reason) { + private boolean showCurrentInputLocked(IBinder windowToken, + @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason) { + final var statsToken = createStatsTokenForFocusedClient(true /* show */, reason); return showCurrentInputLocked(windowToken, statsToken, flags, - MotionEvent.TOOL_TYPE_UNKNOWN, resultReceiver, reason); + MotionEvent.TOOL_TYPE_UNKNOWN, null /* resultReceiver */, reason); } @GuardedBy("ImfLock.class") - private boolean showCurrentInputLocked(IBinder windowToken, - @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, - int lastClickToolType, ResultReceiver resultReceiver, + boolean showCurrentInputLocked(IBinder windowToken, + @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, + int lastClickToolType, @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { - // Create statsToken is none exists. - if (statsToken == null) { - statsToken = createStatsTokenForFocusedClient(true /* show */, - ImeTracker.ORIGIN_SERVER_START_INPUT, reason, false /* fromUser */); - } - if (!mVisibilityStateComputer.onImeShowFlags(statsToken, flags)) { return false; } @@ -3699,7 +3697,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken, - @Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, + @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { int uid = Binder.getCallingUid(); ImeTracing.getInstance().triggerManagerServiceDump( @@ -3728,17 +3726,29 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } } - @GuardedBy("ImfLock.class") - boolean hideCurrentInputLocked(IBinder windowToken, @Nullable ImeTracker.Token statsToken, - @InputMethodManager.HideFlags int flags, ResultReceiver resultReceiver, - @SoftInputShowHideReason int reason) { - // Create statsToken is none exists. - if (statsToken == null) { - final boolean fromUser = reason == SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_BACK_KEY; - statsToken = createStatsTokenForFocusedClient(false /* show */, - ImeTracker.ORIGIN_SERVER_HIDE_INPUT, reason, fromUser); + @Override + @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD) + public void hideSoftInputFromServerForTest() { + super.hideSoftInputFromServerForTest_enforcePermission(); + + synchronized (ImfLock.class) { + hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */, + SoftInputShowHideReason.HIDE_SOFT_INPUT); } + } + + @GuardedBy("ImfLock.class") + private boolean hideCurrentInputLocked(IBinder windowToken, + @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason) { + final var statsToken = createStatsTokenForFocusedClient(false /* show */, reason); + return hideCurrentInputLocked(windowToken, statsToken, flags, null /* resultReceiver */, + reason); + } + @GuardedBy("ImfLock.class") + boolean hideCurrentInputLocked(IBinder windowToken, @NonNull ImeTracker.Token statsToken, + @InputMethodManager.HideFlags int flags, @Nullable ResultReceiver resultReceiver, + @SoftInputShowHideReason int reason) { if (!mVisibilityStateComputer.canHideIme(statsToken, flags)) { return false; } @@ -3915,9 +3925,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub Slog.w(TAG, "If you need to impersonate a foreground user/profile from" + " a background user, use EditorInfo.targetInputMethodUser with" + " INTERACT_ACROSS_USERS_FULL permission."); - hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, - 0 /* flags */, - null /* resultReceiver */, + hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */, SoftInputShowHideReason.HIDE_INVALID_USER); return InputBindResult.INVALID_USER; } @@ -4025,11 +4033,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final ImeVisibilityResult imeVisRes = mVisibilityStateComputer.computeState(windowState, isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags)); if (imeVisRes != null) { + boolean isShow = false; switch (imeVisRes.getReason()) { case SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY: case SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV: case SoftInputShowHideReason.SHOW_STATE_VISIBLE_FORWARD_NAV: case SoftInputShowHideReason.SHOW_STATE_ALWAYS_VISIBLE: + isShow = true; + if (editorInfo != null) { res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection, editorInfo, startInputFlags, @@ -4039,8 +4050,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } break; } - - mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, null /* statsToken */, + final var statsToken = createStatsTokenForFocusedClient(isShow, imeVisRes.getReason()); + mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, statsToken, imeVisRes.getState(), imeVisRes.getReason()); if (imeVisRes.getReason() == SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW) { @@ -4068,13 +4079,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @GuardedBy("ImfLock.class") - private void showCurrentInputImplicitLocked(@NonNull IBinder windowToken, - @SoftInputShowHideReason int reason) { - showCurrentInputLocked(windowToken, null /* statsToken */, InputMethodManager.SHOW_IMPLICIT, - null /* resultReceiver */, reason); - } - - @GuardedBy("ImfLock.class") private boolean canInteractWithImeLocked(int uid, IInputMethodClient client, String methodName, @Nullable ImeTracker.Token statsToken) { if (mCurClient == null || client == null @@ -4776,15 +4780,17 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread private void applyImeVisibility(IBinder token, IBinder windowToken, boolean setVisible, - @Nullable ImeTracker.Token statsToken) { + @NonNull ImeTracker.Token statsToken) { try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibility"); synchronized (ImfLock.class) { if (!calledWithValidTokenLocked(token)) { ImeTracker.forLogging().onFailed(statsToken, - ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY); + ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME); return; } + ImeTracker.forLogging().onProgress(statsToken, + ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME); final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom( windowToken); mVisibilityApplier.applyImeVisibility(requestToken, statsToken, @@ -4862,17 +4868,21 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @BinderThread - private void hideMySoftInput(@NonNull IBinder token, @InputMethodManager.HideFlags int flags, - @SoftInputShowHideReason int reason) { + private void hideMySoftInput(@NonNull IBinder token, @NonNull ImeTracker.Token statsToken, + @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason) { try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideMySoftInput"); synchronized (ImfLock.class) { if (!calledWithValidTokenLocked(token)) { + ImeTracker.forLogging().onFailed(statsToken, + ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME); return; } + ImeTracker.forLogging().onProgress(statsToken, + ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME); final long ident = Binder.clearCallingIdentity(); try { - hideCurrentInputLocked(mLastImeTargetWindow, null /* statsToken */, flags, + hideCurrentInputLocked(mLastImeTargetWindow, statsToken, flags, null /* resultReceiver */, reason); } finally { Binder.restoreCallingIdentity(ident); @@ -4884,18 +4894,22 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @BinderThread - private void showMySoftInput(@NonNull IBinder token, @InputMethodManager.ShowFlags int flags) { + private void showMySoftInput(@NonNull IBinder token, @NonNull ImeTracker.Token statsToken, + @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason) { try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showMySoftInput"); synchronized (ImfLock.class) { if (!calledWithValidTokenLocked(token)) { + ImeTracker.forLogging().onFailed(statsToken, + ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME); return; } + ImeTracker.forLogging().onProgress(statsToken, + ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME); final long ident = Binder.clearCallingIdentity(); try { - showCurrentInputLocked(mLastImeTargetWindow, null /* statsToken */, flags, - null /* resultReceiver */, - SoftInputShowHideReason.SHOW_SOFT_INPUT_FROM_IME); + showCurrentInputLocked(mLastImeTargetWindow, statsToken, flags, + MotionEvent.TOOL_TYPE_UNKNOWN, null /* resultReceiver */, reason); } finally { Binder.restoreCallingIdentity(ident); } @@ -4912,10 +4926,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } } - void onApplyImeVisibilityFromComputer(IBinder windowToken, + void onApplyImeVisibilityFromComputer(IBinder windowToken, @NonNull ImeTracker.Token statsToken, @NonNull ImeVisibilityResult result) { synchronized (ImfLock.class) { - mVisibilityApplier.applyImeVisibility(windowToken, null, result.getState(), + mVisibilityApplier.applyImeVisibility(windowToken, statsToken, result.getState(), result.getReason()); } } @@ -5016,9 +5030,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub case MSG_HIDE_ALL_INPUT_METHODS: synchronized (ImfLock.class) { - final @SoftInputShowHideReason int reason = (int) msg.obj; - hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */, - null /* resultReceiver */, reason); + @SoftInputShowHideReason final int reason = (int) msg.obj; + hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */, reason); } return true; @@ -5176,7 +5189,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final ImeVisibilityResult imeVisRes = mVisibilityStateComputer.onInteractiveChanged( mCurFocusedWindow, interactive); if (imeVisRes != null) { - mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, null, + // Pass in a null statsToken as the IME snapshot is not tracked by ImeTracker. + mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, null /* statsToken */, imeVisRes.getState(), imeVisRes.getReason()); } // Eligible IME processes use new "setInteractive" protocol. @@ -6684,8 +6698,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final String nextIme; final List<InputMethodInfo> nextEnabledImes; if (userId == mSettings.getUserId()) { - hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, - 0 /* flags */, null /* resultReceiver */, + hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */, SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND); mBindingController.unbindCurrentMethod(); @@ -6814,13 +6827,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub * Creates an IME request tracking token for the current focused client. * * @param show whether this is a show or a hide request. - * @param origin the origin of the IME request. * @param reason the reason why the IME request was created. - * @param fromUser whether this request was created directly from user interaction. */ @NonNull private ImeTracker.Token createStatsTokenForFocusedClient(boolean show, - @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason, boolean fromUser) { + @SoftInputShowHideReason int reason) { final int uid = mCurFocusedWindowClient != null ? mCurFocusedWindowClient.mUid : -1; @@ -6828,13 +6839,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub ? mCurFocusedWindowEditorInfo.packageName : "uid(" + uid + ")"; - if (show) { - return ImeTracker.forLogging() - .onRequestShow(packageName, uid, origin, reason, fromUser); - } else { - return ImeTracker.forLogging() - .onRequestHide(packageName, uid, origin, reason, fromUser); - } + return ImeTracker.forLogging().onStart(packageName, uid, + show ? ImeTracker.TYPE_SHOW : ImeTracker.TYPE_HIDE, ImeTracker.ORIGIN_SERVER, + reason, false /* fromUser */); } private static final class InputMethodPrivilegedOperationsImpl @@ -6909,12 +6916,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @Override - public void hideMySoftInput(@InputMethodManager.HideFlags int flags, - @SoftInputShowHideReason int reason, AndroidFuture future /* T=Void */) { + public void hideMySoftInput(@NonNull ImeTracker.Token statsToken, + @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason, + AndroidFuture future /* T=Void */) { @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future; try { - mImms.hideMySoftInput(mToken, flags, reason); + mImms.hideMySoftInput(mToken, statsToken, flags, reason); typedFuture.complete(null); } catch (Throwable e) { typedFuture.completeExceptionally(e); @@ -6923,12 +6931,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @Override - public void showMySoftInput(@InputMethodManager.ShowFlags int flags, + public void showMySoftInput(@NonNull ImeTracker.Token statsToken, + @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason, AndroidFuture future /* T=Void */) { @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future; try { - mImms.showMySoftInput(mToken, flags); + mImms.showMySoftInput(mToken, statsToken, flags, reason); typedFuture.complete(null); } catch (Throwable e) { typedFuture.completeExceptionally(e); @@ -6987,7 +6996,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @Override public void applyImeVisibilityAsync(IBinder windowToken, boolean setVisible, - @Nullable ImeTracker.Token statsToken) { + @NonNull ImeTracker.Token statsToken) { mImms.applyImeVisibility(mToken, windowToken, setVisible, statsToken); } diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java index db6a9af0fe62..9caf5cfe67f3 100644 --- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java +++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java @@ -184,6 +184,13 @@ public class ZeroJankProxy extends IInputMethodManager.Stub { return true; } + @Override + @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD) + public void hideSoftInputFromServerForTest() throws RemoteException { + super.hideSoftInputFromServerForTest_enforcePermission(); + mInner.hideSoftInputFromServerForTest(); + } + @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) @Override public void startInputOrWindowGainedFocusAsync( diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 1f7d5490dd6d..2a487856bfb8 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -851,7 +851,6 @@ class MediaRouter2ServiceImpl { } } - @RequiresPermission(value = Manifest.permission.MEDIA_ROUTING_CONTROL, conditional = true) private boolean checkMediaRoutingControlPermission( int callerUid, int callerPid, @Nullable String callerPackageName) { return PermissionChecker.checkPermissionForDataDelivery( diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java index 7af5c08f5ffa..1dc86f2b7f1e 100644 --- a/services/core/java/com/android/server/media/MediaSession2Record.java +++ b/services/core/java/com/android/server/media/MediaSession2Record.java @@ -56,7 +56,6 @@ public class MediaSession2Record extends MediaSessionRecordImpl { private boolean mIsClosed; private final int mPid; - private final ForegroundServiceDelegationOptions mForegroundServiceDelegationOptions; public MediaSession2Record( Session2Token sessionToken, @@ -76,25 +75,6 @@ public class MediaSession2Record extends MediaSessionRecordImpl { .build(); mPid = pid; mPolicies = policies; - mForegroundServiceDelegationOptions = - new ForegroundServiceDelegationOptions.Builder() - .setClientPid(mPid) - .setClientUid(getUid()) - .setClientPackageName(getPackageName()) - .setClientAppThread(null) - .setSticky(false) - .setClientInstanceName( - "MediaSessionFgsDelegate_" - + getUid() - + "_" - + mPid - + "_" - + getPackageName()) - .setForegroundServiceTypes(0) - .setDelegationService( - ForegroundServiceDelegationOptions - .DELEGATION_SERVICE_MEDIA_PLAYBACK) - .build(); } } @@ -119,7 +99,10 @@ public class MediaSession2Record extends MediaSessionRecordImpl { @Override public ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions() { - return mForegroundServiceDelegationOptions; + // For an app to be eligible for FGS delegation, it needs a media session liked to a media + // notification. Currently, notifications cannot be linked to MediaSession2 so it is not + // supported. + return null; } @Override diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 42851747c5cb..e2163c54f4e2 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -169,7 +169,8 @@ public class MediaSessionService extends SystemService implements Monitor { private UsageStatsManagerInternal mUsageStatsManagerInternal; /* Maps uid with all user engaging session tokens associated to it */ - private final SparseArray<Set<MediaSession.Token>> mUserEngagingSessions = new SparseArray<>(); + private final SparseArray<Set<MediaSessionRecordImpl>> mUserEngagingSessions = + new SparseArray<>(); // The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile) // It's always not null after the MediaSessionService is started. @@ -625,9 +626,7 @@ public class MediaSessionService extends SystemService implements Monitor { } ForegroundServiceDelegationOptions foregroundServiceDelegationOptions = record.getForegroundServiceDelegationOptions(); - if (foregroundServiceDelegationOptions == null - || foregroundServiceDelegationOptions.mClientPid == Process.INVALID_PID) { - // This record doesn't support FGS delegation. In practice, this is MediaSession2. + if (foregroundServiceDelegationOptions == null) { return; } if (allowRunningInForeground) { @@ -640,23 +639,21 @@ public class MediaSessionService extends SystemService implements Monitor { } private void reportMediaInteractionEvent(MediaSessionRecordImpl record, boolean userEngaged) { - if (!android.app.usage.Flags.userInteractionTypeApi() - || !(record instanceof MediaSessionRecord)) { + if (!android.app.usage.Flags.userInteractionTypeApi()) { return; } String packageName = record.getPackageName(); int sessionUid = record.getUid(); - MediaSession.Token token = ((MediaSessionRecord) record).getSessionToken(); if (userEngaged) { if (!mUserEngagingSessions.contains(sessionUid)) { mUserEngagingSessions.put(sessionUid, new HashSet<>()); reportUserInteractionEvent( USAGE_STATS_ACTION_START, record.getUserId(), packageName); } - mUserEngagingSessions.get(sessionUid).add(token); + mUserEngagingSessions.get(sessionUid).add(record); } else if (mUserEngagingSessions.contains(sessionUid)) { - mUserEngagingSessions.get(sessionUid).remove(token); + mUserEngagingSessions.get(sessionUid).remove(record); if (mUserEngagingSessions.get(sessionUid).isEmpty()) { reportUserInteractionEvent( USAGE_STATS_ACTION_STOP, record.getUserId(), packageName); diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index e9a7fe1371ac..ec4b38b10af2 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -3504,7 +3504,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { StatusBarManagerInternal statusbar = getStatusBarManagerInternal(); if (statusbar != null) { - statusbar.moveFocusedTaskToFullscreen(event.getDisplayId()); + statusbar.moveFocusedTaskToFullscreen(getTargetDisplayIdForKeyEvent(event)); logKeyboardSystemsEvent(event, KeyboardLogEvent.MULTI_WINDOW_NAVIGATION); return true; } @@ -3514,7 +3514,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { StatusBarManagerInternal statusbar = getStatusBarManagerInternal(); if (statusbar != null) { - statusbar.enterDesktop(event.getDisplayId()); + statusbar.enterDesktop(getTargetDisplayIdForKeyEvent(event)); logKeyboardSystemsEvent(event, KeyboardLogEvent.DESKTOP_MODE); return true; } @@ -6951,4 +6951,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { == PERMISSION_GRANTED; } } + + private int getTargetDisplayIdForKeyEvent(KeyEvent event) { + int displayId = event.getDisplayId(); + + if (displayId == INVALID_DISPLAY) { + displayId = mTopFocusedDisplayId; + } + + if (displayId == INVALID_DISPLAY) { + return DEFAULT_DISPLAY; + } else { + return displayId; + } + } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 860fe15f648a..7d5aa96db0b8 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -2894,6 +2894,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A /** Makes starting window always fill the associated task. */ private void attachStartingSurfaceToAssociatedTask() { + if (mSyncState == SYNC_STATE_NONE && isEmbedded()) { + // Collect this activity since it's starting window will reparent to task. To ensure + // any starting window's transaction will occur in order. + mTransitionController.collect(this); + } // Associate the configuration of starting window with the task. overrideConfigurationPropagation(mStartingWindow, mStartingData.mAssociatedTask); getSyncTransaction().reparent(mStartingWindow.mSurfaceControl, diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 218b7512b861..e283f3e8ef0e 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -4163,19 +4163,21 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public void onPictureInPictureUiStateChanged(PictureInPictureUiState pipState) { enforceTaskPermission("onPictureInPictureUiStateChanged"); - // The PictureInPictureUiState is sent to current pip task if there is any - // -or- the top standard task (state like entering PiP does not require a pinned task). - final Task task; - if (mRootWindowContainer.getDefaultTaskDisplayArea().hasPinnedTask()) { - task = mRootWindowContainer.getDefaultTaskDisplayArea().getRootPinnedTask(); - } else { - task = mRootWindowContainer.getDefaultTaskDisplayArea().getRootTask( - t -> t.isActivityTypeStandard()); - } - if (task != null && task.getTopMostActivity() != null - && !task.getTopMostActivity().isState(FINISHING, DESTROYING, DESTROYED)) { - mWindowManager.mAtmService.mActivityClientController.onPictureInPictureUiStateChanged( - task.getTopMostActivity(), pipState); + synchronized (mGlobalLock) { + // The PictureInPictureUiState is sent to current pip task if there is any + // -or- the top standard task (state like entering PiP does not require a pinned task). + final Task task; + if (mRootWindowContainer.getDefaultTaskDisplayArea().hasPinnedTask()) { + task = mRootWindowContainer.getDefaultTaskDisplayArea().getRootPinnedTask(); + } else { + task = mRootWindowContainer.getDefaultTaskDisplayArea().getRootTask( + t -> t.isActivityTypeStandard()); + } + if (task != null && task.getTopMostActivity() != null + && !task.getTopMostActivity().isState(FINISHING, DESTROYING, DESTROYED)) { + mWindowManager.mAtmService.mActivityClientController + .onPictureInPictureUiStateChanged(task.getTopMostActivity(), pipState); + } } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index d1d498dbac46..2cda1f55b038 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -74,6 +74,7 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLAS import static com.android.server.wm.ActivityTaskManagerService.ANIMATE; import static com.android.server.wm.ActivityTaskManagerService.H.FIRST_SUPERVISOR_TASK_MSG; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE; +import static com.android.server.wm.ClientLifecycleManager.shouldDispatchCompatClientTransactionIndependently; import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_ALLOWLISTED; import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE; import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE_PRIV; @@ -948,6 +949,13 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } // Schedule transaction. + if (shouldDispatchCompatClientTransactionIndependently(r.mTargetSdk)) { + // LaunchActivityItem has @UnsupportedAppUsage usages. + // Guard the bundleClientTransactionFlag feature with targetSDK on Android 15+. + // To not bundle the transaction, dispatch the pending before schedule new + // transaction. + mService.getLifecycleManager().dispatchPendingTransaction(proc.getThread()); + } mService.getLifecycleManager().scheduleTransactionAndLifecycleItems( proc.getThread(), launchActivityItem, lifecycleItem, // Immediately dispatch the transaction, so that if it fails, the server can diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java index e48e4e84d60d..816fe1dbae94 100644 --- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java +++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java @@ -22,6 +22,7 @@ import android.app.servertransaction.ActivityLifecycleItem; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.ClientTransactionItem; import android.os.Binder; +import android.os.Build; import android.os.IBinder; import android.os.RemoteException; import android.os.Trace; @@ -179,6 +180,22 @@ class ClientLifecycleManager { Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } + /** Executes the pending transaction for the given client process. */ + void dispatchPendingTransaction(@NonNull IApplicationThread client) { + if (!Flags.bundleClientTransactionFlag()) { + return; + } + final ClientTransaction pendingTransaction = mPendingTransactions.remove(client.asBinder()); + if (pendingTransaction != null) { + try { + scheduleTransaction(pendingTransaction); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to deliver pending transaction", e); + // TODO(b/323801078): apply cleanup for individual transaction item if needed. + } + } + } + /** * Called to when {@link WindowSurfacePlacer#continueLayout}. * Dispatches all pending transactions unless there is an ongoing/scheduled layout, in which @@ -233,4 +250,17 @@ class ClientLifecycleManager { && !mWms.mWindowPlacerLocked.isTraversalScheduled() && !mWms.mWindowPlacerLocked.isInLayout(); } + + /** + * Guards the bundleClientTransactionFlag feature with targetSDK on Android 15+. + * + * Suppressing because it can't guard with @EnabledSince on VANILLA_ICE_CREAM yet since the + * version is not published. + * + * TODO(b/324203798): update in V + */ + @SuppressWarnings("AndroidFrameworkCompatChange") + static boolean shouldDispatchCompatClientTransactionIndependently(int appTargetSdk) { + return appTargetSdk <= Build.VERSION_CODES.UPSIDE_DOWN_CAKE; + } } diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index ea31e632cfb8..9fee3433f6be 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -215,11 +215,11 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { * when {@link android.inputmethodservice.InputMethodService} requests to show IME * on {@param imeTarget}. * - * @param imeTarget imeTarget on which IME show request is coming from. - * @param statsToken the token tracking the current IME show request or {@code null} otherwise. + * @param imeTarget imeTarget on which IME request is coming from. + * @param statsToken the token tracking the current IME request. */ void scheduleShowImePostLayout(InsetsControlTarget imeTarget, - @Nullable ImeTracker.Token statsToken) { + @NonNull ImeTracker.Token statsToken) { boolean targetChanged = isTargetChangedWithinActivity(imeTarget); mImeRequester = imeTarget; // Cancel the pre-existing stats token, if any. diff --git a/services/core/java/com/android/server/wm/InsetsControlTarget.java b/services/core/java/com/android/server/wm/InsetsControlTarget.java index b74eb56ebdca..cc3de7a3462c 100644 --- a/services/core/java/com/android/server/wm/InsetsControlTarget.java +++ b/services/core/java/com/android/server/wm/InsetsControlTarget.java @@ -60,8 +60,8 @@ interface InsetsControlTarget { * Instructs the control target to show inset sources. * * @param types to specify which types of insets source window should be shown. - * @param fromIme {@code true} if IME show request originated from {@link InputMethodService}. - * @param statsToken the token tracking the current IME show request or {@code null} otherwise. + * @param fromIme {@code true} if the IME request originated from {@link InputMethodService}. + * @param statsToken the token tracking the current IME request or {@code null} otherwise. */ default void showInsets(@InsetsType int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) { @@ -71,8 +71,8 @@ interface InsetsControlTarget { * Instructs the control target to hide inset sources. * * @param types to specify which types of insets source window should be hidden. - * @param fromIme {@code true} if IME hide request originated from {@link InputMethodService}. - * @param statsToken the token tracking the current IME hide request or {@code null} otherwise. + * @param fromIme {@code true} if the IME request originated from {@link InputMethodService}. + * @param statsToken the token tracking the current IME request or {@code null} otherwise. */ default void hideInsets(@InsetsType int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) { diff --git a/services/core/java/com/android/server/wm/SnapshotCache.java b/services/core/java/com/android/server/wm/SnapshotCache.java index 86804360f6f4..64d8c7555fa6 100644 --- a/services/core/java/com/android/server/wm/SnapshotCache.java +++ b/services/core/java/com/android/server/wm/SnapshotCache.java @@ -16,6 +16,7 @@ package com.android.server.wm; import android.annotation.Nullable; +import android.hardware.HardwareBuffer; import android.util.ArrayMap; import android.window.TaskSnapshot; @@ -92,6 +93,10 @@ abstract class SnapshotCache<TYPE extends WindowContainer> { if (entry != null) { mAppIdMap.remove(entry.topApp); mRunningCache.remove(id); + final HardwareBuffer buffer = entry.snapshot.getHardwareBuffer(); + if (buffer != null) { + buffer.close(); + } } } } diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 5df2edc808f6..77319cc0ba8a 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -829,20 +829,20 @@ public abstract class WindowManagerInternal { * Show IME on imeTargetWindow once IME has finished layout. * * @param imeTargetWindowToken token of the (IME target) window which IME should be shown. - * @param statsToken the token tracking the current IME show request or {@code null} otherwise. + * @param statsToken the token tracking the current IME request. */ public abstract void showImePostLayout(IBinder imeTargetWindowToken, - @Nullable ImeTracker.Token statsToken); + @NonNull ImeTracker.Token statsToken); /** * Hide IME using imeTargetWindow when requested. * - * @param imeTargetWindowToken token of the (IME target) window on which requests hiding IME. + * @param imeTargetWindowToken token of the (IME target) window which requests hiding IME. * @param displayId the id of the display the IME is on. - * @param statsToken the token tracking the current IME hide request or {@code null} otherwise. + * @param statsToken the token tracking the current IME request. */ public abstract void hideIme(IBinder imeTargetWindowToken, int displayId, - @Nullable ImeTracker.Token statsToken); + @NonNull ImeTracker.Token statsToken); /** * Tell window manager about a package that should be running with a restricted range of diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 7e06129832a8..ae5a5cb7316c 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -8258,12 +8258,17 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void showImePostLayout(IBinder imeTargetWindowToken, - @Nullable ImeTracker.Token statsToken) { + @NonNull ImeTracker.Token statsToken) { synchronized (mGlobalLock) { InputTarget imeTarget = getInputTargetFromWindowTokenLocked(imeTargetWindowToken); if (imeTarget == null) { + ImeTracker.forLogging().onFailed(statsToken, + ImeTracker.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET); return; } + ImeTracker.forLogging().onProgress(statsToken, + ImeTracker.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET); + Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0); final InsetsControlTarget controlTarget = imeTarget.getImeControlTarget(); imeTarget = controlTarget.getWindow(); @@ -8278,7 +8283,7 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void hideIme(IBinder imeTargetWindowToken, int displayId, - @Nullable ImeTracker.Token statsToken) { + @NonNull ImeTracker.Token statsToken) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.hideIme"); synchronized (mGlobalLock) { WindowState imeTarget = mWindowMap.get(imeTargetWindowToken); diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index d6fc01aeadd2..a7eb444ecb59 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -329,15 +329,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub deferred); wctApplied.meet(); if (needsSetReady) { - // TODO(b/294925498): Remove this once we have accurate ready - // tracking. - if (hasActivityLaunch(wct) && !mService.mRootWindowContainer - .allPausedActivitiesComplete()) { - // WCT is launching an activity, so we need to wait for its - // lifecycle events. - return; - } - nextTransition.setAllReady(); + setAllReadyIfNeeded(nextTransition, wct); } }); return nextTransition.getToken(); @@ -390,7 +382,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } } - private static boolean hasActivityLaunch(WindowContainerTransaction wct) { + private static boolean hasActivityLaunch(@NonNull WindowContainerTransaction wct) { for (int i = 0; i < wct.getHierarchyOps().size(); ++i) { if (wct.getHierarchyOps().get(i).getType() == HIERARCHY_OP_TYPE_LAUNCH_TASK) { return true; @@ -399,6 +391,46 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub return false; } + private boolean isCreatedTaskFragmentReady(@NonNull WindowContainerTransaction wct) { + for (int i = 0; i < wct.getHierarchyOps().size(); ++i) { + final WindowContainerTransaction.HierarchyOp op = wct.getHierarchyOps().get(i); + if (op.getType() != HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION + || op.getTaskFragmentOperation().getOpType() + != OP_TYPE_CREATE_TASK_FRAGMENT) { + continue; + } + final IBinder tfToken = op.getTaskFragmentOperation() + .getTaskFragmentCreationParams().getFragmentToken(); + final TaskFragment taskFragment = getTaskFragment(tfToken); + if (taskFragment != null && !taskFragment.isReadyToTransit()) { + return false; + } + } + return true; + } + + private void setAllReadyIfNeeded(@NonNull Transition transition, + @NonNull WindowContainerTransaction wct) { + // TODO(b/294925498): Remove this once we have accurate ready tracking. + if (hasActivityLaunch(wct) && !mService.mRootWindowContainer + .allPausedActivitiesComplete()) { + // WCT is launching an activity, so we need to wait for its + // lifecycle events. + return; + } + if (!isCreatedTaskFragmentReady(wct)) { + // When the organizer intercepts a #startActivity, it will create an empty TaskFragment + // for that specific incoming starting activity. We don't want to set all ready here, + // because we requires that #startActivity to be included in this transition, and NOT be + // in its own transition. + // TODO(b/232042367): explicitly ensure the #startActivity and this transaction are in + // the same transition instead of relying on this possible racing condition. + return; + } + + transition.setAllReady(); + } + @Override public int startLegacyTransition(int type, @NonNull RemoteAnimationAdapter adapter, @NonNull IWindowContainerTransactionCallback callback, @@ -529,7 +561,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } mTransitionController.requestStartTransition(transition, null /* startTask */, remoteTransition, null /* displayChange */); - transition.setAllReady(); + setAllReadyIfNeeded(transition, wct); }; mTransitionController.startCollectOrQueue(transition, doApply); } finally { diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java index 1c71a6287c79..1d225ba09bbd 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java @@ -30,7 +30,10 @@ import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SH import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME_IMPLICIT; import static org.junit.Assert.assertThrows; -import static org.mockito.Mockito.any; +import static org.mockito.AdditionalMatchers.and; +import static org.mockito.AdditionalMatchers.not; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; @@ -40,6 +43,7 @@ import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.view.Display; +import android.view.inputmethod.ImeTracker; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -56,7 +60,7 @@ import org.junit.runner.RunWith; * Test the behavior of {@link DefaultImeVisibilityApplier} when performing or applying the IME * visibility state. * - * Build/Install/Run: + * <p>Build/Install/Run: * atest FrameworksInputMethodSystemServerTests:DefaultImeVisibilityApplierTest */ @RunWith(AndroidJUnit4.class) @@ -75,7 +79,8 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe public void testPerformShowIme() throws Exception { synchronized (ImfLock.class) { mVisibilityApplier.performShowIme(new Binder() /* showInputToken */, - null /* statsToken */, 0 /* showFlags */, null, SHOW_SOFT_INPUT); + ImeTracker.Token.empty(), 0 /* showFlags */, null /* resultReceiver */, + SHOW_SOFT_INPUT); } verifyShowSoftInput(false, true, 0 /* showFlags */); } @@ -84,46 +89,66 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe public void testPerformHideIme() throws Exception { synchronized (ImfLock.class) { mVisibilityApplier.performHideIme(new Binder() /* hideInputToken */, - null /* statsToken */, null, HIDE_SOFT_INPUT); + ImeTracker.Token.empty(), null /* resultReceiver */, HIDE_SOFT_INPUT); } verifyHideSoftInput(false, true); } @Test public void testApplyImeVisibility_throwForInvalidState() { - assertThrows(IllegalArgumentException.class, - () -> mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_INVALID)); + assertThrows(IllegalArgumentException.class, () -> { + synchronized (ImfLock.class) { + mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(), + STATE_INVALID); + } + }); } @Test public void testApplyImeVisibility_showIme() { - mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_SHOW_IME); - verify(mMockWindowManagerInternal).showImePostLayout(eq(mWindowToken), any()); + final var statsToken = ImeTracker.Token.empty(); + synchronized (ImfLock.class) { + mVisibilityApplier.applyImeVisibility(mWindowToken, statsToken, STATE_SHOW_IME); + } + verify(mMockWindowManagerInternal).showImePostLayout(eq(mWindowToken), eq(statsToken)); } @Test public void testApplyImeVisibility_hideIme() { - mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_HIDE_IME); - verify(mMockWindowManagerInternal).hideIme(eq(mWindowToken), anyInt(), any()); + final var statsToken = ImeTracker.Token.empty(); + synchronized (ImfLock.class) { + mVisibilityApplier.applyImeVisibility(mWindowToken, statsToken, STATE_HIDE_IME); + } + verify(mMockWindowManagerInternal).hideIme(eq(mWindowToken), anyInt() /* displayId */, + eq(statsToken)); } @Test public void testApplyImeVisibility_hideImeExplicit() throws Exception { mInputMethodManagerService.mImeWindowVis = IME_ACTIVE; - mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_HIDE_IME_EXPLICIT); + synchronized (ImfLock.class) { + mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(), + STATE_HIDE_IME_EXPLICIT); + } verifyHideSoftInput(true, true); } @Test public void testApplyImeVisibility_hideNotAlways() throws Exception { mInputMethodManagerService.mImeWindowVis = IME_ACTIVE; - mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_HIDE_IME_NOT_ALWAYS); + synchronized (ImfLock.class) { + mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(), + STATE_HIDE_IME_NOT_ALWAYS); + } verifyHideSoftInput(true, true); } @Test public void testApplyImeVisibility_showImeImplicit() throws Exception { - mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_SHOW_IME_IMPLICIT); + synchronized (ImfLock.class) { + mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(), + STATE_SHOW_IME_IMPLICIT); + } verifyShowSoftInput(true, true, 0 /* showFlags */); } @@ -135,21 +160,21 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe mInputMethodManagerService.setAttachedClientForTesting(null); startInputOrWindowGainedFocus(mWindowToken, SOFT_INPUT_STATE_ALWAYS_VISIBLE); + final var statsToken = ImeTracker.Token.empty(); synchronized (ImfLock.class) { final int displayIdToShowIme = mInputMethodManagerService.getDisplayIdToShowImeLocked(); // Verify hideIme will apply the expected displayId when the default IME // visibility applier app STATE_HIDE_IME. - mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_HIDE_IME); + mVisibilityApplier.applyImeVisibility(mWindowToken, statsToken, STATE_HIDE_IME); verify(mInputMethodManagerService.mWindowManagerInternal).hideIme( - eq(mWindowToken), eq(displayIdToShowIme), eq(null)); + eq(mWindowToken), eq(displayIdToShowIme), eq(statsToken)); } } @Test public void testShowImeScreenshot() { synchronized (ImfLock.class) { - mVisibilityApplier.showImeScreenshot(mWindowToken, Display.DEFAULT_DISPLAY, - null /* statsToken */); + mVisibilityApplier.showImeScreenshot(mWindowToken, Display.DEFAULT_DISPLAY); } verify(mMockImeTargetVisibilityPolicy).showImeScreenshot(eq(mWindowToken), @@ -174,17 +199,20 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe synchronized (ImfLock.class) { // Simulate the system hides the IME when switching IME services in different users. // (e.g. unbinding the IME from the current user to the profile user) + final var statsToken = ImeTracker.Token.empty(); final int displayIdToShowIme = mInputMethodManagerService.getDisplayIdToShowImeLocked(); - mInputMethodManagerService.hideCurrentInputLocked(mWindowToken, null, 0, null, + mInputMethodManagerService.hideCurrentInputLocked(mWindowToken, + statsToken, 0 /* flags */, null /* resultReceiver */, HIDE_SWITCH_USER); mInputMethodManagerService.onUnbindCurrentMethodByReset(); // Expects applyImeVisibility() -> hideIme() will be called to notify WM for syncing // the IME hidden state. - verify(mVisibilityApplier).applyImeVisibility(eq(mWindowToken), any(), - eq(STATE_HIDE_IME)); + // The unbind will cancel the previous stats token, and create a new one internally. + verify(mVisibilityApplier).applyImeVisibility( + eq(mWindowToken), any(), eq(STATE_HIDE_IME)); verify(mInputMethodManagerService.mWindowManagerInternal).hideIme( - eq(mWindowToken), eq(displayIdToShowIme), eq(null)); + eq(mWindowToken), eq(displayIdToShowIme), and(not(eq(statsToken)), notNull())); } } diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java index fae5f86e4007..a22cacbcb5df 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java @@ -39,9 +39,12 @@ import static com.android.server.inputmethod.InputMethodManagerService.ImeDispla import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.notNull; + import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; +import android.view.inputmethod.ImeTracker; import android.view.inputmethod.InputMethodManager; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -58,7 +61,7 @@ import org.mockito.ArgumentCaptor; * Test the behavior of {@link ImeVisibilityStateComputer} and {@link ImeVisibilityApplier} when * requesting the IME visibility. * - * Build/Install/Run: + * <p> Build/Install/Run: * atest FrameworksInputMethodSystemServerTests:ImeVisibilityStateComputerTest */ @RunWith(AndroidJUnit4.class) @@ -91,7 +94,8 @@ public class ImeVisibilityStateComputerTest extends InputMethodManagerServiceTes @Test public void testRequestImeVisibility_showImplicit() { initImeTargetWindowState(mWindowToken); - boolean res = mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT); + boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(), + InputMethodManager.SHOW_IMPLICIT); mComputer.requestImeVisibility(mWindowToken, res); final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); @@ -106,7 +110,7 @@ public class ImeVisibilityStateComputerTest extends InputMethodManagerServiceTes @Test public void testRequestImeVisibility_showExplicit() { initImeTargetWindowState(mWindowToken); - boolean res = mComputer.onImeShowFlags(null, 0 /* showFlags */); + boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(), 0 /* showFlags */); mComputer.requestImeVisibility(mWindowToken, res); final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); @@ -125,7 +129,7 @@ public class ImeVisibilityStateComputerTest extends InputMethodManagerServiceTes @Test public void testRequestImeVisibility_showExplicit_thenShowImplicit() { initImeTargetWindowState(mWindowToken); - mComputer.onImeShowFlags(null, 0 /* showFlags */); + mComputer.onImeShowFlags(ImeTracker.Token.empty(), 0 /* showFlags */); assertThat(mComputer.mRequestedShowExplicitly).isTrue(); mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT); @@ -139,10 +143,10 @@ public class ImeVisibilityStateComputerTest extends InputMethodManagerServiceTes @Test public void testRequestImeVisibility_showForced_thenShowExplicit() { initImeTargetWindowState(mWindowToken); - mComputer.onImeShowFlags(null, InputMethodManager.SHOW_FORCED); + mComputer.onImeShowFlags(ImeTracker.Token.empty(), InputMethodManager.SHOW_FORCED); assertThat(mComputer.mShowForced).isTrue(); - mComputer.onImeShowFlags(null, 0 /* showFlags */); + mComputer.onImeShowFlags(ImeTracker.Token.empty(), 0 /* showFlags */); assertThat(mComputer.mShowForced).isTrue(); } @@ -152,7 +156,8 @@ public class ImeVisibilityStateComputerTest extends InputMethodManagerServiceTes mComputer.getImePolicy().setA11yRequestNoSoftKeyboard(SHOW_MODE_HIDDEN); initImeTargetWindowState(mWindowToken); - boolean res = mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT); + boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(), + InputMethodManager.SHOW_IMPLICIT); mComputer.requestImeVisibility(mWindowToken, res); final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); @@ -170,7 +175,8 @@ public class ImeVisibilityStateComputerTest extends InputMethodManagerServiceTes mComputer.getImePolicy().setImeHiddenByDisplayPolicy(true); initImeTargetWindowState(mWindowToken); - boolean res = mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT); + boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(), + InputMethodManager.SHOW_IMPLICIT); mComputer.requestImeVisibility(mWindowToken, res); final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); @@ -188,7 +194,8 @@ public class ImeVisibilityStateComputerTest extends InputMethodManagerServiceTes mComputer.setInputShown(true); initImeTargetWindowState(mWindowToken); - assertThat(mComputer.canHideIme(null, InputMethodManager.HIDE_NOT_ALWAYS)).isTrue(); + assertThat(mComputer.canHideIme(ImeTracker.Token.empty(), + InputMethodManager.HIDE_NOT_ALWAYS)).isTrue(); mComputer.requestImeVisibility(mWindowToken, false); final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); @@ -281,7 +288,7 @@ public class ImeVisibilityStateComputerTest extends InputMethodManagerServiceTes final ArgumentCaptor<ImeVisibilityResult> resultCaptor = ArgumentCaptor.forClass( ImeVisibilityResult.class); verify(mInputMethodManagerService).onApplyImeVisibilityFromComputer(targetCaptor.capture(), - resultCaptor.capture()); + notNull() /* statsToken */, resultCaptor.capture()); final IBinder imeInputTarget = targetCaptor.getValue(); final ImeVisibilityResult result = resultCaptor.getValue(); diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java index a1be00aab340..f4d95afaacf4 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java @@ -27,6 +27,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -277,8 +278,9 @@ public class InputMethodManagerServiceTestBase { .setCurrentMethodVisible(); } verify(mMockInputMethod, times(showSoftInput ? 1 : 0)) - .showSoftInput(any(), any(), - showFlags != NO_VERIFY_SHOW_FLAGS ? eq(showFlags) : anyInt(), any()); + .showSoftInput(any() /* showInputToken */ , notNull() /* statsToken */, + showFlags != NO_VERIFY_SHOW_FLAGS ? eq(showFlags) : anyInt() /* flags*/, + any() /* resultReceiver */); } protected void verifyHideSoftInput(boolean setNotVisible, boolean hideSoftInput) @@ -288,6 +290,7 @@ public class InputMethodManagerServiceTestBase { .setCurrentMethodNotVisible(); } verify(mMockInputMethod, times(hideSoftInput ? 1 : 0)) - .hideSoftInput(any(), any(), anyInt(), any()); + .hideSoftInput(any() /* hideInputToken */, notNull() /* statsToken */, + anyInt() /* flags */, any() /* resultReceiver */); } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index fcee70fd0702..e9315c8ed8e6 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -1727,6 +1727,27 @@ public final class DisplayPowerControllerTest { /* ignoreAnimationLimits= */ anyBoolean()); } + @Test + public void testDefaultDozeBrightness() { + float brightness = 0.121f; + when(mPowerManagerMock.getBrightnessConstraint( + PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE)).thenReturn(brightness); + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); + when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f); + when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness( + any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT); + when(mHolder.hbmController.getCurrentBrightnessMax()) + .thenReturn(PowerManager.BRIGHTNESS_MAX); + + DisplayPowerRequest dpr = new DisplayPowerRequest(); + dpr.policy = DisplayPowerRequest.POLICY_DOZE; + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + verify(mHolder.animator).animateTo(eq(brightness), /* linearSecondTarget= */ anyFloat(), + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); + } + /** * Creates a mock and registers it to {@link LocalServices}. */ diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java index 886780655de2..ba462e363b4e 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java @@ -215,7 +215,7 @@ public class AutomaticBrightnessStrategyTest { mAutomaticBrightnessStrategy.setUseAutoBrightness(true); int targetDisplayState = Display.STATE_DOZE; boolean allowAutoBrightnessWhileDozing = true; - int brightnessReason = BrightnessReason.REASON_DOZE; + int brightnessReason = BrightnessReason.REASON_UNKNOWN; int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE; float lastUserSetBrightness = 0.2f; boolean userSetBrightnessChanged = true; diff --git a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java index 20bb549301aa..faa6d97ce0e3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java @@ -27,6 +27,7 @@ import static org.junit.Assert.assertTrue; import android.graphics.PixelFormat; import android.platform.test.annotations.Presubmit; import android.view.InsetsSource; +import android.view.inputmethod.ImeTracker; import androidx.test.filters.SmallTest; @@ -34,6 +35,12 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +/** + * Tests for the {@link ImeInsetsSourceProvider} class. + * + * <p> Build/Install/Run: + * atest WmTests:ImeInsetsSourceProviderTest + */ @SmallTest @Presubmit @RunWith(WindowTestRunner.class) @@ -56,7 +63,7 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase { mDisplayContent.setImeControlTarget(popup); mDisplayContent.setImeLayeringTarget(appWin); popup.mAttrs.format = PixelFormat.TRANSPARENT; - mImeProvider.scheduleShowImePostLayout(appWin, null /* statsToken */); + mImeProvider.scheduleShowImePostLayout(appWin, ImeTracker.Token.empty()); assertTrue(mImeProvider.isReadyToShowIme()); } @@ -65,7 +72,7 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase { WindowState target = createWindow(null, TYPE_APPLICATION, "app"); mDisplayContent.setImeLayeringTarget(target); mDisplayContent.updateImeInputAndControlTarget(target); - mImeProvider.scheduleShowImePostLayout(target, null /* statsToken */); + mImeProvider.scheduleShowImePostLayout(target, ImeTracker.Token.empty()); assertTrue(mImeProvider.isReadyToShowIme()); } @@ -79,7 +86,7 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase { mDisplayContent.setImeLayeringTarget(target); mDisplayContent.setImeControlTarget(target); - mImeProvider.scheduleShowImePostLayout(target, null /* statsToken */); + mImeProvider.scheduleShowImePostLayout(target, ImeTracker.Token.empty()); assertFalse(mImeProvider.isImeShowing()); mImeProvider.checkShowImePostLayout(); assertTrue(mImeProvider.isImeShowing()); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index cd3ce9192509..c8ad4bd47880 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -104,6 +104,7 @@ import android.view.SurfaceControl; import android.view.View; import android.view.WindowInsets; import android.view.WindowManager; +import android.view.inputmethod.ImeTracker; import android.window.ClientWindowFrames; import android.window.ITaskFragmentOrganizer; import android.window.TaskFragmentOrganizer; @@ -126,7 +127,7 @@ import java.util.List; /** * Tests for the {@link WindowState} class. * - * Build/Install/Run: + * <p> Build/Install/Run: * atest WmTests:WindowStateTests */ @SmallTest @@ -1099,7 +1100,7 @@ public class WindowStateTests extends WindowTestsBase { mDisplayContent.setImeInputTarget(app); app.setRequestedVisibleTypes(ime(), ime()); assertTrue(mDisplayContent.shouldImeAttachedToApp()); - controller.getImeSourceProvider().scheduleShowImePostLayout(app, null /* statsToken */); + controller.getImeSourceProvider().scheduleShowImePostLayout(app, ImeTracker.Token.empty()); controller.getImeSourceProvider().getSource().setVisible(true); controller.updateAboveInsetsState(false); @@ -1137,7 +1138,7 @@ public class WindowStateTests extends WindowTestsBase { mDisplayContent.setImeInputTarget(app); app.setRequestedVisibleTypes(ime(), ime()); assertTrue(mDisplayContent.shouldImeAttachedToApp()); - controller.getImeSourceProvider().scheduleShowImePostLayout(app, null /* statsToken */); + controller.getImeSourceProvider().scheduleShowImePostLayout(app, ImeTracker.Token.empty()); controller.getImeSourceProvider().getSource().setVisible(true); controller.updateAboveInsetsState(false); |