diff options
229 files changed, 5831 insertions, 1547 deletions
diff --git a/Android.bp b/Android.bp index 7f4871f5032a..af205d8f0646 100644 --- a/Android.bp +++ b/Android.bp @@ -261,7 +261,6 @@ java_library { "devicepolicyprotosnano", "ImmutabilityAnnotation", - "com.android.sysprop.init", "com.android.sysprop.localization", "PlatformProperties", ], diff --git a/core/api/current.txt b/core/api/current.txt index 7f2b00485417..c42d1ffcba6d 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8718,6 +8718,30 @@ package android.app.admin { } +package android.app.appfunctions { + + @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class AppFunctionManager { + } + + @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class ExecuteAppFunctionRequest implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.os.Bundle getExtras(); + method @NonNull public String getFunctionIdentifier(); + method @NonNull public android.app.appsearch.GenericDocument getParameters(); + method @NonNull public String getTargetPackageName(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.appfunctions.ExecuteAppFunctionRequest> CREATOR; + } + + public static final class ExecuteAppFunctionRequest.Builder { + ctor public ExecuteAppFunctionRequest.Builder(@NonNull String, @NonNull String); + method @NonNull public android.app.appfunctions.ExecuteAppFunctionRequest build(); + method @NonNull public android.app.appfunctions.ExecuteAppFunctionRequest.Builder setExtras(@NonNull android.os.Bundle); + method @NonNull public android.app.appfunctions.ExecuteAppFunctionRequest.Builder setParameters(@NonNull android.app.appsearch.GenericDocument); + } + +} + package android.app.assist { public class AssistContent implements android.os.Parcelable { diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 65acd49d44fa..91aa225039a4 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -1587,6 +1587,16 @@ public class ActivityOptions extends ComponentOptions { } } + /** @hide */ + public static boolean hasLaunchTargetContainer(ActivityOptions options) { + return options.getLaunchDisplayId() != INVALID_DISPLAY + || options.getLaunchTaskDisplayArea() != null + || options.getLaunchTaskDisplayAreaFeatureId() != FEATURE_UNDEFINED + || options.getLaunchRootTask() != null + || options.getLaunchTaskId() != -1 + || options.getLaunchTaskFragmentToken() != null; + } + /** * Gets whether the activity is to be launched into LockTask mode. * @return {@code true} if the activity is to be launched into LockTask mode. diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 7e2a580bec73..90fba2962a23 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1160,7 +1160,7 @@ class ContextImpl extends Context { } mMainThread.getInstrumentation().execStartActivity( getOuterContext(), mMainThread.getApplicationThread(), null, - (Activity) null, intent, -1, options); + (Activity) null, intent, -1, applyLaunchDisplayIfNeeded(options)); } /** @hide */ @@ -1170,8 +1170,8 @@ class ContextImpl extends Context { ActivityTaskManager.getService().startActivityAsUser( mMainThread.getApplicationThread(), getOpPackageName(), getAttributionTag(), intent, intent.resolveTypeIfNeeded(getContentResolver()), - null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, options, - user.getIdentifier()); + null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, + applyLaunchDisplayIfNeeded(options), user.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1194,7 +1194,8 @@ class ContextImpl extends Context { } return mMainThread.getInstrumentation().execStartActivitiesAsUser( getOuterContext(), mMainThread.getApplicationThread(), null, - (Activity) null, intents, options, userHandle.getIdentifier()); + (Activity) null, intents, applyLaunchDisplayIfNeeded(options), + userHandle.getIdentifier()); } @Override @@ -1208,7 +1209,26 @@ class ContextImpl extends Context { } mMainThread.getInstrumentation().execStartActivities( getOuterContext(), mMainThread.getApplicationThread(), null, - (Activity) null, intents, options); + (Activity) null, intents, applyLaunchDisplayIfNeeded(options)); + } + + private Bundle applyLaunchDisplayIfNeeded(@Nullable Bundle options) { + if (!isAssociatedWithDisplay()) { + // return if this Context has no associated display. + return options; + } + + final ActivityOptions activityOptions; + if (options != null) { + activityOptions = ActivityOptions.fromBundle(options); + if (ActivityOptions.hasLaunchTargetContainer(activityOptions)) { + // return if the options already has launching target. + return options; + } + } else { + activityOptions = ActivityOptions.makeBasic(); + } + return activityOptions.setLaunchDisplayId(getAssociatedDisplayId()).toBundle(); } @Override diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java index a01e373c5e83..bf21549198f3 100644 --- a/core/java/android/app/appfunctions/AppFunctionManager.java +++ b/core/java/android/app/appfunctions/AppFunctionManager.java @@ -16,6 +16,9 @@ package android.app.appfunctions; +import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER; + +import android.annotation.FlaggedApi; import android.annotation.SystemService; import android.content.Context; @@ -25,8 +28,8 @@ import android.content.Context; * <p>App function is a specific piece of functionality that an app offers to the system. These * functionalities can be integrated into various system features. * - * @hide */ +@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER) @SystemService(Context.APP_FUNCTION_SERVICE) public final class AppFunctionManager { private final IAppFunctionManager mService; diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.aidl b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.aidl new file mode 100644 index 000000000000..a0b889e1bb68 --- /dev/null +++ b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.aidl @@ -0,0 +1,21 @@ +/* + * 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.app.appfunctions; + +import android.app.appfunctions.ExecuteAppFunctionRequest; + +parcelable ExecuteAppFunctionRequest;
\ No newline at end of file diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java new file mode 100644 index 000000000000..a50425e4db91 --- /dev/null +++ b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java @@ -0,0 +1,189 @@ +/* + * 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.app.appfunctions; + + +import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.app.appsearch.GenericDocument; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * A request to execute an app function. + */ +@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER) +public final class ExecuteAppFunctionRequest implements Parcelable { + @NonNull + public static final Creator<ExecuteAppFunctionRequest> CREATOR = + new Creator<>() { + @Override + public ExecuteAppFunctionRequest createFromParcel(Parcel parcel) { + String targetPackageName = parcel.readString8(); + String functionIdentifier = parcel.readString8(); + GenericDocument parameters; + parameters = GenericDocument.createFromParcel(parcel); + Bundle extras = parcel.readBundle(Bundle.class.getClassLoader()); + return new ExecuteAppFunctionRequest( + targetPackageName, functionIdentifier, extras, parameters); + } + + @Override + public ExecuteAppFunctionRequest[] newArray(int size) { + return new ExecuteAppFunctionRequest[size]; + } + }; + /** + * Returns the package name of the app that hosts the function. + */ + @NonNull + private final String mTargetPackageName; + /** + * Returns the unique string identifier of the app function to be executed. + * TODO(b/357551503): Document how callers can get the available function identifiers. + */ + @NonNull + private final String mFunctionIdentifier; + /** + * Returns additional metadata relevant to this function execution request. + */ + @NonNull + private final Bundle mExtras; + /** + * Returns the parameters required to invoke this function. Within this [GenericDocument], + * the property names are the names of the function parameters and the property values are the + * values of those parameters. + * + * <p>The document may have missing parameters. Developers are advised to implement defensive + * handling measures. + * + * TODO(b/357551503): Document how function parameters can be obtained for function execution + */ + @NonNull + private final GenericDocument mParameters; + + private ExecuteAppFunctionRequest( + @NonNull String targetPackageName, + @NonNull String functionIdentifier, + @NonNull Bundle extras, + @NonNull GenericDocument parameters) { + mTargetPackageName = Objects.requireNonNull(targetPackageName); + mFunctionIdentifier = Objects.requireNonNull(functionIdentifier); + mExtras = Objects.requireNonNull(extras); + mParameters = Objects.requireNonNull(parameters); + } + + /** + * Returns the package name of the app that hosts the function. + */ + @NonNull + public String getTargetPackageName() { + return mTargetPackageName; + } + + /** + * Returns the unique string identifier of the app function to be executed. + */ + @NonNull + public String getFunctionIdentifier() { + return mFunctionIdentifier; + } + + /** + * Returns the function parameters. The key is the parameter name, and the value is the + * parameter value. + * <p> + * The bundle may have missing parameters. Developers are advised to implement defensive + * handling measures. + */ + @NonNull + public GenericDocument getParameters() { + return mParameters; + } + + /** + * Returns the additional data relevant to this function execution. + */ + @NonNull + public Bundle getExtras() { + return mExtras; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString8(mTargetPackageName); + dest.writeString8(mFunctionIdentifier); + mParameters.writeToParcel(dest, flags); + dest.writeBundle(mExtras); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Builder for {@link ExecuteAppFunctionRequest}. + */ + public static final class Builder { + @NonNull + private final String mTargetPackageName; + @NonNull + private final String mFunctionIdentifier; + @NonNull + private Bundle mExtras = Bundle.EMPTY; + @NonNull + private GenericDocument mParameters = new GenericDocument.Builder<>("", "", "").build(); + + public Builder(@NonNull String targetPackageName, @NonNull String functionIdentifier) { + mTargetPackageName = Objects.requireNonNull(targetPackageName); + mFunctionIdentifier = Objects.requireNonNull(functionIdentifier); + } + + /** + * Sets the additional data relevant to this function execution. + */ + @NonNull + public Builder setExtras(@NonNull Bundle extras) { + mExtras = Objects.requireNonNull(extras); + return this; + } + + /** + * Sets the function parameters. + */ + @NonNull + public Builder setParameters(@NonNull GenericDocument parameters) { + mParameters = Objects.requireNonNull(parameters); + return this; + } + + /** + * Builds the {@link ExecuteAppFunctionRequest}. + */ + @NonNull + public ExecuteAppFunctionRequest build() { + return new ExecuteAppFunctionRequest( + mTargetPackageName, mFunctionIdentifier, mExtras, mParameters); + } + } +} diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java index 61d87026b6e9..8975191b54c1 100644 --- a/core/java/android/hardware/biometrics/BiometricConstants.java +++ b/core/java/android/hardware/biometrics/BiometricConstants.java @@ -162,6 +162,13 @@ public interface BiometricConstants { * @hide */ int BIOMETRIC_ERROR_POWER_PRESSED = 19; + + /** + * Mandatory biometrics is not in effect. + * @hide + */ + int BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE = 20; + /** * This constant is only used by SystemUI. It notifies SystemUI that authentication was paused * because the authentication attempt was unsuccessful. diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index de1cac47ff46..9bc46b9f382a 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -80,6 +80,20 @@ public class BiometricManager { BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT; /** + * Lockout error. + * @hide + */ + public static final int BIOMETRIC_ERROR_LOCKOUT = + BiometricConstants.BIOMETRIC_ERROR_LOCKOUT; + + /** + * Mandatory biometrics is not effective. + * @hide + */ + public static final int BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE = + BiometricConstants.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE; + + /** * A security vulnerability has been discovered and the sensor is unavailable until a * security update has addressed this issue. This error can be received if for example, * authentication was requested with {@link Authenticators#BIOMETRIC_STRONG}, but the @@ -113,7 +127,9 @@ public class BiometricManager { BIOMETRIC_ERROR_HW_UNAVAILABLE, BIOMETRIC_ERROR_NONE_ENROLLED, BIOMETRIC_ERROR_NO_HARDWARE, - BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED}) + BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED, + BIOMETRIC_ERROR_LOCKOUT, + BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE}) @Retention(RetentionPolicy.SOURCE) public @interface BiometricError {} diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java index da863e58a882..589622710f3e 100644 --- a/core/java/android/os/VibrationAttributes.java +++ b/core/java/android/os/VibrationAttributes.java @@ -151,31 +151,6 @@ public final class VibrationAttributes implements Parcelable { */ public static final int USAGE_MEDIA = 0x10 | USAGE_CLASS_MEDIA; - /** @hide */ - @IntDef(prefix = { "CATEGORY_" }, value = { - CATEGORY_UNKNOWN, - CATEGORY_KEYBOARD, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface Category {} - - /** - * Category value when the vibration category is unknown. - * - * @hide - */ - public static final int CATEGORY_UNKNOWN = 0x0; - - /** - * Category value for keyboard vibrations. - * - * <p>Most typical keyboard vibrations are haptic feedback for virtual keyboard key - * press/release, for example. - * - * @hide - */ - public static final int CATEGORY_KEYBOARD = 1; - /** * @hide */ @@ -252,14 +227,12 @@ public final class VibrationAttributes implements Parcelable { private final int mUsage; private final int mFlags; private final int mOriginalAudioUsage; - private final int mCategory; private VibrationAttributes(@Usage int usage, @AudioAttributes.AttributeUsage int audioUsage, - @Flag int flags, @Category int category) { + @Flag int flags) { mUsage = usage; mOriginalAudioUsage = audioUsage; mFlags = flags & FLAG_ALL_SUPPORTED; - mCategory = category; } /** @@ -297,20 +270,6 @@ public final class VibrationAttributes implements Parcelable { } /** - * Return the vibration category. - * - * <p>Vibration categories describe the source of the vibration, and it can be combined with - * the vibration usage to best match to a user setting, e.g. a vibration with usage touch and - * category keyboard can be used to control keyboard haptic feedback independently. - * - * @hide - */ - @Category - public int getCategory() { - return mCategory; - } - - /** * Check whether a flag is set * @return true if a flag is set and false otherwise */ @@ -362,14 +321,12 @@ public final class VibrationAttributes implements Parcelable { dest.writeInt(mUsage); dest.writeInt(mOriginalAudioUsage); dest.writeInt(mFlags); - dest.writeInt(mCategory); } private VibrationAttributes(Parcel src) { mUsage = src.readInt(); mOriginalAudioUsage = src.readInt(); mFlags = src.readInt(); - mCategory = src.readInt(); } public static final @NonNull Parcelable.Creator<VibrationAttributes> @@ -392,12 +349,12 @@ public final class VibrationAttributes implements Parcelable { } VibrationAttributes rhs = (VibrationAttributes) o; return mUsage == rhs.mUsage && mOriginalAudioUsage == rhs.mOriginalAudioUsage - && mFlags == rhs.mFlags && mCategory == rhs.mCategory; + && mFlags == rhs.mFlags; } @Override public int hashCode() { - return Objects.hash(mUsage, mOriginalAudioUsage, mFlags, mCategory); + return Objects.hash(mUsage, mOriginalAudioUsage, mFlags); } @Override @@ -405,7 +362,6 @@ public final class VibrationAttributes implements Parcelable { return "VibrationAttributes{" + "mUsage=" + usageToString() + ", mAudioUsage= " + AudioAttributes.usageToString(mOriginalAudioUsage) - + ", mCategory=" + categoryToString() + ", mFlags=" + mFlags + '}'; } @@ -445,23 +401,6 @@ public final class VibrationAttributes implements Parcelable { } } - /** @hide */ - public String categoryToString() { - return categoryToString(mCategory); - } - - /** @hide */ - public static String categoryToString(@Category int category) { - switch (category) { - case CATEGORY_UNKNOWN: - return "UNKNOWN"; - case CATEGORY_KEYBOARD: - return "KEYBOARD"; - default: - return "unknown category " + category; - } - } - /** * Builder class for {@link VibrationAttributes} objects. * By default, all information is set to UNKNOWN. @@ -471,7 +410,6 @@ public final class VibrationAttributes implements Parcelable { private int mUsage = USAGE_UNKNOWN; private int mOriginalAudioUsage = AudioAttributes.USAGE_UNKNOWN; private int mFlags = 0x0; - private int mCategory = CATEGORY_UNKNOWN; /** * Constructs a new Builder with the defaults. @@ -487,7 +425,6 @@ public final class VibrationAttributes implements Parcelable { mUsage = vib.mUsage; mOriginalAudioUsage = vib.mOriginalAudioUsage; mFlags = vib.mFlags; - mCategory = vib.mCategory; } } @@ -554,7 +491,7 @@ public final class VibrationAttributes implements Parcelable { */ public @NonNull VibrationAttributes build() { VibrationAttributes ans = new VibrationAttributes( - mUsage, mOriginalAudioUsage, mFlags, mCategory); + mUsage, mOriginalAudioUsage, mFlags); return ans; } @@ -570,19 +507,6 @@ public final class VibrationAttributes implements Parcelable { } /** - * Sets the attribute describing the category of the corresponding vibration. - * - * @param category The category for the vibration - * @return the same Builder instance. - * - * @hide - */ - public @NonNull Builder setCategory(@Category int category) { - mCategory = category; - return this; - } - - /** * Sets only the flags specified in the bitmask, leaving the other supported flag values * unchanged in the builder. * diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index 3fe063df0e1b..4c4aa6cc4e68 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -223,3 +223,14 @@ flag { description: "Show access entry of location bypass permission in the Privacy Dashboard" bug: "325536053" } + +flag { + name: "dont_remove_existing_uid_states" + is_fixed_read_only: true + namespace: "permissions" + description: "Double check if the uid still exists before attempting to remove its appops state" + bug: "353474742" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/text/Selection.java b/core/java/android/text/Selection.java index 6a54d2378f6c..711578c1482f 100644 --- a/core/java/android/text/Selection.java +++ b/core/java/android/text/Selection.java @@ -350,7 +350,7 @@ public class Selection { private static final char PARAGRAPH_SEPARATOR = '\n'; /** - * Move the cusrot to the closest paragraph start offset. + * Move the cursor to the closest paragraph start offset. * * @param text the spannable text * @param layout layout to be used for drawing. diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig index 5c5a2f687d3b..1c3d73824e7e 100644 --- a/core/java/android/text/flags/flags.aconfig +++ b/core/java/android/text/flags/flags.aconfig @@ -276,4 +276,14 @@ flag { metadata { purpose: PURPOSE_BUGFIX } -}
\ No newline at end of file +} + +flag { + name: "context_menu_hide_unavailable_items" + namespace: "text" + description: "Hide rather than disable unavailable Editor context menu items." + bug: "345709107" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 42d66ce6bf1b..f7745d14188e 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -325,17 +325,62 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private String mTag = TAG; - private final ISurfaceControlViewHostParent mSurfaceControlViewHostParent = - new ISurfaceControlViewHostParent.Stub() { + private static class SurfaceControlViewHostParent extends ISurfaceControlViewHostParent.Stub { + + /** + * mSurfaceView is set in {@link #attach} and cleared in {@link #detach} to prevent + * temporary memory leaks. The remote process's ISurfaceControlViewHostParent binder + * reference extends this object's lifetime. If mSurfaceView is not cleared in + * {@link #detach}, then the SurfaceView and anything it references will not be promptly + * garbage collected. + */ + @Nullable + private SurfaceView mSurfaceView; + + void attach(SurfaceView sv) { + synchronized (this) { + try { + sv.mSurfacePackage.getRemoteInterface().attachParentInterface(this); + mSurfaceView = sv; + } catch (RemoteException e) { + Log.d(TAG, "Failed to attach parent interface to SCVH. Likely SCVH is alraedy " + + "dead."); + } + } + } + + void detach() { + synchronized (this) { + if (mSurfaceView == null) { + return; + } + try { + mSurfaceView.mSurfacePackage.getRemoteInterface().attachParentInterface(null); + } catch (RemoteException e) { + Log.d(TAG, "Failed to remove parent interface from SCVH. Likely SCVH is " + + "already dead"); + } + mSurfaceView = null; + } + } + @Override public void updateParams(WindowManager.LayoutParams[] childAttrs) { - mEmbeddedWindowParams.clear(); - mEmbeddedWindowParams.addAll(Arrays.asList(childAttrs)); + SurfaceView sv; + synchronized (this) { + sv = mSurfaceView; + } + if (sv == null) { + return; + } + + sv.mEmbeddedWindowParams.clear(); + sv.mEmbeddedWindowParams.addAll(Arrays.asList(childAttrs)); - if (isAttachedToWindow()) { - runOnUiThread(() -> { - if (mParent != null) { - mParent.recomputeViewAttributes(SurfaceView.this); + if (sv.isAttachedToWindow()) { + sv.runOnUiThread(() -> { + if (sv.mParent != null) { + sv.mParent.recomputeViewAttributes(sv); } }); } @@ -343,34 +388,45 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall @Override public void forwardBackKeyToParent(@NonNull KeyEvent keyEvent) { - runOnUiThread(() -> { - if (!isAttachedToWindow() || keyEvent.getKeyCode() != KeyEvent.KEYCODE_BACK) { - return; - } - final ViewRootImpl vri = getViewRootImpl(); - if (vri == null) { - return; - } - final InputManager inputManager = mContext.getSystemService(InputManager.class); - if (inputManager == null) { - return; - } - // Check that the event was created recently. - final long timeDiff = SystemClock.uptimeMillis() - keyEvent.getEventTime(); - if (timeDiff > FORWARD_BACK_KEY_TOLERANCE_MS) { - Log.e(TAG, "Ignore the input event that exceed the tolerance time, " - + "exceed " + timeDiff + "ms"); - return; - } - if (inputManager.verifyInputEvent(keyEvent) == null) { - Log.e(TAG, "Received invalid input event"); - return; - } - vri.enqueueInputEvent(keyEvent, null /* receiver */, 0 /* flags */, - true /* processImmediately */); - }); + SurfaceView sv; + synchronized (this) { + sv = mSurfaceView; + } + if (sv == null) { + return; + } + + sv.runOnUiThread(() -> { + if (!sv.isAttachedToWindow() || keyEvent.getKeyCode() != KeyEvent.KEYCODE_BACK) { + return; + } + final ViewRootImpl vri = sv.getViewRootImpl(); + if (vri == null) { + return; + } + final InputManager inputManager = sv.mContext.getSystemService(InputManager.class); + if (inputManager == null) { + return; + } + // Check that the event was created recently. + final long timeDiff = SystemClock.uptimeMillis() - keyEvent.getEventTime(); + if (timeDiff > FORWARD_BACK_KEY_TOLERANCE_MS) { + Log.e(TAG, "Ignore the input event that exceed the tolerance time, " + + "exceed " + timeDiff + "ms"); + return; + } + if (inputManager.verifyInputEvent(keyEvent) == null) { + Log.e(TAG, "Received invalid input event"); + return; + } + vri.enqueueInputEvent(keyEvent, null /* receiver */, 0 /* flags */, + true /* processImmediately */); + }); } - }; + } + + private final SurfaceControlViewHostParent mSurfaceControlViewHostParent = + new SurfaceControlViewHostParent(); private final boolean mRtDrivenClipping = Flags.clipSurfaceviews(); @@ -930,13 +986,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } if (mSurfacePackage != null) { - try { - mSurfacePackage.getRemoteInterface().attachParentInterface(null); - mEmbeddedWindowParams.clear(); - } catch (RemoteException e) { - Log.d(TAG, "Failed to remove parent interface from SCVH. Likely SCVH is " - + "already dead"); - } + mSurfaceControlViewHostParent.detach(); + mEmbeddedWindowParams.clear(); if (releaseSurfacePackage) { mSurfacePackage.release(); mSurfacePackage = null; @@ -2067,12 +2118,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall applyTransactionOnVriDraw(transaction); } mSurfacePackage = p; - try { - mSurfacePackage.getRemoteInterface().attachParentInterface( - mSurfaceControlViewHostParent); - } catch (RemoteException e) { - Log.d(TAG, "Failed to attach parent interface to SCVH. Likely SCVH is already dead."); - } + mSurfaceControlViewHostParent.attach(this); if (isFocused()) { requestEmbeddedFocus(true); diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index 33610a09b7c8..c7e1fba66d7f 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -396,6 +396,9 @@ public class InteractionJankMonitor { int cujType = conf.mCujType; if (!shouldMonitor()) { return false; + } else if (!conf.hasValidView()) { + Log.w(TAG, "The view has since become invalid, aborting the CUJ."); + return false; } RunningTracker tracker = putTrackerIfNoCurrent(cujType, () -> diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java index 14786e107832..cbcbf2db6c51 100644 --- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java @@ -162,8 +162,10 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider, @Nullable ProtoLogViewerConfigReader viewerConfigReader, @NonNull Runnable cacheUpdater) { - assert (viewerConfigFilePath == null || viewerConfigInputStreamProvider == null) : - "Only one of viewerConfigFilePath and viewerConfigInputStreamProvider can be set"; + if (viewerConfigFilePath != null && viewerConfigInputStreamProvider != null) { + throw new RuntimeException("Only one of viewerConfigFilePath and " + + "viewerConfigInputStreamProvider can be set"); + } Producer.init(InitArguments.DEFAULTS); DataSourceParams params = @@ -734,7 +736,7 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto return UUID.nameUUIDFromBytes(fullStringIdentifier.getBytes()).getMostSignificantBits(); } - private static final int STACK_SIZE_TO_PROTO_LOG_ENTRY_CALL = 12; + private static final int STACK_SIZE_TO_PROTO_LOG_ENTRY_CALL = 6; private String collectStackTrace() { StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 9112d376a32f..284c2997f9a9 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -19,14 +19,6 @@ #include "com_android_internal_os_Zygote.h" -#include <async_safe/log.h> - -// sys/mount.h has to come before linux/fs.h due to redefinition of MS_RDONLY, MS_BIND, etc -#include <sys/mount.h> -#include <linux/fs.h> -#include <sys/types.h> -#include <dirent.h> - #include <algorithm> #include <array> #include <atomic> @@ -41,19 +33,18 @@ #include <android/fdsan.h> #include <arpa/inet.h> +#include <dirent.h> #include <fcntl.h> #include <grp.h> #include <inttypes.h> #include <malloc.h> #include <mntent.h> -#include <paths.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> -#include <sys/auxv.h> #include <sys/capability.h> -#include <sys/cdefs.h> #include <sys/eventfd.h> +#include <sys/mount.h> #include <sys/personality.h> #include <sys/prctl.h> #include <sys/resource.h> @@ -66,6 +57,7 @@ #include <sys/wait.h> #include <unistd.h> +#include <async_safe/log.h> #include <android-base/file.h> #include <android-base/logging.h> #include <android-base/properties.h> diff --git a/core/res/res/drawable/ic_zen_mode_icon_animal_paw.xml b/core/res/res/drawable/ic_zen_mode_icon_animal_paw.xml new file mode 100644 index 000000000000..31004ecd0e72 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_animal_paw.xml @@ -0,0 +1,25 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M180,485Q138,485 109,456Q80,427 80,385Q80,343 109,314Q138,285 180,285Q222,285 251,314Q280,343 280,385Q280,427 251,456Q222,485 180,485ZM360,325Q318,325 289,296Q260,267 260,225Q260,183 289,154Q318,125 360,125Q402,125 431,154Q460,183 460,225Q460,267 431,296Q402,325 360,325ZM600,325Q558,325 529,296Q500,267 500,225Q500,183 529,154Q558,125 600,125Q642,125 671,154Q700,183 700,225Q700,267 671,296Q642,325 600,325ZM780,485Q738,485 709,456Q680,427 680,385Q680,343 709,314Q738,285 780,285Q822,285 851,314Q880,343 880,385Q880,427 851,456Q822,485 780,485ZM266,885Q221,885 190.5,850.5Q160,816 160,769Q160,717 195.5,678Q231,639 266,601Q295,570 316,533.5Q337,497 366,465Q388,439 417,422Q446,405 480,405Q514,405 543,421Q572,437 594,463Q622,495 643.5,532Q665,569 694,601Q729,639 764.5,678Q800,717 800,769Q800,816 769.5,850.5Q739,885 694,885Q640,885 587,876Q534,867 480,867Q426,867 373,876Q320,885 266,885Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_apartment_building.xml b/core/res/res/drawable/ic_zen_mode_icon_apartment_building.xml new file mode 100644 index 000000000000..30f01fa9d3b6 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_apartment_building.xml @@ -0,0 +1,25 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M120,840L120,280L280,280L280,120L680,120L680,440L840,440L840,840L520,840L520,680L440,680L440,840L120,840ZM200,760L280,760L280,680L200,680L200,760ZM200,600L280,600L280,520L200,520L200,600ZM200,440L280,440L280,360L200,360L200,440ZM360,600L440,600L440,520L360,520L360,600ZM360,440L440,440L440,360L360,360L360,440ZM360,280L440,280L440,200L360,200L360,280ZM520,600L600,600L600,520L520,520L520,600ZM520,440L600,440L600,360L520,360L520,440ZM520,280L600,280L600,200L520,200L520,280ZM680,760L760,760L760,680L680,680L680,760ZM680,600L760,600L760,520L680,520L680,600Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_book.xml b/core/res/res/drawable/ic_zen_mode_icon_book.xml new file mode 100644 index 000000000000..c30d222da7c1 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_book.xml @@ -0,0 +1,26 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960" + android:autoMirrored="true"> + <path + android:fillColor="@android:color/white" + android:pathData="M560,396L560,328Q593,314 627.5,307Q662,300 700,300Q726,300 751,304Q776,308 800,314L800,378Q776,369 751.5,364.5Q727,360 700,360Q662,360 627,369.5Q592,379 560,396ZM560,616L560,548Q593,534 627.5,527Q662,520 700,520Q726,520 751,524Q776,528 800,534L800,598Q776,589 751.5,584.5Q727,580 700,580Q662,580 627,589Q592,598 560,616ZM560,506L560,438Q593,424 627.5,417Q662,410 700,410Q726,410 751,414Q776,418 800,424L800,488Q776,479 751.5,474.5Q727,470 700,470Q662,470 627,479.5Q592,489 560,506ZM260,640Q307,640 351.5,650.5Q396,661 440,682L440,288Q399,264 353,252Q307,240 260,240Q224,240 188.5,247Q153,254 120,268Q120,268 120,268Q120,268 120,268L120,664Q120,664 120,664Q120,664 120,664Q155,652 189.5,646Q224,640 260,640ZM520,682Q564,661 608.5,650.5Q653,640 700,640Q736,640 770.5,646Q805,652 840,664Q840,664 840,664Q840,664 840,664L840,268Q840,268 840,268Q840,268 840,268Q807,254 771.5,247Q736,240 700,240Q653,240 607,252Q561,264 520,288L520,682ZM480,800Q432,762 376,741Q320,720 260,720Q218,720 177.5,731Q137,742 100,762Q79,773 59.5,761Q40,749 40,726L40,244Q40,233 45.5,223Q51,213 62,208Q108,184 158,172Q208,160 260,160Q318,160 373.5,175Q429,190 480,220Q531,190 586.5,175Q642,160 700,160Q752,160 802,172Q852,184 898,208Q909,213 914.5,223Q920,233 920,244L920,726Q920,749 900.5,761Q881,773 860,762Q823,742 782.5,731Q742,720 700,720Q640,720 584,741Q528,762 480,800ZM280,466Q280,466 280,466Q280,466 280,466L280,466Q280,466 280,466Q280,466 280,466Q280,466 280,466Q280,466 280,466Q280,466 280,466Q280,466 280,466L280,466Q280,466 280,466Q280,466 280,466Q280,466 280,466Q280,466 280,466Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_child.xml b/core/res/res/drawable/ic_zen_mode_icon_child.xml new file mode 100644 index 000000000000..d11772ac1db6 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_child.xml @@ -0,0 +1,25 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M580,470Q559,470 544.5,455.5Q530,441 530,420Q530,399 544.5,384.5Q559,370 580,370Q601,370 615.5,384.5Q630,399 630,420Q630,441 615.5,455.5Q601,470 580,470ZM380,470Q359,470 344.5,455.5Q330,441 330,420Q330,399 344.5,384.5Q359,370 380,370Q401,370 415.5,384.5Q430,399 430,420Q430,441 415.5,455.5Q401,470 380,470ZM480,680Q420,680 371.5,647Q323,614 300,560L660,560Q637,614 588.5,647Q540,680 480,680ZM480,840Q405,840 339.5,811.5Q274,783 225.5,734.5Q177,686 148.5,620.5Q120,555 120,480Q120,405 148.5,339.5Q177,274 225.5,225.5Q274,177 339.5,148.5Q405,120 480,120Q555,120 620.5,148.5Q686,177 734.5,225.5Q783,274 811.5,339.5Q840,405 840,480Q840,555 811.5,620.5Q783,686 734.5,734.5Q686,783 620.5,811.5Q555,840 480,840ZM480,760Q596,760 678,678Q760,596 760,480Q760,364 678,282Q596,200 480,200Q474,200 468,200Q462,200 456,202Q450,208 448,215Q446,222 446,230Q446,251 460.5,265.5Q475,280 496,280Q505,280 512.5,277Q520,274 528,274Q540,274 548,283Q556,292 556,304Q556,327 534.5,333.5Q513,340 496,340Q451,340 418.5,307.5Q386,275 386,230Q386,227 386,224Q386,221 387,216Q304,246 252,317Q200,388 200,480Q200,596 282,678Q364,760 480,760ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_classical_building.xml b/core/res/res/drawable/ic_zen_mode_icon_classical_building.xml new file mode 100644 index 000000000000..26751262c673 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_classical_building.xml @@ -0,0 +1,25 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M200,680L200,400L280,400L280,680L200,680ZM440,680L440,400L520,400L520,680L440,680ZM80,840L80,760L880,760L880,840L80,840ZM680,680L680,400L760,400L760,680L680,680ZM80,320L80,240L480,40L880,240L880,320L80,320ZM258,240L480,240L702,240L258,240ZM258,240L702,240L480,130L258,240Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_croissant.xml b/core/res/res/drawable/ic_zen_mode_icon_croissant.xml new file mode 100644 index 000000000000..199343d0e502 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_croissant.xml @@ -0,0 +1,25 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M804,678Q821,687 834,674Q847,661 838,644L780,536L738,644L804,678ZM604,640L652,640L748,402Q751,394 746.5,388.5Q742,383 736,380L656,348Q647,345 638.5,350Q630,355 628,364L604,640ZM308,640L356,640L332,364Q330,353 321.5,349Q313,345 304,348L224,380Q216,383 212.5,388.5Q209,394 212,402L308,640ZM156,678L222,644L180,536L122,644Q113,661 126,674Q139,687 156,678ZM436,640L524,640L554,302Q556,293 549.5,286.5Q543,280 534,280L426,280Q418,280 411.5,286.5Q405,293 406,302L436,640ZM138,760Q96,760 68,728.5Q40,697 40,654Q40,642 43.5,630.5Q47,619 52,608L140,440Q126,400 141,361Q156,322 194,306L274,274Q288,269 302,267Q316,265 330,268Q344,239 369,219.5Q394,200 426,200L534,200Q566,200 591,219.5Q616,239 630,268Q644,266 658,267.5Q672,269 686,274L766,306Q806,322 822,361Q838,400 820,438L908,606Q914,617 917,629Q920,641 920,654Q920,699 889.5,729.5Q859,760 814,760Q803,760 792,757.5Q781,755 770,750L708,720L250,720L194,750Q181,757 166.5,758.5Q152,760 138,760ZM480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_fork_and_knife.xml b/core/res/res/drawable/ic_zen_mode_icon_fork_and_knife.xml new file mode 100644 index 000000000000..1fa73794eb32 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_fork_and_knife.xml @@ -0,0 +1,25 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M280,880L280,514Q229,500 194.5,458Q160,416 160,360L160,80L240,80L240,360L280,360L280,80L360,80L360,360L400,360L400,80L480,80L480,360Q480,416 445.5,458Q411,500 360,514L360,880L280,880ZM680,880L680,560L560,560L560,280Q560,197 618.5,138.5Q677,80 760,80L760,880L680,880Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_group_of_people.xml b/core/res/res/drawable/ic_zen_mode_icon_group_of_people.xml new file mode 100644 index 000000000000..c6194d529117 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_group_of_people.xml @@ -0,0 +1,25 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M0,720L0,657Q0,614 44,587Q88,560 160,560Q173,560 185,560.5Q197,561 208,563Q194,584 187,607Q180,630 180,655L180,720L0,720ZM240,720L240,655Q240,623 257.5,596.5Q275,570 307,550Q339,530 383.5,520Q428,510 480,510Q533,510 577.5,520Q622,530 654,550Q686,570 703,596.5Q720,623 720,655L720,720L240,720ZM780,720L780,655Q780,629 773.5,606Q767,583 754,563Q765,561 776.5,560.5Q788,560 800,560Q872,560 916,586.5Q960,613 960,657L960,720L780,720ZM325,640L636,640L636,640Q626,620 580.5,605Q535,590 480,590Q425,590 379.5,605Q334,620 325,640ZM160,520Q127,520 103.5,496.5Q80,473 80,440Q80,406 103.5,383Q127,360 160,360Q194,360 217,383Q240,406 240,440Q240,473 217,496.5Q194,520 160,520ZM800,520Q767,520 743.5,496.5Q720,473 720,440Q720,406 743.5,383Q767,360 800,360Q834,360 857,383Q880,406 880,440Q880,473 857,496.5Q834,520 800,520ZM480,480Q430,480 395,445Q360,410 360,360Q360,309 395,274.5Q430,240 480,240Q531,240 565.5,274.5Q600,309 600,360Q600,410 565.5,445Q531,480 480,480ZM480,400Q497,400 508.5,388.5Q520,377 520,360Q520,343 508.5,331.5Q497,320 480,320Q463,320 451.5,331.5Q440,343 440,360Q440,377 451.5,388.5Q463,400 480,400ZM481,640L481,640Q481,640 481,640Q481,640 481,640Q481,640 481,640Q481,640 481,640L481,640ZM480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_headphones.xml b/core/res/res/drawable/ic_zen_mode_icon_headphones.xml new file mode 100644 index 000000000000..f0bc7a211ca5 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_headphones.xml @@ -0,0 +1,25 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M360,840L200,840Q167,840 143.5,816.5Q120,793 120,760L120,480Q120,405 148.5,339.5Q177,274 225.5,225.5Q274,177 339.5,148.5Q405,120 480,120Q555,120 620.5,148.5Q686,177 734.5,225.5Q783,274 811.5,339.5Q840,405 840,480L840,760Q840,793 816.5,816.5Q793,840 760,840L600,840L600,520L760,520L760,480Q760,363 678.5,281.5Q597,200 480,200Q363,200 281.5,281.5Q200,363 200,480L200,520L360,520L360,840ZM280,600L200,600L200,760Q200,760 200,760Q200,760 200,760L280,760L280,600ZM680,600L680,760L760,760Q760,760 760,760Q760,760 760,760L760,600L680,600ZM280,600L280,600L200,600Q200,600 200,600Q200,600 200,600L200,600L280,600ZM680,600L760,600L760,600Q760,600 760,600Q760,600 760,600L680,600Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_heart.xml b/core/res/res/drawable/ic_zen_mode_icon_heart.xml new file mode 100644 index 000000000000..c9b1577d57f4 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_heart.xml @@ -0,0 +1,25 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M480,840L422,788Q321,697 255,631Q189,565 150,512.5Q111,460 95.5,416Q80,372 80,326Q80,232 143,169Q206,106 300,106Q352,106 399,128Q446,150 480,190Q514,150 561,128Q608,106 660,106Q754,106 817,169Q880,232 880,326Q880,372 864.5,416Q849,460 810,512.5Q771,565 705,631Q639,697 538,788L480,840ZM480,732Q576,646 638,584.5Q700,523 736,477.5Q772,432 786,396.5Q800,361 800,326Q800,266 760,226Q720,186 660,186Q613,186 573,212.5Q533,239 518,280L518,280L442,280L442,280Q427,239 387,212.5Q347,186 300,186Q240,186 200,226Q160,266 160,326Q160,361 174,396.5Q188,432 224,477.5Q260,523 322,584.5Q384,646 480,732ZM480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459L480,459L480,459L480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_house.xml b/core/res/res/drawable/ic_zen_mode_icon_house.xml new file mode 100644 index 000000000000..e25d194eb8d0 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_house.xml @@ -0,0 +1,25 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M160,840L160,465L88,520L40,456L160,364L160,240L240,240L240,303L480,120L920,456L872,519L800,465L800,840L160,840ZM240,760L440,760L440,600L520,600L520,760L720,760L720,404L480,221L240,404L240,760ZM160,200Q160,150 195,115Q230,80 280,80Q297,80 308.5,68.5Q320,57 320,40L400,40Q400,90 365,125Q330,160 280,160Q263,160 251.5,171.5Q240,183 240,200L160,200ZM240,760L440,760L440,760L520,760L520,760L720,760L720,760L480,760L240,760Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_lightbulb.xml b/core/res/res/drawable/ic_zen_mode_icon_lightbulb.xml new file mode 100644 index 000000000000..602e60d0824a --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_lightbulb.xml @@ -0,0 +1,25 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M480,880Q454,880 433,867.5Q412,855 400,834L400,834Q367,834 343.5,810.5Q320,787 320,754L320,612Q261,573 225.5,509Q190,445 190,370Q190,249 274.5,164.5Q359,80 480,80Q601,80 685.5,164.5Q770,249 770,370Q770,447 734.5,510Q699,573 640,612L640,754Q640,787 616.5,810.5Q593,834 560,834L560,834Q548,855 527,867.5Q506,880 480,880ZM400,754L560,754L560,718L400,718L400,754ZM400,678L560,678L560,640L400,640L400,678ZM392,560L450,560L450,452L362,364L404,322L480,398L556,322L598,364L510,452L510,560L568,560Q622,534 656,483.5Q690,433 690,370Q690,282 629,221Q568,160 480,160Q392,160 331,221Q270,282 270,370Q270,433 304,483.5Q338,534 392,560ZM480,398L480,398L480,398L480,398L480,398L480,398L480,398L480,398L480,398ZM480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360L480,360L480,360L480,360L480,360L480,360L480,360L480,360L480,360L480,360Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_palette.xml b/core/res/res/drawable/ic_zen_mode_icon_palette.xml new file mode 100644 index 000000000000..f31704de1498 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_palette.xml @@ -0,0 +1,25 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M480,880Q398,880 325,848.5Q252,817 197.5,762.5Q143,708 111.5,635Q80,562 80,480Q80,397 112.5,324Q145,251 200.5,197Q256,143 330,111.5Q404,80 488,80Q568,80 639,107.5Q710,135 763.5,183.5Q817,232 848.5,298.5Q880,365 880,442Q880,557 810,618.5Q740,680 640,680L566,680Q557,680 553.5,685Q550,690 550,696Q550,708 565,730.5Q580,753 580,782Q580,832 552.5,856Q525,880 480,880ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480ZM260,520Q286,520 303,503Q320,486 320,460Q320,434 303,417Q286,400 260,400Q234,400 217,417Q200,434 200,460Q200,486 217,503Q234,520 260,520ZM380,360Q406,360 423,343Q440,326 440,300Q440,274 423,257Q406,240 380,240Q354,240 337,257Q320,274 320,300Q320,326 337,343Q354,360 380,360ZM580,360Q606,360 623,343Q640,326 640,300Q640,274 623,257Q606,240 580,240Q554,240 537,257Q520,274 520,300Q520,326 537,343Q554,360 580,360ZM700,520Q726,520 743,503Q760,486 760,460Q760,434 743,417Q726,400 700,400Q674,400 657,417Q640,434 640,460Q640,486 657,503Q674,520 700,520ZM480,800Q489,800 494.5,795Q500,790 500,782Q500,768 485,749Q470,730 470,692Q470,650 499,625Q528,600 570,600L640,600Q706,600 753,561.5Q800,523 800,442Q800,321 707.5,240.5Q615,160 488,160Q352,160 256,253Q160,346 160,480Q160,613 253.5,706.5Q347,800 480,800Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_rabbit.xml b/core/res/res/drawable/ic_zen_mode_icon_rabbit.xml new file mode 100644 index 000000000000..190d0cb319ec --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_rabbit.xml @@ -0,0 +1,25 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M380,880Q305,880 252.5,827.5Q200,775 200,700Q200,665 217,635.5Q234,606 280,560Q286,554 291.5,547.5Q297,541 306,530Q255,452 227.5,366.5Q200,281 200,200Q200,142 221,111Q242,80 280,80Q337,80 382,135Q427,190 450,236Q459,256 466.5,276.5Q474,297 480,319Q486,297 493.5,276.5Q501,256 511,236Q533,190 578,135Q623,80 680,80Q718,80 739,111Q760,142 760,200Q760,281 732.5,366.5Q705,452 654,530Q663,541 668.5,547.5Q674,554 680,560Q726,606 743,635.5Q760,665 760,700Q760,775 707.5,827.5Q655,880 580,880Q535,880 507.5,870Q480,860 480,860Q480,860 452.5,870Q425,880 380,880ZM380,800Q403,800 426,794.5Q449,789 469,778Q458,773 449,761Q440,749 440,740Q440,732 451.5,726Q463,720 480,720Q497,720 508.5,726Q520,732 520,740Q520,749 511,761Q502,773 491,778Q511,789 534,794.5Q557,800 580,800Q622,800 651,771Q680,742 680,700Q680,682 670,665Q660,648 640,631Q626,619 617,610Q608,601 588,576Q559,541 540,530.5Q521,520 480,520Q439,520 419.5,530.5Q400,541 372,576Q352,601 343,610Q334,619 320,631Q300,648 290,665Q280,682 280,700Q280,742 309,771Q338,800 380,800ZM420,670Q412,670 406,661Q400,652 400,640Q400,628 406,619Q412,610 420,610Q428,610 434,619Q440,628 440,640Q440,652 434,661Q428,670 420,670ZM540,670Q532,670 526,661Q520,652 520,640Q520,628 526,619Q532,610 540,610Q548,610 554,619Q560,628 560,640Q560,652 554,661Q548,670 540,670ZM363,471Q374,463 388,457Q402,451 419,446Q417,398 404.5,350.5Q392,303 373,264Q354,224 331,196.5Q308,169 285,161Q283,167 281.5,176.5Q280,186 280,200Q280,268 301.5,338Q323,408 363,471ZM597,471Q637,408 658.5,338Q680,268 680,200Q680,186 678.5,176.5Q677,167 675,161Q652,169 629,196.5Q606,224 587,264Q569,303 556.5,350.5Q544,398 541,446Q556,450 570,456.5Q584,463 597,471Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_running.xml b/core/res/res/drawable/ic_zen_mode_icon_running.xml new file mode 100644 index 000000000000..472b04e24bc0 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_running.xml @@ -0,0 +1,26 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960" + android:autoMirrored="true"> + <path + android:fillColor="@android:color/white" + android:pathData="M520,920L520,680L436,600L396,776L120,720L136,640L328,680L392,356L320,384L320,520L240,520L240,332L398,264Q433,249 449.5,244.5Q466,240 480,240Q501,240 519,251Q537,262 548,280L588,344Q614,386 658.5,413Q703,440 760,440L760,520Q694,520 636.5,492.5Q579,465 540,420L516,540L600,620L600,920L520,920ZM540,220Q507,220 483.5,196.5Q460,173 460,140Q460,107 483.5,83.5Q507,60 540,60Q573,60 596.5,83.5Q620,107 620,140Q620,173 596.5,196.5Q573,220 540,220Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_shopping_cart.xml b/core/res/res/drawable/ic_zen_mode_icon_shopping_cart.xml new file mode 100644 index 000000000000..92cec4dde7b7 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_shopping_cart.xml @@ -0,0 +1,25 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M280,880Q247,880 223.5,856.5Q200,833 200,800Q200,767 223.5,743.5Q247,720 280,720Q313,720 336.5,743.5Q360,767 360,800Q360,833 336.5,856.5Q313,880 280,880ZM680,880Q647,880 623.5,856.5Q600,833 600,800Q600,767 623.5,743.5Q647,720 680,720Q713,720 736.5,743.5Q760,767 760,800Q760,833 736.5,856.5Q713,880 680,880ZM246,240L342,440L622,440Q622,440 622,440Q622,440 622,440L732,240Q732,240 732,240Q732,240 732,240L246,240ZM208,160L798,160Q821,160 833,180.5Q845,201 834,222L692,478Q681,498 662.5,509Q644,520 622,520L324,520L280,600Q280,600 280,600Q280,600 280,600L760,600L760,680L280,680Q235,680 212,640.5Q189,601 210,562L264,464L120,160L40,160L40,80L170,80L208,160ZM342,440L342,440L622,440Q622,440 622,440Q622,440 622,440L622,440L342,440Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_snowflake.xml b/core/res/res/drawable/ic_zen_mode_icon_snowflake.xml new file mode 100644 index 000000000000..1746e20a6aae --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_snowflake.xml @@ -0,0 +1,25 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M440,880L440,714L310,842L254,786L440,600L440,520L360,520L174,706L118,650L246,520L80,520L80,440L246,440L118,310L174,254L360,440L440,440L440,360L254,174L310,118L440,246L440,80L520,80L520,246L650,118L706,174L520,360L520,440L600,440L786,254L842,310L714,440L880,440L880,520L714,520L842,650L786,706L600,520L520,520L520,600L706,786L650,842L520,714L520,880L440,880Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_speech_bubble.xml b/core/res/res/drawable/ic_zen_mode_icon_speech_bubble.xml new file mode 100644 index 000000000000..4be98abd9369 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_speech_bubble.xml @@ -0,0 +1,25 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M80,880L80,160Q80,127 103.5,103.5Q127,80 160,80L800,80Q833,80 856.5,103.5Q880,127 880,160L880,640Q880,673 856.5,696.5Q833,720 800,720L240,720L80,880ZM206,640L800,640Q800,640 800,640Q800,640 800,640L800,160Q800,160 800,160Q800,160 800,160L160,160Q160,160 160,160Q160,160 160,160L160,685L206,640ZM160,640L160,640L160,160Q160,160 160,160Q160,160 160,160L160,160Q160,160 160,160Q160,160 160,160L160,640Q160,640 160,640Q160,640 160,640Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_train.xml b/core/res/res/drawable/ic_zen_mode_icon_train.xml new file mode 100644 index 000000000000..b6f3445468a5 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_train.xml @@ -0,0 +1,25 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M160,620L160,240Q160,187 187.5,155.5Q215,124 260,107.5Q305,91 362.5,85.5Q420,80 480,80Q546,80 604.5,85.5Q663,91 706.5,107.5Q750,124 775,155.5Q800,187 800,240L800,620Q800,679 759.5,719.5Q719,760 660,760L720,820L720,840L640,840L560,760L400,760L320,840L240,840L240,820L300,760Q241,760 200.5,719.5Q160,679 160,620ZM480,160Q374,160 325,172.5Q276,185 258,200L706,200Q691,183 641.5,171.5Q592,160 480,160ZM240,400L440,400L440,280L240,280L240,400ZM660,480L300,480Q274,480 257,480Q240,480 240,480L240,480L720,480L720,480Q720,480 703,480Q686,480 660,480ZM520,400L720,400L720,280L520,280L520,400ZM340,640Q366,640 383,623Q400,606 400,580Q400,554 383,537Q366,520 340,520Q314,520 297,537Q280,554 280,580Q280,606 297,623Q314,640 340,640ZM620,640Q646,640 663,623Q680,606 680,580Q680,554 663,537Q646,520 620,520Q594,520 577,537Q560,554 560,580Q560,606 577,623Q594,640 620,640ZM300,680L660,680Q686,680 703,663Q720,646 720,620L720,480L240,480L240,620Q240,646 257,663Q274,680 300,680ZM480,200Q592,200 641.5,200Q691,200 706,200L258,200Q276,200 325,200Q374,200 480,200Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_tv.xml b/core/res/res/drawable/ic_zen_mode_icon_tv.xml new file mode 100644 index 000000000000..eaa920aa37cc --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_tv.xml @@ -0,0 +1,25 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M320,840L320,760L160,760Q127,760 103.5,736.5Q80,713 80,680L80,200Q80,167 103.5,143.5Q127,120 160,120L800,120Q833,120 856.5,143.5Q880,167 880,200L880,680Q880,713 856.5,736.5Q833,760 800,760L640,760L640,840L320,840ZM160,680L800,680Q800,680 800,680Q800,680 800,680L800,200Q800,200 800,200Q800,200 800,200L160,200Q160,200 160,200Q160,200 160,200L160,680Q160,680 160,680Q160,680 160,680ZM160,680Q160,680 160,680Q160,680 160,680L160,200Q160,200 160,200Q160,200 160,200L160,200Q160,200 160,200Q160,200 160,200L160,680Q160,680 160,680Q160,680 160,680Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index 61c7a8cde7d5..383033d9ae5d 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -237,6 +237,13 @@ <integer name="config_emergency_call_wait_for_connection_timeout_millis">20000</integer> <java-symbol type="integer" name="config_emergency_call_wait_for_connection_timeout_millis" /> + <!-- The time duration in millis after which Telephony will stop waiting for the OFF state + from cellular modem, consider the request to power off cellular modem as failed, and thus + treat the cellular modem state as ON. + --> + <integer name="config_satellite_wait_for_cellular_modem_off_timeout_millis">120000</integer> + <java-symbol type="integer" name="config_satellite_wait_for_cellular_modem_off_timeout_millis" /> + <!-- Indicates the data limit in bytes that can be used for bootstrap sim until factory reset. -1 means unlimited. --> <integer name="config_esim_bootstrap_data_limit_bytes">-1</integer> diff --git a/core/tests/coretests/src/android/os/VibrationAttributesTest.java b/core/tests/coretests/src/android/os/VibrationAttributesTest.java index d8142c8d91bf..5bdae0e6455b 100644 --- a/core/tests/coretests/src/android/os/VibrationAttributesTest.java +++ b/core/tests/coretests/src/android/os/VibrationAttributesTest.java @@ -28,11 +28,9 @@ public class VibrationAttributesTest { @Test public void testSimple() throws Exception { final VibrationAttributes attr = new VibrationAttributes.Builder() - .setCategory(VibrationAttributes.CATEGORY_KEYBOARD) .setUsage(VibrationAttributes.USAGE_ALARM) .build(); - assertEquals(VibrationAttributes.CATEGORY_KEYBOARD, attr.getCategory()); assertEquals(VibrationAttributes.USAGE_ALARM, attr.getUsage()); } } diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt index ae60d8bc2596..4b97451a0c41 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt @@ -268,7 +268,8 @@ class BubblePositionerTest { ) positioner.update(deviceConfig) val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) - val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) + val bubble = + Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor()) assertThat(positioner.getExpandedViewHeight(bubble)).isEqualTo(MAX_HEIGHT) } @@ -294,6 +295,7 @@ class BubblePositionerTest { 0 /* taskId */, null /* locus */, true /* isDismissable */, + directExecutor(), directExecutor() ) {} @@ -322,6 +324,7 @@ class BubblePositionerTest { 0 /* taskId */, null /* locus */, true /* isDismissable */, + directExecutor(), directExecutor() ) {} @@ -416,7 +419,8 @@ class BubblePositionerTest { positioner.update(deviceConfig) val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) - val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) + val bubble = + Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor()) // This bubble will have max height so it'll always be top aligned assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) @@ -433,7 +437,8 @@ class BubblePositionerTest { positioner.update(deviceConfig) val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) - val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) + val bubble = + Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor()) // Always top aligned in phone portrait assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) @@ -452,7 +457,8 @@ class BubblePositionerTest { positioner.update(deviceConfig) val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) - val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) + val bubble = + Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor()) // This bubble will have max height which is always top aligned on small tablets assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) @@ -470,7 +476,8 @@ class BubblePositionerTest { positioner.update(deviceConfig) val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) - val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) + val bubble = + Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor()) // This bubble will have max height which is always top aligned on small tablets assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) @@ -489,7 +496,8 @@ class BubblePositionerTest { positioner.update(deviceConfig) val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) - val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) + val bubble = + Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor()) // This bubble will have max height which is always top aligned on landscape, large tablet assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) @@ -507,7 +515,8 @@ class BubblePositionerTest { positioner.update(deviceConfig) val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) - val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) + val bubble = + Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor()) val manageButtonHeight = context.resources.getDimensionPixelSize(R.dimen.bubble_manage_button_height) diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt index 84f7bb27ca82..faadf1d623c9 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt @@ -102,6 +102,7 @@ class BubbleStackViewTest { BubbleLogger(UiEventLoggerFake()), positioner, BubbleEducationController(context), + shellExecutor, shellExecutor ) bubbleStackViewManager = FakeBubbleStackViewManager() @@ -364,6 +365,7 @@ class BubbleStackViewTest { /* taskId= */ 0, "locus", /* isDismissable= */ true, + directExecutor(), directExecutor() ) {} inflateBubble(bubble) @@ -373,7 +375,8 @@ class BubbleStackViewTest { private fun createAndInflateBubble(): Bubble { val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) val icon = Icon.createWithResource(context.resources, R.drawable.bubble_ic_overflow_button) - val bubble = Bubble.createAppBubble(intent, UserHandle(1), icon, directExecutor()) + val bubble = + Bubble.createAppBubble(intent, UserHandle(1), icon, directExecutor(), directExecutor()) inflateBubble(bubble) return bubble } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index 5cd2cb7d51d5..021d3c32fd63 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -54,6 +54,8 @@ import com.android.wm.shell.Flags; import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView; import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; import com.android.wm.shell.common.bubbles.BubbleInfo; +import com.android.wm.shell.shared.annotations.ShellBackgroundThread; +import com.android.wm.shell.shared.annotations.ShellMainThread; import java.io.PrintWriter; import java.util.List; @@ -79,6 +81,7 @@ public class Bubble implements BubbleViewProvider { private final LocusId mLocusId; private final Executor mMainExecutor; + private final Executor mBgExecutor; private long mLastUpdated; private long mLastAccessed; @@ -111,7 +114,10 @@ public class Bubble implements BubbleViewProvider { @Nullable private BubbleTaskView mBubbleTaskView; + @Nullable private BubbleViewInfoTask mInflationTask; + @Nullable + private BubbleViewInfoTaskLegacy mInflationTaskLegacy; private boolean mInflateSynchronously; private boolean mPendingIntentCanceled; private boolean mIsImportantConversation; @@ -203,7 +209,9 @@ public class Bubble implements BubbleViewProvider { @VisibleForTesting(visibility = PRIVATE) public Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo, final int desiredHeight, final int desiredHeightResId, @Nullable final String title, - int taskId, @Nullable final String locus, boolean isDismissable, Executor mainExecutor, + int taskId, @Nullable final String locus, boolean isDismissable, + @ShellMainThread Executor mainExecutor, + @ShellBackgroundThread Executor bgExecutor, final Bubbles.BubbleMetadataFlagListener listener) { Objects.requireNonNull(key); Objects.requireNonNull(shortcutInfo); @@ -222,6 +230,7 @@ public class Bubble implements BubbleViewProvider { mTitle = title; mShowBubbleUpdateDot = false; mMainExecutor = mainExecutor; + mBgExecutor = bgExecutor; mTaskId = taskId; mBubbleMetadataFlagListener = listener; mIsAppBubble = false; @@ -233,7 +242,8 @@ public class Bubble implements BubbleViewProvider { @Nullable Icon icon, boolean isAppBubble, String key, - Executor mainExecutor) { + @ShellMainThread Executor mainExecutor, + @ShellBackgroundThread Executor bgExecutor) { mGroupKey = null; mLocusId = null; mFlags = 0; @@ -243,13 +253,15 @@ public class Bubble implements BubbleViewProvider { mKey = key; mShowBubbleUpdateDot = false; mMainExecutor = mainExecutor; + mBgExecutor = bgExecutor; mTaskId = INVALID_TASK_ID; mAppIntent = intent; mDesiredHeight = Integer.MAX_VALUE; mPackageName = intent.getPackage(); } - private Bubble(ShortcutInfo info, Executor mainExecutor) { + private Bubble(ShortcutInfo info, @ShellMainThread Executor mainExecutor, + @ShellBackgroundThread Executor bgExecutor) { mGroupKey = null; mLocusId = null; mFlags = 0; @@ -259,6 +271,7 @@ public class Bubble implements BubbleViewProvider { mKey = getBubbleKeyForShortcut(info); mShowBubbleUpdateDot = false; mMainExecutor = mainExecutor; + mBgExecutor = bgExecutor; mTaskId = INVALID_TASK_ID; mAppIntent = null; mDesiredHeight = Integer.MAX_VALUE; @@ -267,24 +280,21 @@ public class Bubble implements BubbleViewProvider { } /** Creates an app bubble. */ - public static Bubble createAppBubble( - Intent intent, - UserHandle user, - @Nullable Icon icon, - Executor mainExecutor) { + public static Bubble createAppBubble(Intent intent, UserHandle user, @Nullable Icon icon, + @ShellMainThread Executor mainExecutor, @ShellBackgroundThread Executor bgExecutor) { return new Bubble(intent, user, icon, /* isAppBubble= */ true, /* key= */ getAppBubbleKeyForApp(intent.getPackage(), user), - mainExecutor); + mainExecutor, bgExecutor); } /** Creates a shortcut bubble. */ public static Bubble createShortcutBubble( ShortcutInfo info, - Executor mainExecutor) { - return new Bubble(info, mainExecutor); + @ShellMainThread Executor mainExecutor, @ShellBackgroundThread Executor bgExecutor) { + return new Bubble(info, mainExecutor, bgExecutor); } /** @@ -309,7 +319,7 @@ public class Bubble implements BubbleViewProvider { public Bubble(@NonNull final BubbleEntry entry, final Bubbles.BubbleMetadataFlagListener listener, final Bubbles.PendingIntentCanceledListener intentCancelListener, - Executor mainExecutor) { + @ShellMainThread Executor mainExecutor, @ShellBackgroundThread Executor bgExecutor) { mIsAppBubble = false; mKey = entry.getKey(); mGroupKey = entry.getGroupKey(); @@ -324,6 +334,7 @@ public class Bubble implements BubbleViewProvider { }); }; mMainExecutor = mainExecutor; + mBgExecutor = bgExecutor; mTaskId = INVALID_TASK_ID; setEntry(entry); } @@ -557,40 +568,70 @@ public class Bubble implements BubbleViewProvider { @Nullable BubbleBarLayerView layerView, BubbleIconFactory iconFactory, boolean skipInflation) { - if (isBubbleLoading()) { - mInflationTask.cancel(true /* mayInterruptIfRunning */); - } - mInflationTask = new BubbleViewInfoTask(this, - context, - expandedViewManager, - taskViewFactory, - positioner, - stackView, - layerView, - iconFactory, - skipInflation, - callback, - mMainExecutor); - if (mInflateSynchronously) { - mInflationTask.onPostExecute(mInflationTask.doInBackground()); + if (Flags.bubbleViewInfoExecutors()) { + if (mInflationTask != null && mInflationTask.getStatus() != FINISHED) { + mInflationTask.cancel(true /* mayInterruptIfRunning */); + } + // TODO(b/353894869): switch to executors + mInflationTask = new BubbleViewInfoTask(this, + context, + expandedViewManager, + taskViewFactory, + positioner, + stackView, + layerView, + iconFactory, + skipInflation, + callback, + mMainExecutor); + if (mInflateSynchronously) { + mInflationTask.onPostExecute(mInflationTask.doInBackground()); + } else { + mInflationTask.execute(); + } } else { - mInflationTask.execute(); + if (mInflationTaskLegacy != null && mInflationTaskLegacy.getStatus() != FINISHED) { + mInflationTaskLegacy.cancel(true /* mayInterruptIfRunning */); + } + mInflationTaskLegacy = new BubbleViewInfoTaskLegacy(this, + context, + expandedViewManager, + taskViewFactory, + positioner, + stackView, + layerView, + iconFactory, + skipInflation, + bubble -> { + if (callback != null) { + callback.onBubbleViewsReady(bubble); + } + }, + mMainExecutor); + if (mInflateSynchronously) { + mInflationTaskLegacy.onPostExecute(mInflationTaskLegacy.doInBackground()); + } else { + mInflationTaskLegacy.execute(); + } } } - private boolean isBubbleLoading() { - return mInflationTask != null && mInflationTask.getStatus() != FINISHED; - } - boolean isInflated() { return (mIconView != null && mExpandedView != null) || mBubbleBarExpandedView != null; } void stopInflation() { - if (mInflationTask == null) { - return; + if (Flags.bubbleViewInfoExecutors()) { + if (mInflationTask == null) { + return; + } + mInflationTask.cancel(true /* mayInterruptIfRunning */); + } else { + if (mInflationTaskLegacy == null) { + return; + } + mInflationTaskLegacy.cancel(true /* mayInterruptIfRunning */); } - mInflationTask.cancel(true /* mayInterruptIfRunning */); } void setViewInfo(BubbleViewInfoTask.BubbleViewInfo info) { @@ -626,6 +667,42 @@ public class Bubble implements BubbleViewProvider { } /** + * @deprecated {@link BubbleViewInfoTaskLegacy} is deprecated. + */ + @Deprecated + void setViewInfoLegacy(BubbleViewInfoTaskLegacy.BubbleViewInfo info) { + if (!isInflated()) { + mIconView = info.imageView; + mExpandedView = info.expandedView; + mBubbleBarExpandedView = info.bubbleBarExpandedView; + } + + mShortcutInfo = info.shortcutInfo; + mAppName = info.appName; + if (mTitle == null) { + mTitle = mAppName; + } + mFlyoutMessage = info.flyoutMessage; + + mBadgeBitmap = info.badgeBitmap; + mRawBadgeBitmap = info.rawBadgeBitmap; + mBubbleBitmap = info.bubbleBitmap; + + mDotColor = info.dotColor; + mDotPath = info.dotPath; + + if (mExpandedView != null) { + mExpandedView.update(this /* bubble */); + } + if (mBubbleBarExpandedView != null) { + mBubbleBarExpandedView.update(this /* bubble */); + } + if (mIconView != null) { + mIconView.setRenderedBubble(this /* bubble */); + } + } + + /** * Set visibility of bubble in the expanded state. * * <p>Note that this contents visibility doesn't affect visibility at {@link android.view.View}, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 29520efd70b0..cfe3cfad123f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -1495,7 +1495,7 @@ public class BubbleController implements ConfigurationChangeListener, b.setAppBubbleIntent(intent); } else { // App bubble does not exist, lets add and expand it - b = Bubble.createAppBubble(intent, user, icon, mMainExecutor); + b = Bubble.createAppBubble(intent, user, icon, mMainExecutor, mBackgroundExecutor); } ProtoLog.d(WM_SHELL_BUBBLES, "inflateAndAdd %s", appBubbleKey); b.setShouldAutoExpand(true); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index 3c6c6fa0d8d5..4ad1802cba7f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -43,6 +43,8 @@ import com.android.wm.shell.R; import com.android.wm.shell.bubbles.Bubbles.DismissReason; import com.android.wm.shell.common.bubbles.BubbleBarUpdate; import com.android.wm.shell.common.bubbles.RemovedBubble; +import com.android.wm.shell.shared.annotations.ShellBackgroundThread; +import com.android.wm.shell.shared.annotations.ShellMainThread; import java.io.PrintWriter; import java.util.ArrayList; @@ -201,6 +203,7 @@ public class BubbleData { private final BubblePositioner mPositioner; private final BubbleEducationController mEducationController; private final Executor mMainExecutor; + private final Executor mBgExecutor; /** Bubbles that are actively in the stack. */ private final List<Bubble> mBubbles; /** Bubbles that aged out to overflow. */ @@ -246,12 +249,14 @@ public class BubbleData { private HashMap<String, String> mSuppressedGroupKeys = new HashMap<>(); public BubbleData(Context context, BubbleLogger bubbleLogger, BubblePositioner positioner, - BubbleEducationController educationController, Executor mainExecutor) { + BubbleEducationController educationController, @ShellMainThread Executor mainExecutor, + @ShellBackgroundThread Executor bgExecutor) { mContext = context; mLogger = bubbleLogger; mPositioner = positioner; mEducationController = educationController; mMainExecutor = mainExecutor; + mBgExecutor = bgExecutor; mOverflow = new BubbleOverflow(context, positioner); mBubbles = new ArrayList<>(); mOverflowBubbles = new ArrayList<>(); @@ -431,7 +436,8 @@ public class BubbleData { bubbleToReturn = new Bubble(entry, mBubbleMetadataFlagListener, mCancelledListener, - mMainExecutor); + mMainExecutor, + mBgExecutor); } else { // If there's no entry it must be a persisted bubble bubbleToReturn = persistedBubble; @@ -450,7 +456,7 @@ public class BubbleData { String bubbleKey = Bubble.getBubbleKeyForShortcut(info); Bubble bubbleToReturn = findAndRemoveBubbleFromOverflow(bubbleKey); if (bubbleToReturn == null) { - bubbleToReturn = Bubble.createShortcutBubble(info, mMainExecutor); + bubbleToReturn = Bubble.createShortcutBubble(info, mMainExecutor, mBgExecutor); } return bubbleToReturn; } @@ -461,7 +467,7 @@ public class BubbleData { user); Bubble bubbleToReturn = findAndRemoveBubbleFromOverflow(bubbleKey); if (bubbleToReturn == null) { - bubbleToReturn = Bubble.createAppBubble(intent, user, null, mMainExecutor); + bubbleToReturn = Bubble.createAppBubble(intent, user, null, mMainExecutor, mBgExecutor); } return bubbleToReturn; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt index df12999afc9d..818ba45bec42 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt @@ -31,6 +31,9 @@ import com.android.wm.shell.bubbles.storage.BubbleEntity import com.android.wm.shell.bubbles.storage.BubblePersistentRepository import com.android.wm.shell.bubbles.storage.BubbleVolatileRepository import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.shared.annotations.ShellBackgroundThread +import com.android.wm.shell.shared.annotations.ShellMainThread +import java.util.concurrent.Executor import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -41,7 +44,8 @@ import kotlinx.coroutines.yield class BubbleDataRepository( private val launcherApps: LauncherApps, - private val mainExecutor: ShellExecutor, + @ShellMainThread private val mainExecutor: ShellExecutor, + @ShellBackgroundThread private val bgExecutor: Executor, private val persistentRepository: BubblePersistentRepository, ) { private val volatileRepository = BubbleVolatileRepository(launcherApps) @@ -259,8 +263,8 @@ class BubbleDataRepository( entity.locus, entity.isDismissable, mainExecutor, - bubbleMetadataFlagListener - ) + bgExecutor, + bubbleMetadataFlagListener) } } mainExecutor.execute { cb(bubbles) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java index 69119cf4338e..03a2efd902f9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java @@ -54,6 +54,7 @@ import java.util.concurrent.Executor; /** * Simple task to inflate views & load necessary info to display a bubble. */ +// TODO(b/353894869): switch to executors public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask.BubbleViewInfo> { private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleViewInfoTask" : TAG_BUBBLES; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java new file mode 100644 index 000000000000..5cfebf8f1647 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java @@ -0,0 +1,346 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.bubbles; + +import static com.android.wm.shell.bubbles.BadgedImageView.DEFAULT_PATH_SIZE; +import static com.android.wm.shell.bubbles.BadgedImageView.WHITE_SCRIM_ALPHA; +import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; +import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ShortcutInfo; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Path; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.os.AsyncTask; +import android.util.Log; +import android.util.PathParser; +import android.view.LayoutInflater; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.graphics.ColorUtils; +import com.android.launcher3.icons.BitmapInfo; +import com.android.launcher3.icons.BubbleIconFactory; +import com.android.wm.shell.R; +import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView; +import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; + +import java.lang.ref.WeakReference; +import java.util.Objects; +import java.util.concurrent.Executor; + +/** + * Simple task to inflate views & load necessary info to display a bubble. + * + * @deprecated Deprecated since this is using an AsyncTask. Use {@link BubbleViewInfoTask} instead. + */ +@Deprecated +// TODO(b/353894869): remove once flag for loading view info with executors rolls out +public class BubbleViewInfoTaskLegacy extends + AsyncTask<Void, Void, BubbleViewInfoTaskLegacy.BubbleViewInfo> { + private static final String TAG = + TAG_WITH_CLASS_NAME ? "BubbleViewInfoTaskLegacy" : TAG_BUBBLES; + + + /** + * Callback to find out when the bubble has been inflated & necessary data loaded. + */ + public interface Callback { + /** + * Called when data has been loaded for the bubble. + */ + void onBubbleViewsReady(Bubble bubble); + } + + private Bubble mBubble; + private WeakReference<Context> mContext; + private WeakReference<BubbleExpandedViewManager> mExpandedViewManager; + private WeakReference<BubbleTaskViewFactory> mTaskViewFactory; + private WeakReference<BubblePositioner> mPositioner; + private WeakReference<BubbleStackView> mStackView; + private WeakReference<BubbleBarLayerView> mLayerView; + private BubbleIconFactory mIconFactory; + private boolean mSkipInflation; + private Callback mCallback; + private Executor mMainExecutor; + + /** + * Creates a task to load information for the provided {@link Bubble}. Once all info + * is loaded, {@link Callback} is notified. + */ + BubbleViewInfoTaskLegacy(Bubble b, + Context context, + BubbleExpandedViewManager expandedViewManager, + BubbleTaskViewFactory taskViewFactory, + BubblePositioner positioner, + @Nullable BubbleStackView stackView, + @Nullable BubbleBarLayerView layerView, + BubbleIconFactory factory, + boolean skipInflation, + Callback c, + Executor mainExecutor) { + mBubble = b; + mContext = new WeakReference<>(context); + mExpandedViewManager = new WeakReference<>(expandedViewManager); + mTaskViewFactory = new WeakReference<>(taskViewFactory); + mPositioner = new WeakReference<>(positioner); + mStackView = new WeakReference<>(stackView); + mLayerView = new WeakReference<>(layerView); + mIconFactory = factory; + mSkipInflation = skipInflation; + mCallback = c; + mMainExecutor = mainExecutor; + } + + @Override + protected BubbleViewInfo doInBackground(Void... voids) { + if (!verifyState()) { + // If we're in an inconsistent state, then switched modes and should just bail now. + return null; + } + if (mLayerView.get() != null) { + return BubbleViewInfo.populateForBubbleBar(mContext.get(), mExpandedViewManager.get(), + mTaskViewFactory.get(), mPositioner.get(), mLayerView.get(), mIconFactory, + mBubble, mSkipInflation); + } else { + return BubbleViewInfo.populate(mContext.get(), mExpandedViewManager.get(), + mTaskViewFactory.get(), mPositioner.get(), mStackView.get(), mIconFactory, + mBubble, mSkipInflation); + } + } + + @Override + protected void onPostExecute(BubbleViewInfo viewInfo) { + if (isCancelled() || viewInfo == null) { + return; + } + + mMainExecutor.execute(() -> { + if (!verifyState()) { + return; + } + mBubble.setViewInfoLegacy(viewInfo); + if (mCallback != null) { + mCallback.onBubbleViewsReady(mBubble); + } + }); + } + + private boolean verifyState() { + if (mExpandedViewManager.get().isShowingAsBubbleBar()) { + return mLayerView.get() != null; + } else { + return mStackView.get() != null; + } + } + + /** + * Info necessary to render a bubble. + */ + @VisibleForTesting + public static class BubbleViewInfo { + // TODO(b/273312602): for foldables it might make sense to populate all of the views + + // Always populated + ShortcutInfo shortcutInfo; + String appName; + Bitmap rawBadgeBitmap; + + // Only populated when showing in taskbar + @Nullable BubbleBarExpandedView bubbleBarExpandedView; + + // These are only populated when not showing in taskbar + @Nullable BadgedImageView imageView; + @Nullable BubbleExpandedView expandedView; + int dotColor; + Path dotPath; + @Nullable Bubble.FlyoutMessage flyoutMessage; + Bitmap bubbleBitmap; + Bitmap badgeBitmap; + + @Nullable + public static BubbleViewInfo populateForBubbleBar(Context c, + BubbleExpandedViewManager expandedViewManager, + BubbleTaskViewFactory taskViewFactory, + BubblePositioner positioner, + BubbleBarLayerView layerView, + BubbleIconFactory iconFactory, + Bubble b, + boolean skipInflation) { + BubbleViewInfo info = new BubbleViewInfo(); + + if (!skipInflation && !b.isInflated()) { + BubbleTaskView bubbleTaskView = b.getOrCreateBubbleTaskView(taskViewFactory); + LayoutInflater inflater = LayoutInflater.from(c); + info.bubbleBarExpandedView = (BubbleBarExpandedView) inflater.inflate( + R.layout.bubble_bar_expanded_view, layerView, false /* attachToRoot */); + info.bubbleBarExpandedView.initialize( + expandedViewManager, positioner, false /* isOverflow */, bubbleTaskView); + } + + if (!populateCommonInfo(info, c, b, iconFactory)) { + // if we failed to update common fields return null + return null; + } + + return info; + } + + @VisibleForTesting + @Nullable + public static BubbleViewInfo populate(Context c, + BubbleExpandedViewManager expandedViewManager, + BubbleTaskViewFactory taskViewFactory, + BubblePositioner positioner, + BubbleStackView stackView, + BubbleIconFactory iconFactory, + Bubble b, + boolean skipInflation) { + BubbleViewInfo info = new BubbleViewInfo(); + + // View inflation: only should do this once per bubble + if (!skipInflation && !b.isInflated()) { + LayoutInflater inflater = LayoutInflater.from(c); + info.imageView = (BadgedImageView) inflater.inflate( + R.layout.bubble_view, stackView, false /* attachToRoot */); + info.imageView.initialize(positioner); + + BubbleTaskView bubbleTaskView = b.getOrCreateBubbleTaskView(taskViewFactory); + info.expandedView = (BubbleExpandedView) inflater.inflate( + R.layout.bubble_expanded_view, stackView, false /* attachToRoot */); + info.expandedView.initialize( + expandedViewManager, stackView, positioner, false /* isOverflow */, + bubbleTaskView); + } + + if (!populateCommonInfo(info, c, b, iconFactory)) { + // if we failed to update common fields return null + return null; + } + + // Flyout + info.flyoutMessage = b.getFlyoutMessage(); + if (info.flyoutMessage != null) { + info.flyoutMessage.senderAvatar = + loadSenderAvatar(c, info.flyoutMessage.senderIcon); + } + return info; + } + } + + /** + * Modifies the given {@code info} object and populates common fields in it. + * + * <p>This method returns {@code true} if the update was successful and {@code false} otherwise. + * Callers should assume that the info object is unusable if the update was unsuccessful. + */ + private static boolean populateCommonInfo( + BubbleViewInfo info, Context c, Bubble b, BubbleIconFactory iconFactory) { + if (b.getShortcutInfo() != null) { + info.shortcutInfo = b.getShortcutInfo(); + } + + // App name & app icon + PackageManager pm = BubbleController.getPackageManagerForUser(c, + b.getUser().getIdentifier()); + ApplicationInfo appInfo; + Drawable badgedIcon; + Drawable appIcon; + try { + appInfo = pm.getApplicationInfo( + b.getPackageName(), + PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.MATCH_DISABLED_COMPONENTS + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.MATCH_DIRECT_BOOT_AWARE); + if (appInfo != null) { + info.appName = String.valueOf(pm.getApplicationLabel(appInfo)); + } + appIcon = pm.getApplicationIcon(b.getPackageName()); + badgedIcon = pm.getUserBadgedIcon(appIcon, b.getUser()); + } catch (PackageManager.NameNotFoundException exception) { + // If we can't find package... don't think we should show the bubble. + Log.w(TAG, "Unable to find package: " + b.getPackageName()); + return false; + } + + Drawable bubbleDrawable = null; + try { + // Badged bubble image + bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo, + b.getIcon()); + } catch (Exception e) { + // If we can't create the icon we'll default to the app icon + Log.w(TAG, "Exception creating icon for the bubble: " + b.getKey()); + } + + if (bubbleDrawable == null) { + // Default to app icon + bubbleDrawable = appIcon; + } + + BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon, + b.isImportantConversation()); + info.badgeBitmap = badgeBitmapInfo.icon; + // Raw badge bitmap never includes the important conversation ring + info.rawBadgeBitmap = b.isImportantConversation() + ? iconFactory.getBadgeBitmap(badgedIcon, false).icon + : badgeBitmapInfo.icon; + + float[] bubbleBitmapScale = new float[1]; + info.bubbleBitmap = iconFactory.getBubbleBitmap(bubbleDrawable, bubbleBitmapScale); + + // Dot color & placement + Path iconPath = PathParser.createPathFromPathData( + c.getResources().getString(com.android.internal.R.string.config_icon_mask)); + Matrix matrix = new Matrix(); + float scale = bubbleBitmapScale[0]; + float radius = DEFAULT_PATH_SIZE / 2f; + matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */, + radius /* pivot y */); + iconPath.transform(matrix); + info.dotPath = iconPath; + info.dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color, + Color.WHITE, WHITE_SCRIM_ALPHA); + return true; + } + + @Nullable + static Drawable loadSenderAvatar(@NonNull final Context context, @Nullable final Icon icon) { + Objects.requireNonNull(context); + if (icon == null) return null; + try { + if (icon.getType() == Icon.TYPE_URI + || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) { + context.grantUriPermission(context.getPackageName(), + icon.getUri(), Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + return icon.loadDrawable(context); + } catch (Exception e) { + Log.w(TAG, "loadSenderAvatar failed: " + e.getMessage()); + return null; + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 955361ffac1b..63a25730f1aa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -164,8 +164,10 @@ public abstract class WMShellModule { BubbleLogger logger, BubblePositioner positioner, BubbleEducationController educationController, - @ShellMainThread ShellExecutor mainExecutor) { - return new BubbleData(context, logger, positioner, educationController, mainExecutor); + @ShellMainThread ShellExecutor mainExecutor, + @ShellBackgroundThread ShellExecutor bgExecutor) { + return new BubbleData(context, logger, positioner, educationController, mainExecutor, + bgExecutor); } // Note: Handler needed for LauncherApps.register @@ -198,7 +200,7 @@ public abstract class WMShellModule { IWindowManager wmService) { return new BubbleController(context, shellInit, shellCommandHandler, shellController, data, null /* synchronizer */, floatingContentCoordinator, - new BubbleDataRepository(launcherApps, mainExecutor, + new BubbleDataRepository(launcherApps, mainExecutor, bgExecutor, new BubblePersistentRepository(context)), statusBarService, windowManager, windowManagerShellWrapper, userManager, launcherApps, logger, taskStackListener, organizer, positioner, displayController, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt index 05c9d02a0de7..b6f2a25ff1c0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.desktopmode +import com.android.internal.annotations.VisibleForTesting import com.android.internal.protolog.ProtoLog import com.android.internal.util.FrameworkStatsLog import com.android.wm.shell.protolog.ShellProtoLogGroup @@ -128,7 +129,10 @@ class DesktopModeEventLogger { /* task_y */ taskUpdate.taskY, /* session_id */ - sessionId + sessionId, + taskUpdate.minimizeReason?.reason ?: UNSET_MINIMIZE_REASON, + taskUpdate.unminimizeReason?.reason ?: UNSET_UNMINIMIZE_REASON, + ) } @@ -142,6 +146,8 @@ class DesktopModeEventLogger { * @property taskWidth width of the task in px * @property taskX x-coordinate of the top-left corner * @property taskY y-coordinate of the top-left corner + * @property minimizeReason the reason the task was minimized + * @property unminimizeEvent the reason the task was unminimized * */ data class TaskUpdate( @@ -151,8 +157,52 @@ class DesktopModeEventLogger { val taskWidth: Int, val taskX: Int, val taskY: Int, + val minimizeReason: MinimizeReason? = null, + val unminimizeReason: UnminimizeReason? = null, ) + // Default value used when the task was not minimized. + @VisibleForTesting + const val UNSET_MINIMIZE_REASON = + FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__UNSET_MINIMIZE + + /** The reason a task was minimized. */ + enum class MinimizeReason (val reason: Int) { + TASK_LIMIT( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_TASK_LIMIT + ), + MINIMIZE_BUTTON( // TODO(b/356843241): use this enum value + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_BUTTON + ), + } + + // Default value used when the task was not unminimized. + @VisibleForTesting + const val UNSET_UNMINIMIZE_REASON = + FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNSET_UNMINIMIZE + + /** The reason a task was unminimized. */ + enum class UnminimizeReason (val reason: Int) { + UNKNOWN( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNMINIMIZE_UNKNOWN + ), + TASKBAR_TAP( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNMINIMIZE_TASKBAR_TAP + ), + ALT_TAB( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNMINIMIZE_ALT_TAB + ), + TASK_LAUNCH( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNMINIMIZE_TASK_LAUNCH + ), + } + /** * Enum EnterReason mapped to the EnterReason definition in * stats/atoms/desktopmode/desktopmode_extensions_atoms.proto diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp index 35b2f56bca92..a231e381beda 100644 --- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp @@ -15,6 +15,7 @@ // package { + default_team: "trendy_team_multitasking_windowing", // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import // all of the 'license_kinds' from "frameworks_base_license" @@ -30,6 +31,46 @@ filegroup { ], } +java_library { + name: "WMShellFlickerTestsSplitScreenBase", + srcs: [ + ":WMShellFlickerTestsSplitScreenBase-src", + ], + static_libs: [ + "WMShellFlickerTestsBase", + "wm-shell-flicker-utils", + "androidx.test.ext.junit", + "flickertestapplib", + "flickerlib", + "flickerlib-helpers", + "flickerlib-trace_processor_shell", + "platform-test-annotations", + "wm-flicker-common-app-helpers", + "wm-flicker-common-assertions", + "launcher-helper-lib", + "launcher-aosp-tapl", + ], +} + +android_test { + name: "WMShellFlickerTestsSplitScreen", + defaults: ["WMShellFlickerTestsDefault"], + manifest: "AndroidManifest.xml", + package_name: "com.android.wm.shell.flicker.splitscreen", + instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen", + test_config_template: "AndroidTestTemplate.xml", + srcs: ["src/**/*.kt"], + exclude_srcs: ["src/**/benchmark/*.kt"], + static_libs: [ + "WMShellFlickerTestsBase", + "WMShellFlickerTestsSplitScreenBase", + ], + data: ["trace_config/*"], +} + +//////////////////////////////////////////////////////////////////////////////// +// Begin cleanup after gcl merges + filegroup { name: "WMShellFlickerTestsSplitScreenGroup1-src", srcs: [ @@ -61,27 +102,6 @@ filegroup { ], } -java_library { - name: "WMShellFlickerTestsSplitScreenBase", - srcs: [ - ":WMShellFlickerTestsSplitScreenBase-src", - ], - static_libs: [ - "WMShellFlickerTestsBase", - "wm-shell-flicker-utils", - "androidx.test.ext.junit", - "flickertestapplib", - "flickerlib", - "flickerlib-helpers", - "flickerlib-trace_processor_shell", - "platform-test-annotations", - "wm-flicker-common-app-helpers", - "wm-flicker-common-assertions", - "launcher-helper-lib", - "launcher-aosp-tapl", - ], -} - android_test { name: "WMShellFlickerTestsSplitScreenGroup1", defaults: ["WMShellFlickerTestsDefault"], @@ -154,3 +174,156 @@ android_test { ], data: ["trace_config/*"], } + +//////////////////////////////////////////////////////////////////////////////// +// End cleanup after gcl merges + +//////////////////////////////////////////////////////////////////////////////// +// Begin breakdowns for FlickerTestsRotation module + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-CatchAll", + base: "WMShellFlickerTestsSplitScreen", + exclude_filters: [ + "com.android.wm.shell.flicker.splitscreen.CopyContentInSplit", + "com.android.wm.shell.flicker.splitscreen.DismissSplitScreenByDivider", + "com.android.wm.shell.flicker.splitscreen.DismissSplitScreenByGoHome", + "com.android.wm.shell.flicker.splitscreen.DragDividerToResize", + "com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromAllApps", + "com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromNotification", + "com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromShortcut", + "com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromTaskbar", + "com.android.wm.shell.flicker.splitscreen.EnterSplitScreenFromOverview", + "com.android.wm.shell.flicker.splitscreen.MultipleShowImeRequestsInSplitScreen", + "com.android.wm.shell.flicker.splitscreen.SwitchAppByDoubleTapDivider", + "com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromAnotherApp", + "com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromHome", + "com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromRecent", + "com.android.wm.shell.flicker.splitscreen.SwitchBetweenSplitPairs", + "com.android.wm.shell.flicker.splitscreen.SwitchBetweenSplitPairsNoPip", + "com.android.wm.shell.flicker.splitscreen.", + ], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-CopyContentInSplit", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.CopyContentInSplit"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-DismissSplitScreenByDivider", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.DismissSplitScreenByDivider"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-DismissSplitScreenByGoHome", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.DismissSplitScreenByGoHome"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-DragDividerToResize", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.DragDividerToResize"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-EnterSplitScreenByDragFromAllApps", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromAllApps"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-EnterSplitScreenByDragFromNotification", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromNotification"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-EnterSplitScreenByDragFromShortcut", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromShortcut"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-EnterSplitScreenByDragFromTaskbar", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromTaskbar"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-EnterSplitScreenFromOverview", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.EnterSplitScreenFromOverview"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-MultipleShowImeRequestsInSplitScreen", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.MultipleShowImeRequestsInSplitScreen"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-SwitchAppByDoubleTapDivider", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchAppByDoubleTapDivider"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-SwitchBackToSplitFromAnotherApp", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromAnotherApp"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-SwitchBackToSplitFromHome", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromHome"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-SwitchBackToSplitFromRecent", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromRecent"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-SwitchBetweenSplitPairs", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchBetweenSplitPairs"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-SwitchBetweenSplitPairsNoPip", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchBetweenSplitPairsNoPip"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsSplitScreen-UnlockKeyguardToSplitScreen", + base: "WMShellFlickerTestsSplitScreen", + include_filters: ["com.android.wm.shell.flicker.splitscreen.UnlockKeyguardToSplitScreen"], + test_suites: ["device-tests"], +} + +// End breakdowns for FlickerTestsRotation module +//////////////////////////////////////////////////////////////////////////////// diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp b/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp index e151ab2c5878..29a9f1050b25 100644 --- a/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp @@ -15,6 +15,7 @@ // package { + default_team: "trendy_team_app_compat", // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import // all of the 'license_kinds' from "frameworks_base_license" @@ -23,6 +24,9 @@ package { default_applicable_licenses: ["frameworks_base_license"], } +//////////////////////////////////////////////////////////////////////////////// +// Begin cleanup after gcl merge + filegroup { name: "WMShellFlickerTestsAppCompat-src", srcs: [ @@ -41,3 +45,80 @@ android_test { static_libs: ["WMShellFlickerTestsBase"], data: ["trace_config/*"], } + +//////////////////////////////////////////////////////////////////////////////// +// End cleanup after gcl merge + +android_test { + name: "WMShellFlickerTestsAppCompat", + defaults: ["WMShellFlickerTestsDefault"], + manifest: "AndroidManifest.xml", + package_name: "com.android.wm.shell.flicker", + instrumentation_target_package: "com.android.wm.shell.flicker", + test_config_template: "AndroidTestTemplate.xml", + srcs: ["src/**/*.kt"], + static_libs: ["WMShellFlickerTestsBase"], + data: ["trace_config/*"], +} + +//////////////////////////////////////////////////////////////////////////////// +// Begin breakdowns for WMShellFlickerTestsAppCompat module + +test_module_config { + name: "WMShellFlickerTestsAppCompat-CatchAll", + base: "WMShellFlickerTestsAppCompat", + exclude_filters: [ + "com.android.wm.shell.flicker.appcompat.OpenAppInSizeCompatModeTest", + "com.android.wm.shell.flicker.appcompat.OpenTransparentActivityTest", + "com.android.wm.shell.flicker.appcompat.QuickSwitchLauncherToLetterboxAppTest", + "com.android.wm.shell.flicker.appcompat.RepositionFixedPortraitAppTest", + "com.android.wm.shell.flicker.appcompat.RestartAppInSizeCompatModeTest", + "com.android.wm.shell.flicker.appcompat.RotateImmersiveAppInFullscreenTest", + ], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsAppCompat-OpenAppInSizeCompatModeTest", + base: "WMShellFlickerTestsAppCompat", + include_filters: ["com.android.wm.shell.flicker.appcompat.OpenAppInSizeCompatModeTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsAppCompat-OpenTransparentActivityTest", + base: "WMShellFlickerTestsAppCompat", + include_filters: ["com.android.wm.shell.flicker.appcompat.OpenTransparentActivityTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsAppCompat-QuickSwitchLauncherToLetterboxAppTest", + base: "WMShellFlickerTestsAppCompat", + include_filters: ["com.android.wm.shell.flicker.appcompat.QuickSwitchLauncherToLetterboxAppTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsAppCompat-RepositionFixedPortraitAppTest", + base: "WMShellFlickerTestsAppCompat", + include_filters: ["com.android.wm.shell.flicker.appcompat.RepositionFixedPortraitAppTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsAppCompat-RestartAppInSizeCompatModeTest", + base: "WMShellFlickerTestsAppCompat", + include_filters: ["com.android.wm.shell.flicker.appcompat.RestartAppInSizeCompatModeTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsAppCompat-RotateImmersiveAppInFullscreenTest", + base: "WMShellFlickerTestsAppCompat", + include_filters: ["com.android.wm.shell.flicker.appcompat.RotateImmersiveAppInFullscreenTest"], + test_suites: ["device-tests"], +} + +// End breakdowns for FlickerTestsRotation module +//////////////////////////////////////////////////////////////////////////////// diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp b/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp index f0b4f1faad46..2ff7ab231c28 100644 --- a/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp @@ -15,6 +15,7 @@ // package { + default_team: "trendy_team_multitasking_windowing", // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import // all of the 'license_kinds' from "frameworks_base_license" @@ -34,3 +35,57 @@ android_test { static_libs: ["WMShellFlickerTestsBase"], data: ["trace_config/*"], } + +//////////////////////////////////////////////////////////////////////////////// +// Begin breakdowns for WMShellFlickerTestsBubbles module + +test_module_config { + name: "WMShellFlickerTestsBubbles-CatchAll", + base: "WMShellFlickerTestsBubbles", + exclude_filters: [ + "com.android.wm.shell.flicker.bubble.ChangeActiveActivityFromBubbleTest", + "com.android.wm.shell.flicker.bubble.DragToDismissBubbleScreenTest", + "com.android.wm.shell.flicker.bubble.OpenActivityFromBubbleOnLocksreenTest", + "com.android.wm.shell.flicker.bubble.OpenActivityFromBubbleTest", + "com.android.wm.shell.flicker.bubble.SendBubbleNotificationTest", + ], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsBubbles-ChangeActiveActivityFromBubbleTest", + base: "WMShellFlickerTestsBubbles", + include_filters: ["com.android.wm.shell.flicker.bubble.ChangeActiveActivityFromBubbleTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsBubbles-DragToDismissBubbleScreenTest", + base: "WMShellFlickerTestsBubbles", + include_filters: ["com.android.wm.shell.flicker.bubble.DragToDismissBubbleScreenTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsBubbles-OpenActivityFromBubbleOnLocksreenTest", + base: "WMShellFlickerTestsBubbles", + include_filters: ["com.android.wm.shell.flicker.bubble.OpenActivityFromBubbleOnLocksreenTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsBubbles-OpenActivityFromBubbleTest", + base: "WMShellFlickerTestsBubbles", + include_filters: ["com.android.wm.shell.flicker.bubble.OpenActivityFromBubbleTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsBubbles-SendBubbleNotificationTest", + base: "WMShellFlickerTestsBubbles", + include_filters: ["com.android.wm.shell.flicker.bubble.SendBubbleNotificationTest"], + test_suites: ["device-tests"], +} + +// End breakdowns for WMShellFlickerTestsBubbles module +//////////////////////////////////////////////////////////////////////////////// diff --git a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp index faeb342a44be..4165ed093929 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp @@ -15,6 +15,7 @@ // package { + default_team: "trendy_team_multitasking_windowing", // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import // all of the 'license_kinds' from "frameworks_base_license" @@ -24,6 +25,14 @@ package { } filegroup { + name: "WMShellFlickerTestsPipApps-src", + srcs: ["src/**/apps/*.kt"], +} + +//////////////////////////////////////////////////////////////////////////////// +// Begin cleanup after gcl merges + +filegroup { name: "WMShellFlickerTestsPip1-src", srcs: [ "src/**/A*.kt", @@ -52,11 +61,6 @@ filegroup { srcs: ["src/**/common/*.kt"], } -filegroup { - name: "WMShellFlickerTestsPipApps-src", - srcs: ["src/**/apps/*.kt"], -} - android_test { name: "WMShellFlickerTestsPip1", defaults: ["WMShellFlickerTestsDefault"], @@ -107,6 +111,21 @@ android_test { data: ["trace_config/*"], } +//////////////////////////////////////////////////////////////////////////////// +// End cleanup after gcl merges + +android_test { + name: "WMShellFlickerTestsPip", + defaults: ["WMShellFlickerTestsDefault"], + manifest: "AndroidManifest.xml", + package_name: "com.android.wm.shell.flicker.pip", + instrumentation_target_package: "com.android.wm.shell.flicker.pip", + test_config_template: "AndroidTestTemplate.xml", + srcs: ["src/**/*.kt"], + static_libs: ["WMShellFlickerTestsBase"], + data: ["trace_config/*"], +} + android_test { name: "WMShellFlickerTestsPipApps", defaults: ["WMShellFlickerTestsDefault"], @@ -146,3 +165,185 @@ csuite_test { test_plan_include: "csuitePlan.xml", test_config_template: "csuiteDefaultTemplate.xml", } + +//////////////////////////////////////////////////////////////////////////////// +// Begin breakdowns for WMShellFlickerTestsPip module + +test_module_config { + name: "WMShellFlickerTestsPip-CatchAll", + base: "WMShellFlickerTestsPip", + exclude_filters: [ + "com.android.wm.shell.flicker.pip.AutoEnterPipOnGoToHomeTest", + "com.android.wm.shell.flicker.pip.AutoEnterPipWithSourceRectHintTest", + "com.android.wm.shell.flicker.pip.ClosePipBySwipingDownTest", + "com.android.wm.shell.flicker.pip.ClosePipWithDismissButtonTest", + "com.android.wm.shell.flicker.pip.EnterPipOnUserLeaveHintTest", + "com.android.wm.shell.flicker.pip.EnterPipViaAppUiButtonTest", + "com.android.wm.shell.flicker.pip.ExitPipToAppViaExpandButtonTest", + "com.android.wm.shell.flicker.pip.ExitPipToAppViaIntentTest", + "com.android.wm.shell.flicker.pip.ExpandPipOnDoubleClickTest", + "com.android.wm.shell.flicker.pip.ExpandPipOnPinchOpenTest", + "com.android.wm.shell.flicker.pip.FromSplitScreenAutoEnterPipOnGoToHomeTest", + "com.android.wm.shell.flicker.pip.FromSplitScreenEnterPipOnUserLeaveHintTest", + "com.android.wm.shell.flicker.pip.MovePipDownOnShelfHeightChange", + "com.android.wm.shell.flicker.pip.MovePipOnImeVisibilityChangeTest", + "com.android.wm.shell.flicker.pip.MovePipUpOnShelfHeightChangeTest", + "com.android.wm.shell.flicker.pip.PipAspectRatioChangeTest", + "com.android.wm.shell.flicker.pip.PipDragTest", + "com.android.wm.shell.flicker.pip.PipDragThenSnapTest", + "com.android.wm.shell.flicker.pip.PipPinchInTest", + "com.android.wm.shell.flicker.pip.SetRequestedOrientationWhilePinned", + "com.android.wm.shell.flicker.pip.ShowPipAndRotateDisplay", + ], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-AutoEnterPipOnGoToHomeTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.AutoEnterPipOnGoToHomeTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-AutoEnterPipWithSourceRectHintTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.AutoEnterPipWithSourceRectHintTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-ClosePipBySwipingDownTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.ClosePipBySwipingDownTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-ClosePipWithDismissButtonTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.ClosePipWithDismissButtonTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-EnterPipOnUserLeaveHintTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.EnterPipOnUserLeaveHintTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-EnterPipViaAppUiButtonTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.EnterPipViaAppUiButtonTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-ExitPipToAppViaExpandButtonTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.ExitPipToAppViaExpandButtonTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-ExitPipToAppViaIntentTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.ExitPipToAppViaIntentTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-ExpandPipOnDoubleClickTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.ExpandPipOnDoubleClickTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-ExpandPipOnPinchOpenTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.ExpandPipOnPinchOpenTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-FromSplitScreenAutoEnterPipOnGoToHomeTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.FromSplitScreenAutoEnterPipOnGoToHomeTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-FromSplitScreenEnterPipOnUserLeaveHintTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.FromSplitScreenEnterPipOnUserLeaveHintTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-MovePipDownOnShelfHeightChange", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.MovePipDownOnShelfHeightChange"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-MovePipOnImeVisibilityChangeTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.MovePipOnImeVisibilityChangeTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-MovePipUpOnShelfHeightChangeTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.MovePipUpOnShelfHeightChangeTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-PipAspectRatioChangeTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.PipAspectRatioChangeTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-PipDragTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.PipDragTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-PipDragThenSnapTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.PipDragThenSnapTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-PipPinchInTest", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.PipPinchInTest"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-SetRequestedOrientationWhilePinned", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.SetRequestedOrientationWhilePinned"], + test_suites: ["device-tests"], +} + +test_module_config { + name: "WMShellFlickerTestsPip-ShowPipAndRotateDisplay", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.ShowPipAndRotateDisplay"], + test_suites: ["device-tests"], +} + +// End breakdowns for WMShellFlickerTestsPip module +//////////////////////////////////////////////////////////////////////////////// diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt index e35995775f76..9ec62c965a14 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt @@ -124,6 +124,7 @@ class BubbleDataRepositoryTest : ShellTestCase() { private val testHandler = Handler(Looper.getMainLooper()) private val mainExecutor = HandlerExecutor(testHandler) + private val bgExecutor = HandlerExecutor(testHandler) private val launcherApps = mock<LauncherApps>() private val persistedBubbles = SparseArray<List<BubbleEntity>>() @@ -134,7 +135,8 @@ class BubbleDataRepositoryTest : ShellTestCase() { @Before fun setup() { persistentRepository = BubblePersistentRepository(mContext) - dataRepository = spy(BubbleDataRepository(launcherApps, mainExecutor, persistentRepository)) + dataRepository = + spy(BubbleDataRepository(launcherApps, mainExecutor, bgExecutor, persistentRepository)) persistedBubbles.put(0, user0BubbleEntities) persistedBubbles.put(1, user1BubbleEntities) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java index c138a2498d35..859602ec709f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java @@ -117,6 +117,8 @@ public class BubbleDataTest extends ShellTestCase { private BubbleEducationController mEducationController; @Mock private ShellExecutor mMainExecutor; + @Mock + private ShellExecutor mBgExecutor; @Captor private ArgumentCaptor<BubbleData.Update> mUpdateCaptor; @@ -144,47 +146,47 @@ public class BubbleDataTest extends ShellTestCase { when(ranking.isTextChanged()).thenReturn(true); mEntryInterruptive = createBubbleEntry(1, "interruptive", "package.d", ranking); mBubbleInterruptive = new Bubble(mEntryInterruptive, mBubbleMetadataFlagListener, null, - mMainExecutor); + mMainExecutor, mBgExecutor); mEntryDismissed = createBubbleEntry(1, "dismissed", "package.d", null); mBubbleDismissed = new Bubble(mEntryDismissed, mBubbleMetadataFlagListener, null, - mMainExecutor); + mMainExecutor, mBgExecutor); mEntryLocusId = createBubbleEntry(1, "keyLocus", "package.e", null, new LocusId("locusId1")); mBubbleLocusId = new Bubble(mEntryLocusId, mBubbleMetadataFlagListener, null /* pendingIntentCanceledListener */, - mMainExecutor); + mMainExecutor, mBgExecutor); mBubbleA1 = new Bubble(mEntryA1, mBubbleMetadataFlagListener, mPendingIntentCanceledListener, - mMainExecutor); + mMainExecutor, mBgExecutor); mBubbleA2 = new Bubble(mEntryA2, mBubbleMetadataFlagListener, mPendingIntentCanceledListener, - mMainExecutor); + mMainExecutor, mBgExecutor); mBubbleA3 = new Bubble(mEntryA3, mBubbleMetadataFlagListener, mPendingIntentCanceledListener, - mMainExecutor); + mMainExecutor, mBgExecutor); mBubbleB1 = new Bubble(mEntryB1, mBubbleMetadataFlagListener, mPendingIntentCanceledListener, - mMainExecutor); + mMainExecutor, mBgExecutor); mBubbleB2 = new Bubble(mEntryB2, mBubbleMetadataFlagListener, mPendingIntentCanceledListener, - mMainExecutor); + mMainExecutor, mBgExecutor); mBubbleB3 = new Bubble(mEntryB3, mBubbleMetadataFlagListener, mPendingIntentCanceledListener, - mMainExecutor); + mMainExecutor, mBgExecutor); mBubbleC1 = new Bubble(mEntryC1, mBubbleMetadataFlagListener, mPendingIntentCanceledListener, - mMainExecutor); + mMainExecutor, mBgExecutor); Intent appBubbleIntent = new Intent(mContext, BubblesTestActivity.class); appBubbleIntent.setPackage(mContext.getPackageName()); @@ -192,12 +194,12 @@ public class BubbleDataTest extends ShellTestCase { appBubbleIntent, new UserHandle(1), mock(Icon.class), - mMainExecutor); + mMainExecutor, mBgExecutor); mPositioner = new TestableBubblePositioner(mContext, mContext.getSystemService(WindowManager.class)); mBubbleData = new BubbleData(getContext(), mBubbleLogger, mPositioner, mEducationController, - mMainExecutor); + mMainExecutor, mBgExecutor); // Used by BubbleData to set lastAccessedTime when(mTimeSource.currentTimeMillis()).thenReturn(1000L); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java index afec1ee12341..50c4a1828026 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java @@ -61,6 +61,8 @@ public class BubbleTest extends ShellTestCase { private StatusBarNotification mSbn; @Mock private ShellExecutor mMainExecutor; + @Mock + private ShellExecutor mBgExecutor; private BubbleEntry mBubbleEntry; private Bundle mExtras; @@ -85,7 +87,8 @@ public class BubbleTest extends ShellTestCase { when(mNotif.getBubbleMetadata()).thenReturn(metadata); when(mSbn.getKey()).thenReturn("mock"); mBubbleEntry = new BubbleEntry(mSbn, null, true, false, false, false); - mBubble = new Bubble(mBubbleEntry, mBubbleMetadataFlagListener, null, mMainExecutor); + mBubble = new Bubble(mBubbleEntry, mBubbleMetadataFlagListener, null, mMainExecutor, + mBgExecutor); } @Test @@ -176,7 +179,8 @@ public class BubbleTest extends ShellTestCase { @Test public void testBubbleIsConversation_hasNoShortcut() { - Bubble bubble = new Bubble(mBubbleEntry, mBubbleMetadataFlagListener, null, mMainExecutor); + Bubble bubble = new Bubble(mBubbleEntry, mBubbleMetadataFlagListener, null, mMainExecutor, + mBgExecutor); assertThat(bubble.getShortcutInfo()).isNull(); assertThat(bubble.isConversation()).isFalse(); } @@ -199,7 +203,7 @@ public class BubbleTest extends ShellTestCase { Intent intent = new Intent(mContext, BubblesTestActivity.class); intent.setPackage(mContext.getPackageName()); Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1 /* userId */), - null /* icon */, mMainExecutor); + null /* icon */, mMainExecutor, mBgExecutor); BubbleInfo bubbleInfo = bubble.asBubbleBarBubble(); assertThat(bubble.getShortcutInfo()).isNull(); @@ -215,6 +219,6 @@ public class BubbleTest extends ShellTestCase { .build(); return new Bubble("mockKey", shortcutInfo, 10, Resources.ID_NULL, "mockTitle", 0 /* taskId */, "mockLocus", true /* isDismissible */, - mMainExecutor, mBubbleMetadataFlagListener); + mMainExecutor, mBgExecutor, mBubbleMetadataFlagListener); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt index 4a4c5e860bb2..8035e917d5b4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt @@ -70,6 +70,7 @@ class BubbleViewInfoTest : ShellTestCase() { private lateinit var bubble: Bubble private lateinit var bubbleController: BubbleController private lateinit var mainExecutor: ShellExecutor + private lateinit var bgExecutor: ShellExecutor private lateinit var bubbleStackView: BubbleStackView private lateinit var bubbleBarLayerView: BubbleBarLayerView private lateinit var bubblePositioner: BubblePositioner @@ -92,6 +93,7 @@ class BubbleViewInfoTest : ShellTestCase() { ) mainExecutor = TestShellExecutor() + bgExecutor = TestShellExecutor() val windowManager = context.getSystemService(WindowManager::class.java) val shellInit = ShellInit(mainExecutor) val shellCommandHandler = ShellCommandHandler() @@ -104,7 +106,8 @@ class BubbleViewInfoTest : ShellTestCase() { mock<BubbleLogger>(), bubblePositioner, BubbleEducationController(context), - mainExecutor + mainExecutor, + bgExecutor ) val surfaceSynchronizer = { obj: Runnable -> obj.run() } @@ -132,7 +135,7 @@ class BubbleViewInfoTest : ShellTestCase() { null, mainExecutor, mock<Handler>(), - mock<ShellExecutor>(), + bgExecutor, mock<TaskViewTransitions>(), mock<Transitions>(), mock<SyncTransactionQueue>(), @@ -256,7 +259,7 @@ class BubbleViewInfoTest : ShellTestCase() { "mockLocus", true /* isDismissible */, mainExecutor, - metadataFlagListener - ) + bgExecutor, + metadataFlagListener) } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt index 4548fcb06c55..70b366110692 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt @@ -16,14 +16,17 @@ package com.android.wm.shell.desktopmode -import com.android.dx.mockito.inline.extended.ExtendedMockito +import com.android.dx.mockito.inline.extended.ExtendedMockito.verify import com.android.internal.util.FrameworkStatsLog import com.android.modules.utils.testing.ExtendedMockitoRule import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_MINIMIZE_REASON +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_UNMINIMIZE_REASON import kotlinx.coroutines.runBlocking -import org.junit.Before import org.junit.Rule import org.junit.Test import org.mockito.kotlin.eq @@ -33,7 +36,7 @@ import org.mockito.kotlin.eq */ class DesktopModeEventLoggerTest { - private val desktopModeEventLogger = DesktopModeEventLogger() + private val desktopModeEventLogger = DesktopModeEventLogger() @JvmField @Rule @@ -44,7 +47,7 @@ class DesktopModeEventLoggerTest { fun logSessionEnter_enterReason() = runBlocking { desktopModeEventLogger.logSessionEnter(sessionId = SESSION_ID, EnterReason.UNKNOWN_ENTER) - ExtendedMockito.verify { + verify { FrameworkStatsLog.write( eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED), /* event */ @@ -63,7 +66,7 @@ class DesktopModeEventLoggerTest { fun logSessionExit_exitReason() = runBlocking { desktopModeEventLogger.logSessionExit(sessionId = SESSION_ID, ExitReason.UNKNOWN_EXIT) - ExtendedMockito.verify { + verify { FrameworkStatsLog.write( eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED), /* event */ @@ -82,7 +85,7 @@ class DesktopModeEventLoggerTest { fun logTaskAdded_taskUpdate() = runBlocking { desktopModeEventLogger.logTaskAdded(sessionId = SESSION_ID, TASK_UPDATE) - ExtendedMockito.verify { + verify { FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), /* task_event */ eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED), @@ -99,7 +102,9 @@ class DesktopModeEventLoggerTest { /* task_y */ eq(TASK_UPDATE.taskY), /* session_id */ - eq(SESSION_ID)) + eq(SESSION_ID), + eq(UNSET_MINIMIZE_REASON), + eq(UNSET_UNMINIMIZE_REASON)) } } @@ -107,7 +112,7 @@ class DesktopModeEventLoggerTest { fun logTaskRemoved_taskUpdate() = runBlocking { desktopModeEventLogger.logTaskRemoved(sessionId = SESSION_ID, TASK_UPDATE) - ExtendedMockito.verify { + verify { FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), /* task_event */ eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED), @@ -124,7 +129,9 @@ class DesktopModeEventLoggerTest { /* task_y */ eq(TASK_UPDATE.taskY), /* session_id */ - eq(SESSION_ID)) + eq(SESSION_ID), + eq(UNSET_MINIMIZE_REASON), + eq(UNSET_UNMINIMIZE_REASON)) } } @@ -132,10 +139,11 @@ class DesktopModeEventLoggerTest { fun logTaskInfoChanged_taskUpdate() = runBlocking { desktopModeEventLogger.logTaskInfoChanged(sessionId = SESSION_ID, TASK_UPDATE) - ExtendedMockito.verify { + verify { FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), /* task_event */ - eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED), + eq(FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED), /* instance_id */ eq(TASK_UPDATE.instanceId), /* uid */ @@ -149,7 +157,71 @@ class DesktopModeEventLoggerTest { /* task_y */ eq(TASK_UPDATE.taskY), /* session_id */ - eq(SESSION_ID)) + eq(SESSION_ID), + eq(UNSET_MINIMIZE_REASON), + eq(UNSET_UNMINIMIZE_REASON)) + } + } + + @Test + fun logTaskInfoChanged_logsTaskUpdateWithMinimizeReason() = runBlocking { + desktopModeEventLogger.logTaskInfoChanged(sessionId = SESSION_ID, + createTaskUpdate(minimizeReason = MinimizeReason.TASK_LIMIT)) + + verify { + FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), + /* task_event */ + eq(FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED), + /* instance_id */ + eq(TASK_UPDATE.instanceId), + /* uid */ + eq(TASK_UPDATE.uid), + /* task_height */ + eq(TASK_UPDATE.taskHeight), + /* task_width */ + eq(TASK_UPDATE.taskWidth), + /* task_x */ + eq(TASK_UPDATE.taskX), + /* task_y */ + eq(TASK_UPDATE.taskY), + /* session_id */ + eq(SESSION_ID), + /* minimize_reason */ + eq(MinimizeReason.TASK_LIMIT.reason), + /* unminimize_reason */ + eq(UNSET_UNMINIMIZE_REASON)) + } + } + + @Test + fun logTaskInfoChanged_logsTaskUpdateWithUnminimizeReason() = runBlocking { + desktopModeEventLogger.logTaskInfoChanged(sessionId = SESSION_ID, + createTaskUpdate(unminimizeReason = UnminimizeReason.TASKBAR_TAP)) + + verify { + FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), + /* task_event */ + eq(FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED), + /* instance_id */ + eq(TASK_UPDATE.instanceId), + /* uid */ + eq(TASK_UPDATE.uid), + /* task_height */ + eq(TASK_UPDATE.taskHeight), + /* task_width */ + eq(TASK_UPDATE.taskWidth), + /* task_x */ + eq(TASK_UPDATE.taskX), + /* task_y */ + eq(TASK_UPDATE.taskY), + /* session_id */ + eq(SESSION_ID), + /* minimize_reason */ + eq(UNSET_MINIMIZE_REASON), + /* unminimize_reason */ + eq(UnminimizeReason.TASKBAR_TAP.reason)) } } @@ -165,5 +237,11 @@ class DesktopModeEventLoggerTest { private val TASK_UPDATE = TaskUpdate( TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y ) + + private fun createTaskUpdate( + minimizeReason: MinimizeReason? = null, + unminimizeReason: UnminimizeReason? = null, + ) = TaskUpdate(TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y, minimizeReason, + unminimizeReason) } }
\ No newline at end of file diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig index a1f51687b077..faea6d42b156 100644 --- a/libs/hwui/aconfig/hwui_flags.aconfig +++ b/libs/hwui/aconfig/hwui_flags.aconfig @@ -2,6 +2,13 @@ package: "com.android.graphics.hwui.flags" container: "system" flag { + name: "runtime_color_filters_blenders" + namespace: "core_graphics" + description: "API for AGSL authored runtime color filters and blenders" + bug: "358126864" +} + +flag { name: "clip_shader" is_exported: true namespace: "core_graphics" diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 25c767a88b17..c36eda908d32 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -84,6 +84,7 @@ import android.util.ArrayMap; import android.util.IntArray; import android.util.Log; import android.util.Pair; +import android.util.Slog; import android.view.KeyEvent; import com.android.internal.annotations.GuardedBy; @@ -4283,7 +4284,7 @@ public class AudioManager { final OnAudioFocusChangeListener listener = fri.mRequest.getOnAudioFocusChangeListener(); if (listener != null) { - Log.d(TAG, "dispatching onAudioFocusChange(" + Slog.i(TAG, "dispatching onAudioFocusChange(" + msg.arg1 + ") to " + msg.obj); listener.onAudioFocusChange(msg.arg1); } diff --git a/nfc/api/current.txt b/nfc/api/current.txt index b0d1f71e749f..447e980adaee 100644 --- a/nfc/api/current.txt +++ b/nfc/api/current.txt @@ -220,11 +220,14 @@ package android.nfc.cardemulation { field @Deprecated public static final String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT"; field public static final String CATEGORY_OTHER = "other"; field public static final String CATEGORY_PAYMENT = "payment"; + field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final String DH = "DH"; + field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final String ESE = "ESE"; field public static final String EXTRA_CATEGORY = "category"; field public static final String EXTRA_SERVICE_COMPONENT = "component"; field public static final int SELECTION_MODE_ALWAYS_ASK = 1; // 0x1 field public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2; // 0x2 field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0 + field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final String UICC = "UICC"; } public abstract class HostApduService extends android.app.Service { diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt index ae63e1939c00..2ff9829ddfd7 100644 --- a/nfc/api/system-current.txt +++ b/nfc/api/system-current.txt @@ -75,6 +75,8 @@ package android.nfc.cardemulation { public final class CardEmulation { method @FlaggedApi("android.permission.flags.wallet_role_enabled") @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static android.content.ComponentName getPreferredPaymentService(@NonNull android.content.Context); method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<android.nfc.cardemulation.ApduServiceInfo> getServices(@NonNull String, int); + method @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public void overrideRoutingTable(@NonNull android.app.Activity, @Nullable String, @Nullable String); + method @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public void recoverRoutingTable(@NonNull android.app.Activity); } } diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl index cb97f23e813c..79f1275ec629 100644 --- a/nfc/java/android/nfc/INfcCardEmulation.aidl +++ b/nfc/java/android/nfc/INfcCardEmulation.aidl @@ -48,6 +48,6 @@ interface INfcCardEmulation boolean setServiceEnabledForCategoryOther(int userHandle, in ComponentName app, boolean status); boolean isDefaultPaymentRegistered(); - boolean overrideRoutingTable(int userHandle, String protocol, String technology); - boolean recoverRoutingTable(int userHandle); + void overrideRoutingTable(int userHandle, String protocol, String technology, in String pkg); + void recoverRoutingTable(int userHandle); } diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java index e0438ce9258c..03372b206f48 100644 --- a/nfc/java/android/nfc/cardemulation/CardEmulation.java +++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.StringDef; import android.annotation.SystemApi; import android.annotation.UserHandleAware; import android.annotation.UserIdInt; @@ -43,6 +44,8 @@ import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.util.Log; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.HashMap; import java.util.HexFormat; import java.util.List; @@ -148,6 +151,21 @@ public final class CardEmulation { * that service will be invoked directly. */ public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2; + /** + * Route to Device Host (DH). + */ + @FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE) + public static final String DH = "DH"; + /** + * Route to eSE. + */ + @FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE) + public static final String ESE = "ESE"; + /** + * Route to UICC. + */ + @FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE) + public static final String UICC = "UICC"; static boolean sIsInitialized = false; static HashMap<Context, CardEmulation> sCardEmus = new HashMap<Context, CardEmulation>(); @@ -865,11 +883,22 @@ public final class CardEmulation { sService.setServiceEnabledForCategoryOther(userId, service, status), false); } + /** @hide */ + @StringDef({ + DH, + ESE, + UICC + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ProtocolAndTechnologyRoute {} + /** * Setting NFC controller routing table, which includes Protocol Route and Technology Route, * while this Activity is in the foreground. * - * The parameter set to null can be used to keep current values for that entry. + * The parameter set to null can be used to keep current values for that entry. Either + * Protocol Route or Technology Route should be override when calling this API, otherwise + * throw {@link IllegalArgumentException}. * <p> * Example usage in an Activity that requires to set proto route to "ESE" and keep tech route: * <pre> @@ -877,26 +906,39 @@ public final class CardEmulation { * mNfcAdapter.overrideRoutingTable(this , "ESE" , null); * }</pre> * </p> - * Also activities must call this method when it goes to the background, - * with all parameters set to null. + * Also activities must call {@link #recoverRoutingTable(Activity)} + * when it goes to the background. Only the package of the + * currently preferred service (the service set as preferred by the current foreground + * application via {@link CardEmulation#setPreferredService(Activity, ComponentName)} or the + * current Default Wallet Role Holder {@link android.app.role.RoleManager#ROLE_WALLET}), + * otherwise a call to this method will fail and throw {@link SecurityException}. * @param activity The Activity that requests NFC controller routing table to be changed. * @param protocol ISO-DEP route destination, which can be "DH" or "UICC" or "ESE". * @param technology Tech-A, Tech-B route destination, which can be "DH" or "UICC" or "ESE". - * @return true if operation is successful and false otherwise - * + * @throws SecurityException if the caller is not the preferred NFC service + * @throws IllegalArgumentException if the activity is not resumed or the caller is not in the + * foreground, or both protocol route and technology route are null. + * <p> * This is a high risk API and only included to support mainline effort * @hide */ - public boolean overrideRoutingTable(Activity activity, String protocol, String technology) { - if (activity == null) { - throw new NullPointerException("activity or service or category is null"); - } + @SystemApi + @FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE) + public void overrideRoutingTable( + @NonNull Activity activity, @ProtocolAndTechnologyRoute @Nullable String protocol, + @ProtocolAndTechnologyRoute @Nullable String technology) { if (!activity.isResumed()) { throw new IllegalArgumentException("Activity must be resumed."); } - return callServiceReturn(() -> + if (protocol == null && technology == null) { + throw new IllegalArgumentException(("Both Protocol and Technology are null.")); + } + callService(() -> sService.overrideRoutingTable( - mContext.getUser().getIdentifier(), protocol, technology), false); + mContext.getUser().getIdentifier(), + protocol, + technology, + mContext.getPackageName())); } /** @@ -904,20 +946,19 @@ public final class CardEmulation { * which was changed by {@link #overrideRoutingTable(Activity, String, String)} * * @param activity The Activity that requested NFC controller routing table to be changed. - * @return true if operation is successful and false otherwise + * @throws IllegalArgumentException if the caller is not in the foreground. * * @hide */ - public boolean recoverRoutingTable(Activity activity) { - if (activity == null) { - throw new NullPointerException("activity is null"); - } + @SystemApi + @FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE) + public void recoverRoutingTable(@NonNull Activity activity) { if (!activity.isResumed()) { throw new IllegalArgumentException("Activity must be resumed."); } - return callServiceReturn(() -> + callService(() -> sService.recoverRoutingTable( - mContext.getUser().getIdentifier()), false); + mContext.getUser().getIdentifier())); } /** diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig index 5819b98dd411..0fda91d2b48e 100644 --- a/nfc/java/android/nfc/flags.aconfig +++ b/nfc/java/android/nfc/flags.aconfig @@ -133,3 +133,11 @@ flag { description: "Add Settings.ACTION_MANAGE_OTHER_NFC_SERVICES_SETTINGS" bug: "358129872" } + +flag { + name: "nfc_override_recover_routing_table" + is_exported: true + namespace: "nfc" + description: "Enable override and recover routing table" + bug: "329043523" +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveErrorFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveErrorFragment.java index d33433f3983b..2fb32a7da432 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveErrorFragment.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveErrorFragment.java @@ -16,10 +16,12 @@ package com.android.packageinstaller; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import android.app.Activity; import android.app.AlertDialog; +import android.app.BroadcastOptions; import android.app.Dialog; import android.app.DialogFragment; import android.app.PendingIntent; @@ -161,25 +163,31 @@ public class UnarchiveErrorFragment extends DialogFragment implements return; } + // Allow the error handling actvities to start in the background. + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityStartMode( + MODE_BACKGROUND_ACTIVITY_START_ALLOWED); switch (mStatus) { case PackageInstaller.UNARCHIVAL_ERROR_USER_ACTION_NEEDED: activity.startIntentSender(mExtraIntent.getIntentSender(), /* fillInIntent= */ - null, /* flagsMask= */ 0, FLAG_ACTIVITY_NEW_TASK, /* extraFlags= */ 0); + null, /* flagsMask= */ 0, FLAG_ACTIVITY_NEW_TASK, /* extraFlags= */ 0, + options.toBundle()); break; case PackageInstaller.UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE: if (mExtraIntent != null) { activity.startIntentSender(mExtraIntent.getIntentSender(), /* fillInIntent= */ - null, /* flagsMask= */ 0, FLAG_ACTIVITY_NEW_TASK, /* extraFlags= */ 0); + null, /* flagsMask= */ 0, FLAG_ACTIVITY_NEW_TASK, /* extraFlags= */ 0, + options.toBundle()); } else { Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE"); - startActivity(intent); + startActivity(intent, options.toBundle()); } break; case PackageInstaller.UNARCHIVAL_ERROR_INSTALLER_DISABLED: Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri uri = Uri.fromParts("package", mInstallerPackageName, null); intent.setData(uri); - startActivity(intent); + startActivity(intent, options.toBundle()); break; default: // Do nothing. The rest of the dialogs are purely informational. diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java index de60fdc2b47e..b3ac54a9f7bc 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java @@ -21,6 +21,8 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.media.AudioDeviceAttributes; +import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.net.Uri; import android.provider.DeviceConfig; @@ -41,9 +43,12 @@ import com.android.settingslib.flags.Flags; import com.android.settingslib.widget.AdaptiveIcon; import com.android.settingslib.widget.AdaptiveOutlineDrawable; +import com.google.common.collect.ImmutableSet; + import java.io.IOException; import java.util.List; import java.util.Locale; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -57,6 +62,9 @@ public class BluetoothUtils { public static final String BT_ADVANCED_HEADER_ENABLED = "bt_advanced_header_enabled"; private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25; private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH"; + private static final Set<Integer> SA_PROFILES = + ImmutableSet.of( + BluetoothProfile.A2DP, BluetoothProfile.LE_AUDIO, BluetoothProfile.HEARING_AID); private static ErrorListener sErrorListener; @@ -895,4 +903,62 @@ public class BluetoothUtils { } return null; } + + /** + * Gets {@link AudioDeviceAttributes} of bluetooth device for spatial audio. Returns null if + * it's not an audio device(no A2DP, LE Audio and Hearing Aid profile). + */ + @Nullable + public static AudioDeviceAttributes getAudioDeviceAttributesForSpatialAudio( + CachedBluetoothDevice cachedDevice, + @AudioManager.AudioDeviceCategory int audioDeviceCategory) { + AudioDeviceAttributes saDevice = null; + for (LocalBluetoothProfile profile : cachedDevice.getProfiles()) { + // pick first enabled profile that is compatible with spatial audio + if (SA_PROFILES.contains(profile.getProfileId()) + && profile.isEnabled(cachedDevice.getDevice())) { + switch (profile.getProfileId()) { + case BluetoothProfile.A2DP: + saDevice = + new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, + cachedDevice.getAddress()); + break; + case BluetoothProfile.LE_AUDIO: + if (audioDeviceCategory + == AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER) { + saDevice = + new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLE_SPEAKER, + cachedDevice.getAddress()); + } else { + saDevice = + new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLE_HEADSET, + cachedDevice.getAddress()); + } + + break; + case BluetoothProfile.HEARING_AID: + saDevice = + new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_HEARING_AID, + cachedDevice.getAddress()); + break; + default: + Log.i( + TAG, + "unrecognized profile for spatial audio: " + + profile.getProfileId()); + break; + } + break; + } + } + return saDevice; + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java index 20a0339b9182..58dc8c7aad6c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java @@ -108,6 +108,12 @@ public @interface DeviceSettingId { /** Device setting ID for device details footer. */ int DEVICE_SETTING_ID_DEVICE_DETAILS_FOOTER = 19; + /** Device setting ID for spatial audio group. */ + int DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE = 20; + + /** Device setting ID for "More Settings" page. */ + int DEVICE_SETTING_ID_MORE_SETTINGS = 21; + /** Device setting ID for ANC. */ int DEVICE_SETTING_ID_ANC = 1001; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItem.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItem.kt index 9ee33b0dbffe..a0fe5d275b36 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItem.kt +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItem.kt @@ -27,6 +27,7 @@ import android.os.Parcelable * @property packageName The package name for service binding. * @property className The class name for service binding. * @property intentAction The intent action for service binding. + * @property preferenceKey The preference key if it's a built-in preference. * @property extras Extra bundle */ data class DeviceSettingItem( @@ -34,6 +35,7 @@ data class DeviceSettingItem( val packageName: String, val className: String, val intentAction: String, + val preferenceKey: String? = null, val extras: Bundle = Bundle.EMPTY, ) : Parcelable { @@ -45,6 +47,7 @@ data class DeviceSettingItem( writeString(packageName) writeString(className) writeString(intentAction) + writeString(preferenceKey) writeBundle(extras) } } @@ -60,6 +63,7 @@ data class DeviceSettingItem( packageName = readString() ?: "", className = readString() ?: "", intentAction = readString() ?: "", + preferenceKey = readString() ?: "", extras = readBundle((Bundle::class.java.classLoader)) ?: Bundle.EMPTY, ) } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt index 326bb31bdb9f..ce7064c9d781 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt @@ -18,6 +18,7 @@ package com.android.settingslib.bluetooth.devicesettings.data.repository import android.bluetooth.BluetoothAdapter import android.content.Context +import android.text.TextUtils import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.bluetooth.devicesettings.ActionSwitchPreference import com.android.settingslib.bluetooth.devicesettings.DeviceSetting @@ -28,6 +29,7 @@ import com.android.settingslib.bluetooth.devicesettings.MultiTogglePreference import com.android.settingslib.bluetooth.devicesettings.ToggleInfo import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigModel +import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel @@ -76,8 +78,7 @@ class DeviceSettingRepositoryImpl( coroutineScope, backgroundCoroutineContext, ) - } - ) + }) override suspend fun getDeviceSettingsConfig( cachedDevice: CachedBluetoothDevice @@ -96,11 +97,15 @@ class DeviceSettingRepositoryImpl( DeviceSettingConfigModel( mainItems = mainContentItems.map { it.toModel() }, moreSettingsItems = moreSettingsItems.map { it.toModel() }, - moreSettingsPageFooter = moreSettingsFooter - ) + moreSettingsPageFooter = moreSettingsFooter) - private fun DeviceSettingItem.toModel(): DeviceSettingConfigItemModel = - DeviceSettingConfigItemModel(settingId) + private fun DeviceSettingItem.toModel(): DeviceSettingConfigItemModel { + return if (!TextUtils.isEmpty(preferenceKey)) { + DeviceSettingConfigItemModel.BuiltinItem(settingId, preferenceKey!!) + } else { + DeviceSettingConfigItemModel.AppProvidedItem(settingId) + } + } private fun DeviceSetting.toModel( cachedDevice: CachedBluetoothDevice, @@ -113,7 +118,7 @@ class DeviceSettingRepositoryImpl( id = settingId, title = pref.title, summary = pref.summary, - icon = pref.icon, + icon = pref.icon?.let { DeviceSettingIcon.BitmapIcon(it) }, isAllowedChangingState = pref.isAllowedChangingState, intent = pref.intent, switchState = @@ -149,5 +154,6 @@ class DeviceSettingRepositoryImpl( else -> DeviceSettingModel.Unknown(cachedDevice, settingId) } - private fun ToggleInfo.toModel(): ToggleModel = ToggleModel(label, icon) + private fun ToggleInfo.toModel(): ToggleModel = + ToggleModel(label, DeviceSettingIcon.BitmapIcon(icon)) } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt index cd597ee65bce..e97f76cca3a9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt @@ -25,9 +25,20 @@ data class DeviceSettingConfigModel( /** Items need to be shown in device details more settings page. */ val moreSettingsItems: List<DeviceSettingConfigItemModel>, /** Footer text in more settings page. */ - val moreSettingsPageFooter: String) + val moreSettingsPageFooter: String +) /** Models a device setting item in config. */ -data class DeviceSettingConfigItemModel( - @DeviceSettingId val settingId: Int, -) +sealed interface DeviceSettingConfigItemModel { + @DeviceSettingId val settingId: Int + + /** A built-in item in Settings. */ + data class BuiltinItem( + @DeviceSettingId override val settingId: Int, + val preferenceKey: String? + ) : DeviceSettingConfigItemModel + + /** A remote item provided by other apps. */ + data class AppProvidedItem(@DeviceSettingId override val settingId: Int) : + DeviceSettingConfigItemModel +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt index db782803937c..2a6321704a1a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt @@ -18,6 +18,7 @@ package com.android.settingslib.bluetooth.devicesettings.shared.model import android.content.Intent import android.graphics.Bitmap +import androidx.annotation.DrawableRes import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId @@ -32,7 +33,7 @@ sealed interface DeviceSettingModel { @DeviceSettingId override val id: Int, val title: String, val summary: String? = null, - val icon: Bitmap? = null, + val icon: DeviceSettingIcon? = null, val intent: Intent? = null, val switchState: DeviceSettingStateModel.ActionSwitchPreferenceState? = null, val isAllowedChangingState: Boolean = true, @@ -59,4 +60,12 @@ sealed interface DeviceSettingModel { } /** Models a toggle in [DeviceSettingModel.MultiTogglePreference]. */ -data class ToggleModel(val label: String, val icon: Bitmap) +data class ToggleModel(val label: String, val icon: DeviceSettingIcon) + +/** Models an icon in device settings. */ +sealed interface DeviceSettingIcon { + + data class BitmapIcon(val bitmap: Bitmap) : DeviceSettingIcon + + data class ResourceIcon(@DrawableRes val resId: Int) : DeviceSettingIcon +} diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java index 4b141e7bed25..69ef718200b9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java +++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java @@ -22,6 +22,7 @@ import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL; import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY; import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleEvent; import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleTime; +import static android.service.notification.SystemZenRules.PACKAGE_ANDROID; import static android.service.notification.ZenModeConfig.tryParseCountdownConditionId; import static android.service.notification.ZenModeConfig.tryParseEventConditionId; import static android.service.notification.ZenModeConfig.tryParseScheduleConditionId; @@ -129,7 +130,11 @@ public class ZenMode implements Parcelable { } public static ZenMode manualDndMode(AutomaticZenRule manualRule, boolean isActive) { - return new ZenMode(MANUAL_DND_MODE_ID, manualRule, + // Manual rule is owned by the system, so we set it here + AutomaticZenRule manualRuleWithPkg = new AutomaticZenRule.Builder(manualRule) + .setPackage(PACKAGE_ANDROID) + .build(); + return new ZenMode(MANUAL_DND_MODE_ID, manualRuleWithPkg, isActive ? Status.ENABLED_AND_ACTIVE : Status.ENABLED, true); } diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt index ebba7f152b90..2f8105ae461d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt @@ -63,7 +63,7 @@ typealias GroupIdToVolumes = Map<Int, Int> /** Provides audio sharing functionality. */ interface AudioSharingRepository { /** Whether the device is in audio sharing. */ - val inAudioSharing: Flow<Boolean> + val inAudioSharing: StateFlow<Boolean> /** The primary headset groupId in audio sharing. */ val primaryGroupId: StateFlow<Int> @@ -101,7 +101,7 @@ class AudioSharingRepositoryImpl( .flowOn(backgroundCoroutineContext) .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), false) - override val inAudioSharing: Flow<Boolean> = + override val inAudioSharing: StateFlow<Boolean> = isAudioSharingProfilesReady.flatMapLatest { ready -> if (ready) { btManager.profileManager.leAudioBroadcastProfile.onBroadcastStartedOrStopped @@ -113,6 +113,7 @@ class AudioSharingRepositoryImpl( flowOf(false) } } + .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), false) private val primaryChange: Flow<Unit> = callbackFlow { val callback = @@ -254,7 +255,7 @@ class AudioSharingRepositoryImpl( } class AudioSharingRepositoryEmptyImpl : AudioSharingRepository { - override val inAudioSharing: Flow<Boolean> = flowOf(false) + override val inAudioSharing: StateFlow<Boolean> = MutableStateFlow(false) override val primaryGroupId: StateFlow<Int> = MutableStateFlow(BluetoothCsipSetCoordinator.GROUP_ID_INVALID) override val secondaryGroupId: StateFlow<Int> = diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java index 4551f1eba84b..926d3cb448e8 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java @@ -31,10 +31,13 @@ import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeBroadcastReceiveState; +import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; +import android.media.AudioDeviceAttributes; +import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.net.Uri; import android.platform.test.flag.junit.SetFlagsRule; @@ -70,6 +73,9 @@ public class BluetoothUtilsTest { @Mock private BluetoothDevice mBluetoothDevice; @Mock private AudioManager mAudioManager; @Mock private PackageManager mPackageManager; + @Mock private LeAudioProfile mA2dpProfile; + @Mock private LeAudioProfile mLeAudioProfile; + @Mock private LeAudioProfile mHearingAid; @Mock private LocalBluetoothLeBroadcast mBroadcast; @Mock private LocalBluetoothProfileManager mProfileManager; @Mock private LocalBluetoothManager mLocalBluetoothManager; @@ -100,6 +106,9 @@ public class BluetoothUtilsTest { when(mLocalBluetoothManager.getProfileManager()).thenReturn(mProfileManager); when(mProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast); when(mProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant); + when(mA2dpProfile.getProfileId()).thenReturn(BluetoothProfile.A2DP); + when(mLeAudioProfile.getProfileId()).thenReturn(BluetoothProfile.LE_AUDIO); + when(mHearingAid.getProfileId()).thenReturn(BluetoothProfile.HEARING_AID); } @Test @@ -756,4 +765,84 @@ public class BluetoothUtilsTest { mContext.getContentResolver(), mLocalBluetoothManager)) .isEqualTo(mCachedBluetoothDevice); } + + @Test + public void getAudioDeviceAttributesForSpatialAudio_bleHeadset() { + String address = "11:22:33:44:55:66"; + when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); + when(mCachedBluetoothDevice.getAddress()).thenReturn(address); + when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mLeAudioProfile)); + when(mLeAudioProfile.isEnabled(mBluetoothDevice)).thenReturn(true); + + AudioDeviceAttributes attr = + BluetoothUtils.getAudioDeviceAttributesForSpatialAudio( + mCachedBluetoothDevice, AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES); + + assertThat(attr) + .isEqualTo( + new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLE_HEADSET, + address)); + } + + @Test + public void getAudioDeviceAttributesForSpatialAudio_bleSpeaker() { + String address = "11:22:33:44:55:66"; + when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); + when(mCachedBluetoothDevice.getAddress()).thenReturn(address); + when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mLeAudioProfile)); + when(mLeAudioProfile.isEnabled(mBluetoothDevice)).thenReturn(true); + + AudioDeviceAttributes attr = + BluetoothUtils.getAudioDeviceAttributesForSpatialAudio( + mCachedBluetoothDevice, AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER); + + assertThat(attr) + .isEqualTo( + new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLE_SPEAKER, + address)); + } + + @Test + public void getAudioDeviceAttributesForSpatialAudio_a2dp() { + String address = "11:22:33:44:55:66"; + when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); + when(mCachedBluetoothDevice.getAddress()).thenReturn(address); + when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mA2dpProfile)); + when(mA2dpProfile.isEnabled(mBluetoothDevice)).thenReturn(true); + + AudioDeviceAttributes attr = + BluetoothUtils.getAudioDeviceAttributesForSpatialAudio( + mCachedBluetoothDevice, AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES); + + assertThat(attr) + .isEqualTo( + new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, + address)); + } + + @Test + public void getAudioDeviceAttributesForSpatialAudio_hearingAid() { + String address = "11:22:33:44:55:66"; + when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); + when(mCachedBluetoothDevice.getAddress()).thenReturn(address); + when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mHearingAid)); + when(mHearingAid.isEnabled(mBluetoothDevice)).thenReturn(true); + + AudioDeviceAttributes attr = + BluetoothUtils.getAudioDeviceAttributesForSpatialAudio( + mCachedBluetoothDevice, AudioManager.AUDIO_DEVICE_CATEGORY_HEARING_AID); + + assertThat(attr) + .isEqualTo( + new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_HEARING_AID, + address)); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt index 2b29a6ef5e5a..9568d66ac7db 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt @@ -37,9 +37,9 @@ class DeviceSettingsConfigTest { "package_name_1", "class_name_1", "intent_action_1", - Bundle() - ) - ), + null, + Bundle(), + )), moreSettingsItems = listOf( DeviceSettingItem( @@ -47,9 +47,9 @@ class DeviceSettingsConfigTest { "package_name_2", "class_name_2", "intent_action_2", - Bundle() - ) - ), + null, + Bundle(), + )), moreSettingsFooter = "footer", extras = Bundle().apply { putString("key1", "value1") }, ) diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt index fee23945f7b5..4c5ee9e4ee4c 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt @@ -40,6 +40,7 @@ import com.android.settingslib.bluetooth.devicesettings.MultiTogglePreferenceSta import com.android.settingslib.bluetooth.devicesettings.ToggleInfo import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigModel +import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel @@ -352,7 +353,7 @@ class DeviceSettingRepositoryTest { val pref = serviceResponse.preference as ActionSwitchPreference assertThat(actual.title).isEqualTo(pref.title) assertThat(actual.summary).isEqualTo(pref.summary) - assertThat(actual.icon).isEqualTo(pref.icon) + assertThat(actual.icon).isEqualTo(DeviceSettingIcon.BitmapIcon(pref.icon!!)) assertThat(actual.isAllowedChangingState).isEqualTo(pref.isAllowedChangingState) if (pref.hasSwitch()) { assertThat(actual.switchState!!.checked).isEqualTo(pref.checked) diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java index d9fdcc38b576..f46424773fef 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java @@ -19,6 +19,7 @@ package com.android.settingslib.notification.modes; import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS; import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE; import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY; +import static android.service.notification.SystemZenRules.PACKAGE_ANDROID; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -67,6 +68,7 @@ public class ZenModeTest { assertThat(manualMode.canEditNameAndIcon()).isFalse(); assertThat(manualMode.canBeDeleted()).isFalse(); assertThat(manualMode.isActive()).isFalse(); + assertThat(manualMode.getRule().getPackageName()).isEqualTo(PACKAGE_ANDROID); } @Test diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 6d78705d1fbc..5ea75be11c47 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -748,6 +748,7 @@ android_library { "//frameworks/libs/systemui:motion_tool_lib", "//frameworks/libs/systemui:contextualeducationlib", "androidx.core_core-animation-testing", + "androidx.lifecycle_lifecycle-runtime-testing", "androidx.compose.ui_ui", "flag-junit", "ravenwood-junit", @@ -789,6 +790,7 @@ android_library { "SystemUI-tests-base", "androidx.test.uiautomator_uiautomator", "androidx.core_core-animation-testing", + "androidx.lifecycle_lifecycle-runtime-testing", "mockito-target-extended-minus-junit4", "mockito-kotlin-nodeps", "androidx.test.ext.junit", diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 5632e300da35..df286546e9b6 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -380,6 +380,17 @@ flag { } flag { + name: "status_bar_stop_updating_window_height" + namespace: "systemui" + description: "Don't have PhoneStatusBarView manually trigger an update of the height in " + "StatusBarWindowController" + bug: "360115167" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "compose_bouncer" namespace: "systemui" description: "Use the new compose bouncer in SystemUI" diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index b93b0490816d..7472d818d71a 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -1410,7 +1410,10 @@ fun AccessibilityContainer(viewModel: BaseCommunalViewModel, content: @Composabl R.string.accessibility_action_label_close_communal_hub ) ) { - viewModel.changeScene(CommunalScenes.Blank) + viewModel.changeScene( + CommunalScenes.Blank, + "closed by accessibility" + ) true }, CustomAccessibilityAction( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt index eea00c4f2935..fb7c42254caa 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt @@ -29,8 +29,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment @@ -42,6 +40,7 @@ import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer +import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.ui.composable.LockscreenContent import com.android.systemui.lifecycle.rememberViewModel @@ -114,7 +113,7 @@ constructor( } @Composable -private fun ShadeBody( +fun ShadeBody( viewModel: QuickSettingsContainerViewModel, ) { val isEditing by viewModel.editModeViewModel.isEditing.collectAsStateWithLifecycle() @@ -131,6 +130,7 @@ private fun ShadeBody( } else { QuickSettingsLayout( viewModel = viewModel, + modifier = Modifier.sysuiResTag("quick_settings_panel") ) } } @@ -158,11 +158,6 @@ private fun QuickSettingsLayout( Modifier.fillMaxWidth().heightIn(max = QuickSettingsShade.Dimensions.GridMaxHeight), viewModel.editModeViewModel::startEditing, ) - Button( - onClick = { viewModel.editModeViewModel.startEditing() }, - ) { - Text("Edit mode") - } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt index 7920e74eff01..7dc53eaf53ac 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt @@ -56,6 +56,7 @@ import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInteropFilter import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.colorResource @@ -88,6 +89,8 @@ import com.android.systemui.media.controls.ui.controller.MediaCarouselController import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager import com.android.systemui.media.controls.ui.view.MediaHost import com.android.systemui.media.controls.ui.view.MediaHostState +import com.android.systemui.media.controls.ui.view.MediaHostState.Companion.COLLAPSED +import com.android.systemui.media.controls.ui.view.MediaHostState.Companion.EXPANDED import com.android.systemui.media.dagger.MediaModule.QS_PANEL import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL import com.android.systemui.notifications.ui.composable.NotificationScrollingStack @@ -110,6 +113,7 @@ import com.android.systemui.statusbar.notification.stack.ui.viewmodel.Notificati import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.phone.ui.StatusBarIconController import com.android.systemui.statusbar.phone.ui.TintedIconManager +import com.android.systemui.util.Utils import dagger.Lazy import javax.inject.Inject import javax.inject.Named @@ -260,6 +264,11 @@ private fun SceneScope.SingleShade( shadeSession: SaveableSession, ) { val cutoutLocation = LocalDisplayCutout.current.location + val isLandscape = LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Compact + val usingCollapsedLandscapeMedia = + Utils.useCollapsedMediaInLandscape(LocalContext.current.resources) + val isExpanded = !usingCollapsedLandscapeMedia || !isLandscape + mediaHost.expansion = if (isExpanded) EXPANDED else COLLAPSED val maxNotifScrimTop = remember { mutableStateOf(0f) } val tileSquishiness by @@ -275,9 +284,7 @@ private fun SceneScope.SingleShade( layoutState.isTransitioningBetween(Scenes.Gone, Scenes.Shade) || layoutState.isTransitioningBetween(Scenes.Lockscreen, Scenes.Shade) // Media is visible and we are in landscape on a small height screen - val mediaInRow = - isMediaVisible && - LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Compact + val mediaInRow = isMediaVisible && isLandscape val mediaOffset by animateSceneDpAsState(value = InQQS, key = MediaLandscapeTopOffset, canOverflow = false) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt index b329534e6e3a..3487730945da 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt @@ -81,6 +81,7 @@ internal fun Modifier.multiPointerDraggable( enabled: () -> Boolean, startDragImmediately: (startedPosition: Offset) -> Boolean, onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController, + onFirstPointerDown: () -> Unit = {}, swipeDetector: SwipeDetector = DefaultSwipeDetector, dispatcher: NestedScrollDispatcher, ): Modifier = @@ -90,6 +91,7 @@ internal fun Modifier.multiPointerDraggable( enabled, startDragImmediately, onDragStarted, + onFirstPointerDown, swipeDetector, dispatcher, ) @@ -101,6 +103,7 @@ private data class MultiPointerDraggableElement( private val startDragImmediately: (startedPosition: Offset) -> Boolean, private val onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController, + private val onFirstPointerDown: () -> Unit, private val swipeDetector: SwipeDetector, private val dispatcher: NestedScrollDispatcher, ) : ModifierNodeElement<MultiPointerDraggableNode>() { @@ -110,6 +113,7 @@ private data class MultiPointerDraggableElement( enabled = enabled, startDragImmediately = startDragImmediately, onDragStarted = onDragStarted, + onFirstPointerDown = onFirstPointerDown, swipeDetector = swipeDetector, dispatcher = dispatcher, ) @@ -119,6 +123,7 @@ private data class MultiPointerDraggableElement( node.enabled = enabled node.startDragImmediately = startDragImmediately node.onDragStarted = onDragStarted + node.onFirstPointerDown = onFirstPointerDown node.swipeDetector = swipeDetector } } @@ -129,6 +134,7 @@ internal class MultiPointerDraggableNode( var startDragImmediately: (startedPosition: Offset) -> Boolean, var onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController, + var onFirstPointerDown: () -> Unit, var swipeDetector: SwipeDetector = DefaultSwipeDetector, private val dispatcher: NestedScrollDispatcher, ) : @@ -225,6 +231,7 @@ internal class MultiPointerDraggableNode( startedPosition = null } else if (startedPosition == null) { startedPosition = pointers.first().position + onFirstPointerDown() } } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt index f06214645144..d1e83bacf40a 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt @@ -67,6 +67,7 @@ private class SwipeToSceneNode( enabled = ::enabled, startDragImmediately = ::startDragImmediately, onDragStarted = draggableHandler::onDragStarted, + onFirstPointerDown = ::onFirstPointerDown, swipeDetector = swipeDetector, dispatcher = dispatcher, ) @@ -101,6 +102,15 @@ private class SwipeToSceneNode( delegate(ScrollBehaviorOwnerNode(draggableHandler.nestedScrollKey, nestedScrollHandlerImpl)) } + private fun onFirstPointerDown() { + // When we drag our finger across the screen, the NestedScrollConnection keeps track of all + // the scroll events until we lift our finger. However, in some cases, the connection might + // not receive the "up" event. This can lead to an incorrect initial state for the gesture. + // To prevent this issue, we can call the reset() method when the first finger touches the + // screen. This ensures that the NestedScrollConnection starts from a correct state. + nestedScrollHandlerImpl.connection.reset() + } + override fun onDetach() { // Make sure we reset the scroll connection when this modifier is removed from composition nestedScrollHandlerImpl.connection.reset() diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt index 228f7ba48d3e..16fb533bbe06 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt @@ -129,7 +129,11 @@ class PriorityNestedScrollConnection( return onPriorityStop(available) } - /** Method to call before destroying the object or to reset the initial state. */ + /** + * Method to call before destroying the object or to reset the initial state. + * + * TODO(b/303224944) This method should be removed. + */ fun reset() { // Step 3c: To ensure that an onStop is always called for every onStart. onPriorityStop(velocity = Velocity.Zero) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt index 9ebc42650d45..d8a06f54e74b 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt @@ -23,6 +23,9 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.platform.LocalViewConfiguration @@ -266,4 +269,38 @@ class NestedScrollToSceneTest { val transition = assertThat(state.transitionState).isTransition() assertThat(transition).hasProgress(0.5f) } + + @Test + fun resetScrollTracking_afterMissingPointerUpEvent() { + var canScroll = true + var hasScrollable by mutableStateOf(true) + val state = setup2ScenesAndScrollTouchSlop { + if (hasScrollable) { + Modifier.scrollable(rememberScrollableState { if (canScroll) it else 0f }, Vertical) + } else { + Modifier + } + } + + // The gesture is consumed by the component in the scene. + scrollUp(percent = 0.2f) + + // STL keeps track of the scroll consumed. The scene remains in Idle. + assertThat(state.transitionState).isIdle() + + // The scrollable component disappears, and does not send the signal (pointer up) to reset + // the consumed amount. + hasScrollable = false + pointerUp() + + // A new scrollable component appears and allows the scene to consume the scroll. + hasScrollable = true + canScroll = false + pointerDownAndScrollTouchSlop() + scrollUp(percent = 0.2f) + + // STL can only start the transition if it has reset the amount of scroll consumed. + val transition = assertThat(state.transitionState).isTransition() + assertThat(transition).hasProgress(0.2f) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt index 9ccf99b301b1..70529cc762e0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt @@ -112,7 +112,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { val scene by collectLastValue(communalSceneInteractor.currentScene) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") assertThat(scene).isEqualTo(CommunalScenes.Communal) communalSceneInteractor.setEditModeState(EditModeState.STARTING) @@ -133,7 +133,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { val scene by collectLastValue(communalSceneInteractor.currentScene) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") assertThat(scene).isEqualTo(CommunalScenes.Communal) communalSceneInteractor.setIsLaunchingWidget(true) @@ -154,7 +154,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { val scene by collectLastValue(communalSceneInteractor.currentScene) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") assertThat(scene).isEqualTo(CommunalScenes.Communal) communalSceneInteractor.setIsLaunchingWidget(false) @@ -174,7 +174,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { testScope.runTest { val scene by collectLastValue(communalSceneInteractor.currentScene) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") assertThat(scene).isEqualTo(CommunalScenes.Communal) communalInteractor.setEditModeOpen(true) @@ -213,7 +213,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { whenever(centralSurfaces.isLaunchingActivityOverLockscreen).thenReturn(false) val scene by collectLastValue(communalSceneInteractor.currentScene) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") assertThat(scene).isEqualTo(CommunalScenes.Communal) updateDocked(true) @@ -233,7 +233,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { whenever(centralSurfaces.isLaunchingActivityOverLockscreen).thenReturn(true) val scene by collectLastValue(communalSceneInteractor.currentScene) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") assertThat(scene).isEqualTo(CommunalScenes.Communal) updateDocked(true) @@ -270,7 +270,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { testScope.runTest { val scene by collectLastValue(communalSceneInteractor.currentScene) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") assertThat(scene).isEqualTo(CommunalScenes.Communal) fakeKeyguardTransitionRepository.sendTransitionSteps( @@ -292,7 +292,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { testScope.runTest { val scene by collectLastValue(communalSceneInteractor.currentScene) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") assertThat(scene).isEqualTo(CommunalScenes.Communal) fakeKeyguardTransitionRepository.sendTransitionSteps( @@ -320,7 +320,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fun dockingOnLockscreen_forcesCommunal() = with(kosmos) { testScope.runTest { - communalSceneInteractor.changeScene(CommunalScenes.Blank) + communalSceneInteractor.changeScene(CommunalScenes.Blank, "test") val scene by collectLastValue(communalSceneInteractor.currentScene) // device is docked while on the lockscreen @@ -342,7 +342,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fun dockingOnLockscreen_doesNotForceCommunalIfDreamStarts() = with(kosmos) { testScope.runTest { - communalSceneInteractor.changeScene(CommunalScenes.Blank) + communalSceneInteractor.changeScene(CommunalScenes.Blank, "test") val scene by collectLastValue(communalSceneInteractor.currentScene) // device is docked while on the lockscreen @@ -374,7 +374,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { // Device is dreaming and on communal. updateDreaming(true) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") val scene by collectLastValue(communalSceneInteractor.currentScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) @@ -391,7 +391,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { // Device is not dreaming and on communal. updateDreaming(false) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") // Scene stays as Communal advanceTimeBy(SCREEN_TIMEOUT.milliseconds) @@ -406,7 +406,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { // Device is dreaming and on communal. updateDreaming(true) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") val scene by collectLastValue(communalSceneInteractor.currentScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) @@ -429,7 +429,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { // Device is on communal, but not dreaming. updateDreaming(false) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") val scene by collectLastValue(communalSceneInteractor.currentScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) @@ -450,7 +450,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { testScope.runTest { // Device is on communal. - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") // Device stays on the hub after the timeout since we're not dreaming. advanceTimeBy(SCREEN_TIMEOUT.milliseconds * 2) @@ -471,7 +471,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { // Device is dreaming and on communal. updateDreaming(true) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") val scene by collectLastValue(communalSceneInteractor.currentScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) @@ -500,7 +500,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { // Device is dreaming and on communal. updateDreaming(true) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") val scene by collectLastValue(communalSceneInteractor.currentScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) @@ -520,7 +520,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { testScope.runTest { val scene by collectLastValue(communalSceneInteractor.currentScene) - communalSceneInteractor.changeScene(CommunalScenes.Blank) + communalSceneInteractor.changeScene(CommunalScenes.Blank, "test") assertThat(scene).isEqualTo(CommunalScenes.Blank) fakeKeyguardTransitionRepository.sendTransitionSteps( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index e57a4cbc230e..864795b062be 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -482,7 +482,7 @@ class CommunalInteractorTest : SysuiTestCase() { testScope.runTest { val targetScene = CommunalScenes.Communal - underTest.changeScene(targetScene) + underTest.changeScene(targetScene, "test") val desiredScene = collectLastValue(communalRepository.currentScene) runCurrent() @@ -635,7 +635,7 @@ class CommunalInteractorTest : SysuiTestCase() { runCurrent() assertThat(isCommunalShowing()).isEqualTo(false) - underTest.changeScene(CommunalScenes.Communal) + underTest.changeScene(CommunalScenes.Communal, "test") isCommunalShowing = collectLastValue(underTest.isCommunalShowing) runCurrent() @@ -659,12 +659,12 @@ class CommunalInteractorTest : SysuiTestCase() { assertThat(isCommunalShowing).isFalse() // Verify scene changes (without the flag) to communal sets the value to true - underTest.changeScene(CommunalScenes.Communal) + underTest.changeScene(CommunalScenes.Communal, "test") runCurrent() assertThat(isCommunalShowing).isTrue() // Verify scene changes (without the flag) to blank sets the value back to false - underTest.changeScene(CommunalScenes.Blank) + underTest.changeScene(CommunalScenes.Blank, "test") runCurrent() assertThat(isCommunalShowing).isFalse() } @@ -679,7 +679,7 @@ class CommunalInteractorTest : SysuiTestCase() { assertThat(isCommunalShowing).isFalse() // Verify scene changes without the flag doesn't have any impact - underTest.changeScene(CommunalScenes.Communal) + underTest.changeScene(CommunalScenes.Communal, "test") runCurrent() assertThat(isCommunalShowing).isFalse() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt index 43293c7a50ca..ed7e9107240e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt @@ -53,7 +53,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { val currentScene by collectLastValue(underTest.currentScene) assertThat(currentScene).isEqualTo(CommunalScenes.Blank) - underTest.changeScene(CommunalScenes.Communal) + underTest.changeScene(CommunalScenes.Communal, "test") assertThat(currentScene).isEqualTo(CommunalScenes.Communal) } @@ -63,7 +63,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { val currentScene by collectLastValue(underTest.currentScene) assertThat(currentScene).isEqualTo(CommunalScenes.Blank) - underTest.snapToScene(CommunalScenes.Communal) + underTest.snapToScene(CommunalScenes.Communal, "test") assertThat(currentScene).isEqualTo(CommunalScenes.Communal) } @@ -75,6 +75,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { assertThat(currentScene).isEqualTo(CommunalScenes.Blank) underTest.snapToScene( CommunalScenes.Communal, + "test", ActivityTransitionAnimator.TIMINGS.totalDuration ) assertThat(currentScene).isEqualTo(CommunalScenes.Blank) @@ -86,7 +87,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { fun changeSceneForActivityStartOnDismissKeyguard() = testScope.runTest { val currentScene by collectLastValue(underTest.currentScene) - underTest.snapToScene(CommunalScenes.Communal) + underTest.snapToScene(CommunalScenes.Communal, "test") assertThat(currentScene).isEqualTo(CommunalScenes.Communal) underTest.changeSceneForActivityStartOnDismissKeyguard() @@ -97,7 +98,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { fun changeSceneForActivityStartOnDismissKeyguard_willNotChangeScene_forEditModeActivity() = testScope.runTest { val currentScene by collectLastValue(underTest.currentScene) - underTest.snapToScene(CommunalScenes.Communal) + underTest.snapToScene(CommunalScenes.Communal, "test") assertThat(currentScene).isEqualTo(CommunalScenes.Communal) underTest.setEditModeState(EditModeState.STARTING) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt index 3a23e14e2777..7e28e19d0ee0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt @@ -158,7 +158,7 @@ class CommunalTutorialInteractorTest : SysuiTestCase() { kosmos.setCommunalAvailable(true) communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED) - communalInteractor.changeScene(CommunalScenes.Blank) + communalInteractor.changeScene(CommunalScenes.Blank, "test") assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_NOT_STARTED) } @@ -171,7 +171,7 @@ class CommunalTutorialInteractorTest : SysuiTestCase() { goToCommunal() communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED) - communalInteractor.changeScene(CommunalScenes.Blank) + communalInteractor.changeScene(CommunalScenes.Blank, "test") assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED) } @@ -184,13 +184,13 @@ class CommunalTutorialInteractorTest : SysuiTestCase() { goToCommunal() communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) - communalInteractor.changeScene(CommunalScenes.Blank) + communalInteractor.changeScene(CommunalScenes.Blank, "test") assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED) } private suspend fun goToCommunal() { kosmos.setCommunalAvailable(true) - communalInteractor.changeScene(CommunalScenes.Communal) + communalInteractor.changeScene(CommunalScenes.Communal, "test") } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt index e36fd75445e2..a052b078167d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt @@ -76,7 +76,7 @@ class CommunalTransitionAnimatorControllerTest : SysuiTestCase() { val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget) val scene by collectLastValue(communalSceneInteractor.currentScene) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") Truth.assertThat(scene).isEqualTo(CommunalScenes.Communal) communalSceneInteractor.setIsLaunchingWidget(true) assertTrue(launching!!) @@ -103,7 +103,7 @@ class CommunalTransitionAnimatorControllerTest : SysuiTestCase() { val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget) val scene by collectLastValue(communalSceneInteractor.currentScene) - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") Truth.assertThat(scene).isEqualTo(CommunalScenes.Communal) communalSceneInteractor.setIsLaunchingWidget(true) assertTrue(launching!!) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt index 7a86e57779b9..da82b5f4a1f7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt @@ -68,7 +68,6 @@ import com.android.systemui.navigationbar.gestural.domain.GestureInteractor import com.android.systemui.testKosmos import com.android.systemui.touch.TouchInsetManager import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat @@ -87,6 +86,7 @@ import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.isNull import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.eq import org.mockito.kotlin.spy @@ -669,7 +669,7 @@ class DreamOverlayServiceTest : SysuiTestCase() { runCurrent() verify(mDreamOverlayCallback).onRedirectWake(true) client.onWakeRequested() - verify(mCommunalInteractor).changeScene(eq(CommunalScenes.Communal), isNull()) + verify(mCommunalInteractor).changeScene(eq(CommunalScenes.Communal), any(), isNull()) verify(mUiEventLogger).log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_DREAM_AWAKE_START) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index 8c1e8de315b1..9792c28d5023 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -845,7 +845,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GONE) // WHEN the glanceable hub is shown - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") runCurrent() assertThat(transitionRepository) @@ -1004,7 +1004,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest fun alternateBouncerToGlanceableHub() = testScope.runTest { // GIVEN the device is idle on the glanceable hub - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") runCurrent() clearInvocations(transitionRepository) @@ -1123,7 +1123,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest fun primaryBouncerToGlanceableHub() = testScope.runTest { // GIVEN the device is idle on the glanceable hub - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") runCurrent() // GIVEN a prior transition has run to PRIMARY_BOUNCER @@ -1157,7 +1157,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest advanceTimeBy(600L) // GIVEN the device is idle on the glanceable hub - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") runCurrent() // GIVEN a prior transition has run to PRIMARY_BOUNCER @@ -1971,7 +1971,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest fun glanceableHubToLockscreen_communalKtfRefactor() = testScope.runTest { // GIVEN a prior transition has run to GLANCEABLE_HUB - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") runCurrent() clearInvocations(transitionRepository) @@ -2035,7 +2035,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest fun glanceableHubToDozing_communalKtfRefactor() = testScope.runTest { // GIVEN a prior transition has run to GLANCEABLE_HUB - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") runCurrent() clearInvocations(transitionRepository) @@ -2136,7 +2136,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest fun glanceableHubToOccluded_communalKtfRefactor() = testScope.runTest { // GIVEN a prior transition has run to GLANCEABLE_HUB - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") runCurrent() clearInvocations(transitionRepository) @@ -2184,7 +2184,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest fun glanceableHubToGone_communalKtfRefactor() = testScope.runTest { // GIVEN a prior transition has run to GLANCEABLE_HUB - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") runCurrent() clearInvocations(transitionRepository) @@ -2265,7 +2265,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest advanceTimeBy(600L) // GIVEN a prior transition has run to GLANCEABLE_HUB - communalSceneInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") runCurrent() clearInvocations(transitionRepository) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt new file mode 100644 index 000000000000..59992650cfc7 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.composefragment.viewmodel + +import android.content.testableContext +import android.testing.TestableLooper.RunWithLooper +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.testing.TestLifecycleOwner +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope +import com.android.systemui.qs.fgsManagerController +import com.android.systemui.res.R +import com.android.systemui.shade.largeScreenHeaderHelper +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestResult +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +@RunWithLooper +@OptIn(ExperimentalCoroutinesApi::class) +class QSFragmentComposeViewModelTest : SysuiTestCase() { + private val kosmos = testKosmos() + + private val lifecycleOwner = + TestLifecycleOwner( + initialState = Lifecycle.State.CREATED, + coroutineDispatcher = kosmos.testDispatcher, + ) + + private val underTest by lazy { + kosmos.qsFragmentComposeViewModelFactory.create(lifecycleOwner.lifecycleScope) + } + + @Before + fun setUp() { + Dispatchers.setMain(kosmos.testDispatcher) + } + + @After + fun teardown() { + Dispatchers.resetMain() + } + + // For now the state changes at 0.5f expansion. This will change once we implement animation + // (and this test will fail) + @Test + fun qsExpansionValueChanges_correctExpansionState() = + with(kosmos) { + testScope.testWithinLifecycle { + val expansionState by collectLastValue(underTest.expansionState) + + underTest.qsExpansionValue = 0f + assertThat(expansionState) + .isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QQS) + + underTest.qsExpansionValue = 0.3f + assertThat(expansionState) + .isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QQS) + + underTest.qsExpansionValue = 0.7f + assertThat(expansionState).isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QS) + + underTest.qsExpansionValue = 1f + assertThat(expansionState).isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QS) + } + } + + @Test + fun qqsHeaderHeight_largeScreenHeader_0() = + with(kosmos) { + testScope.testWithinLifecycle { + val qqsHeaderHeight by collectLastValue(underTest.qqsHeaderHeight) + + testableContext.orCreateTestableResources.addOverride( + R.bool.config_use_large_screen_shade_header, + true + ) + fakeConfigurationRepository.onConfigurationChange() + + assertThat(qqsHeaderHeight).isEqualTo(0) + } + } + + @Test + fun qqsHeaderHeight_noLargeScreenHeader_providedByHelper() = + with(kosmos) { + testScope.testWithinLifecycle { + val qqsHeaderHeight by collectLastValue(underTest.qqsHeaderHeight) + + testableContext.orCreateTestableResources.addOverride( + R.bool.config_use_large_screen_shade_header, + false + ) + fakeConfigurationRepository.onConfigurationChange() + + assertThat(qqsHeaderHeight) + .isEqualTo(largeScreenHeaderHelper.getLargeScreenHeaderHeight()) + } + } + + @Test + fun footerActionsControllerInit() = + with(kosmos) { + testScope.testWithinLifecycle { + underTest + runCurrent() + assertThat(fgsManagerController.initialized).isTrue() + } + } + + private inline fun TestScope.testWithinLifecycle( + crossinline block: suspend TestScope.() -> TestResult + ): TestResult { + return runTest { + lifecycleOwner.setCurrentState(Lifecycle.State.RESUMED) + block().also { lifecycleOwner.setCurrentState(Lifecycle.State.DESTROYED) } + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt index 956353845506..1118a6150fcc 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.ui.viewmodel import android.testing.TestableLooper.RunWithLooper +import androidx.lifecycle.LifecycleOwner import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -59,7 +60,7 @@ class QuickSettingsSceneContentViewModelTest : SysuiTestCase() { private val footerActionsViewModel = mock<FooterActionsViewModel>() private val footerActionsViewModelFactory = mock<FooterActionsViewModel.Factory> { - whenever(create(any())).thenReturn(footerActionsViewModel) + whenever(create(any<LifecycleOwner>())).thenReturn(footerActionsViewModel) } private val footerActionsController = mock<FooterActionsController>() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index cd84abc50802..aee3ce052d78 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -133,6 +133,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { sceneInteractor = sceneInteractor, falsingInteractor = kosmos.falsingInteractor, powerInteractor = kosmos.powerInteractor, + motionEventHandlerReceiver = {}, ) .apply { setTransitionState(transitionState) } } @@ -199,6 +200,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { shadeSceneContentViewModel.activateIn(testScope) shadeSceneActionsViewModel.activateIn(testScope) bouncerSceneContentViewModel.activateIn(testScope) + sceneContainerViewModel.activateIn(testScope) assertWithMessage("Initial scene key mismatch!") .that(sceneContainerViewModel.currentScene.value) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt index ea95aab4a1c4..f85823ad7c33 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.scene.ui.viewmodel import android.view.MotionEvent @@ -25,6 +27,7 @@ import com.android.systemui.classifier.fakeFalsingManager import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.kosmos.testScope +import com.android.systemui.lifecycle.activateIn import com.android.systemui.power.data.repository.fakePowerRepository import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor @@ -37,6 +40,8 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -57,6 +62,9 @@ class SceneContainerViewModelTest : SysuiTestCase() { private lateinit var underTest: SceneContainerViewModel + private lateinit var activationJob: Job + private var motionEventHandler: SceneContainerViewModel.MotionEventHandler? = null + @Before fun setUp() { underTest = @@ -64,10 +72,28 @@ class SceneContainerViewModelTest : SysuiTestCase() { sceneInteractor = sceneInteractor, falsingInteractor = kosmos.falsingInteractor, powerInteractor = kosmos.powerInteractor, + motionEventHandlerReceiver = { motionEventHandler -> + this@SceneContainerViewModelTest.motionEventHandler = motionEventHandler + }, ) + activationJob = Job() + underTest.activateIn(testScope, activationJob) } @Test + fun activate_setsMotionEventHandler() = + testScope.runTest { assertThat(motionEventHandler).isNotNull() } + + @Test + fun deactivate_clearsMotionEventHandler() = + testScope.runTest { + activationJob.cancel() + runCurrent() + + assertThat(motionEventHandler).isNull() + } + + @Test fun isVisible() = testScope.runTest { val isVisible by collectLastValue(underTest.isVisible) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java index 9005ae3c3d41..89aa670e5f0c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java @@ -241,7 +241,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { alm.showNotification(entry); final boolean removedImmediately = alm.removeNotification( - entry.getKey(), /* releaseImmediately = */ false); + entry.getKey(), /* releaseImmediately = */ false, "removeDeferred"); assertFalse(removedImmediately); assertTrue(alm.isHeadsUpEntry(entry.getKey())); } @@ -254,7 +254,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { alm.showNotification(entry); final boolean removedImmediately = alm.removeNotification( - entry.getKey(), /* releaseImmediately = */ true); + entry.getKey(), /* releaseImmediately = */ true, "forceRemove"); assertTrue(removedImmediately); assertFalse(alm.isHeadsUpEntry(entry.getKey())); } @@ -430,7 +430,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { hum.showNotification(entry); final boolean removedImmediately = hum.removeNotification( - entry.getKey(), /* releaseImmediately = */ false); + entry.getKey(), /* releaseImmediately = */ false, "beforeMinimumDisplayTime"); assertFalse(removedImmediately); assertTrue(hum.isHeadsUpEntry(entry.getKey())); @@ -452,7 +452,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { assertTrue(hum.isHeadsUpEntry(entry.getKey())); final boolean removedImmediately = hum.removeNotification( - entry.getKey(), /* releaseImmediately = */ false); + entry.getKey(), /* releaseImmediately = */ false, "afterMinimumDisplayTime"); assertTrue(removedImmediately); assertFalse(hum.isHeadsUpEntry(entry.getKey())); } @@ -466,7 +466,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { hum.showNotification(entry); final boolean removedImmediately = hum.removeNotification( - entry.getKey(), /* releaseImmediately = */ true); + entry.getKey(), /* releaseImmediately = */ true, "afterMinimumDisplayTime"); assertTrue(removedImmediately); assertFalse(hum.isHeadsUpEntry(entry.getKey())); } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt index 7a6838a6c9ac..ca106fa01f9a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt @@ -179,8 +179,8 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager mContext .getOrCreateTestableResources() .addOverride(R.integer.ambient_notification_extension_time, 500) - mAvalancheController = AvalancheController(dumpManager, mUiEventLogger, - mHeadsUpManagerLogger, mBgHandler) + mAvalancheController = + AvalancheController(dumpManager, mUiEventLogger, mHeadsUpManagerLogger, mBgHandler) } @Test @@ -200,7 +200,12 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager hmp.addSwipedOutNotification(entry.key) // Remove should succeed because the notification is swiped out - val removedImmediately = hmp.removeNotification(entry.key, /* releaseImmediately= */ false) + val removedImmediately = + hmp.removeNotification( + entry.key, + /* releaseImmediately= */ false, + /* reason= */ "swipe out" + ) Assert.assertTrue(removedImmediately) Assert.assertFalse(hmp.isHeadsUpEntry(entry.key)) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java index 69207ba07e6e..3efabd743141 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java @@ -100,7 +100,7 @@ class TestableHeadsUpManager extends BaseHeadsUpManager { @Override public boolean removeNotification(@NonNull String key, boolean releaseImmediately, - boolean animate) { + boolean animate, @NonNull String reason) { throw new UnsupportedOperationException(); } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt index bcad7e7bc31c..54b7d25325c5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt @@ -23,6 +23,7 @@ import android.provider.Settings import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.notification.modes.TestModeBuilder +import com.android.settingslib.notification.modes.ZenMode import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testDispatcher @@ -30,6 +31,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogDelegate +import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogEventLogger import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -40,6 +42,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.clearInvocations import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.times import org.mockito.kotlin.verify @SmallTest @@ -50,9 +53,16 @@ class ModesDialogViewModelTest : SysuiTestCase() { private val repository = kosmos.fakeZenModeRepository private val interactor = kosmos.zenModeInteractor private val mockDialogDelegate = kosmos.mockModesDialogDelegate + private val mockDialogEventLogger = kosmos.mockModesDialogEventLogger private val underTest = - ModesDialogViewModel(context, interactor, kosmos.testDispatcher, mockDialogDelegate) + ModesDialogViewModel( + context, + interactor, + kosmos.testDispatcher, + mockDialogDelegate, + mockDialogEventLogger + ) @Test fun tiles_filtersOutUserDisabledModes() = @@ -432,4 +442,84 @@ class ModesDialogViewModelTest : SysuiTestCase() { assertThat(intent.extras?.getString(Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID)) .isEqualTo("B") } + + @Test + fun onClick_logsOnOffEvents() = + testScope.runTest { + val tiles by collectLastValue(underTest.tiles) + + repository.addModes( + listOf( + TestModeBuilder.MANUAL_DND_ACTIVE, + TestModeBuilder() + .setId("id1") + .setName("Inactive Mode One") + .setActive(false) + .setManualInvocationAllowed(true) + .build(), + TestModeBuilder() + .setId("id2") + .setName("Active Non-Invokable Mode Two") // but can be turned off by tile + .setActive(true) + .setManualInvocationAllowed(false) + .build(), + ) + ) + runCurrent() + + assertThat(tiles?.size).isEqualTo(3) + + // Trigger onClick for each tile in sequence + tiles?.forEach { it.onClick.invoke() } + runCurrent() + + val onModeCaptor = argumentCaptor<ZenMode>() + val offModeCaptor = argumentCaptor<ZenMode>() + + // manual mode and mode 2 should have turned off + verify(mockDialogEventLogger, times(2)).logModeOff(offModeCaptor.capture()) + val off0 = offModeCaptor.firstValue + assertThat(off0.isManualDnd).isTrue() + + val off1 = offModeCaptor.secondValue + assertThat(off1.id).isEqualTo("id2") + + // should also have logged turning mode 1 on + verify(mockDialogEventLogger).logModeOn(onModeCaptor.capture()) + val on = onModeCaptor.lastValue + assertThat(on.id).isEqualTo("id1") + } + + @Test + fun onLongClick_logsSettingsEvents() = + testScope.runTest { + val tiles by collectLastValue(underTest.tiles) + + repository.addModes( + listOf( + TestModeBuilder.MANUAL_DND_ACTIVE, + TestModeBuilder() + .setId("id1") + .setName("Inactive Mode One") + .setActive(false) + .setManualInvocationAllowed(true) + .build(), + ) + ) + runCurrent() + + assertThat(tiles?.size).isEqualTo(2) + val modeCaptor = argumentCaptor<ZenMode>() + + // long click manual DND and then automatic mode + tiles?.forEach { it.onLongClick.invoke() } + runCurrent() + + verify(mockDialogEventLogger, times(2)).logModeSettings(modeCaptor.capture()) + val manualMode = modeCaptor.firstValue + assertThat(manualMode.isManualDnd).isTrue() + + val automaticMode = modeCaptor.lastValue + assertThat(automaticMode.id).isEqualTo("id1") + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt index 7385a4731401..7c55f7a91a76 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt @@ -32,7 +32,6 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.android.systemui.volume.data.repository.TestAudioDevicesFactory import com.android.systemui.volume.data.repository.audioRepository -import com.android.systemui.volume.data.repository.audioSharingRepository import com.android.systemui.volume.domain.model.AudioOutputDevice import com.android.systemui.volume.localMediaController import com.android.systemui.volume.localMediaRepository @@ -222,32 +221,4 @@ class AudioOutputInteractorTest : SysuiTestCase() { val testIcon = TestStubDrawable() } - - @Test - fun inAudioSharing_returnTrue() { - with(kosmos) { - testScope.runTest { - audioSharingRepository.setInAudioSharing(true) - - val inAudioSharing by collectLastValue(underTest.isInAudioSharing) - runCurrent() - - assertThat(inAudioSharing).isTrue() - } - } - } - - @Test - fun notInAudioSharing_returnFalse() { - with(kosmos) { - testScope.runTest { - audioSharingRepository.setInAudioSharing(false) - - val inAudioSharing by collectLastValue(underTest.isInAudioSharing) - runCurrent() - - assertThat(inAudioSharing).isFalse() - } - } - } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt index a1fcfcd0f749..c9d147b6c81c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt @@ -49,6 +49,24 @@ class AudioSharingInteractorTest : SysuiTestCase() { } @Test + fun handleInAudioSharingChange() { + with(kosmos) { + testScope.runTest { + with(audioSharingRepository) { setInAudioSharing(true) } + val inAudioSharing by collectLastValue(underTest.isInAudioSharing) + runCurrent() + + Truth.assertThat(inAudioSharing).isEqualTo(true) + + with(audioSharingRepository) { setInAudioSharing(false) } + runCurrent() + + Truth.assertThat(inAudioSharing).isEqualTo(false) + } + } + } + + @Test fun handlePrimaryGroupChange_nullVolume() { with(kosmos) { testScope.runTest { diff --git a/packages/SystemUI/res-keyguard/values-ar/strings.xml b/packages/SystemUI/res-keyguard/values-ar/strings.xml index 57fd9ea8319f..d069c0181218 100644 --- a/packages/SystemUI/res-keyguard/values-ar/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ar/strings.xml @@ -71,7 +71,7 @@ <string name="kg_prompt_after_dpm_lock" msgid="6002804765868345917">"لمزيد من الأمان، تم قفل الجهاز وفقًا لسياسة العمل."</string> <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"يجب إدخال رقم التعريف الشخصي بعد إلغاء الفتح الذكي."</string> <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"يجب إدخال كلمة المرور بعد إلغاء الفتح الذكي."</string> - <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"يجب رسم النقش بعد إلغاء التأمين."</string> + <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"يجب رسم النقش بعد إلغاء الفتح الذكي."</string> <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"سيتم تثبيت التحديث عندما لا يكون الجهاز قيد الاستخدام."</string> <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"يجب تعزيز الأمان. لم يُستخدَم رقم PIN لبعض الوقت."</string> <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"يجب تعزيز الأمان. لم تستخدَم كلمة المرور لبعض الوقت."</string> diff --git a/packages/SystemUI/res-keyguard/values-ru/strings.xml b/packages/SystemUI/res-keyguard/values-ru/strings.xml index fd90d08caf9e..cf2057cafb8b 100644 --- a/packages/SystemUI/res-keyguard/values-ru/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ru/strings.xml @@ -57,7 +57,7 @@ <string name="kg_wrong_pin" msgid="4160978845968732624">"Неверный PIN-код"</string> <string name="kg_wrong_pin_try_again" msgid="3129729383303430190">"Неверный PIN-код."</string> <string name="kg_wrong_input_try_fp_suggestion" msgid="3143861542242024833">"Повторите попытку или используйте отпечаток пальца."</string> - <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Отпечаток не распознан."</string> + <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Отпечаток не распознан"</string> <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"Лицо не распознано."</string> <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"Повторите попытку или введите PIN-код."</string> <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"Повторите попытку или введите пароль."</string> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 0da252da5cc9..60fff282d041 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -104,6 +104,7 @@ import android.util.SparseBooleanArray; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.compose.animation.scene.ObservableTransitionState; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.foldables.FoldGracePeriodProvider; import com.android.internal.jank.InteractionJankMonitor; @@ -119,6 +120,7 @@ import com.android.systemui.CoreStartable; import com.android.systemui.Dumpable; import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider; +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; @@ -140,6 +142,9 @@ import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.clocks.WeatherData; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.res.R; +import com.android.systemui.scene.domain.interactor.SceneInteractor; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; +import com.android.systemui.scene.shared.model.Scenes; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; @@ -150,6 +155,7 @@ import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostu import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.Assert; +import com.android.systemui.util.kotlin.JavaAdapter; import dalvik.annotation.optimization.NeverCompile; @@ -279,6 +285,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final UserTracker mUserTracker; private final KeyguardUpdateMonitorLogger mLogger; private final boolean mIsSystemUser; + private final Provider<JavaAdapter> mJavaAdapter; + private final Provider<SceneInteractor> mSceneInteractor; + private final Provider<AlternateBouncerInteractor> mAlternateBouncerInteractor; private final AuthController mAuthController; private final UiEventLogger mUiEventLogger; private final Set<String> mAllowFingerprintOnOccludingActivitiesFromPackage; @@ -563,7 +572,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab */ private boolean shouldDismissKeyguardOnTrustGrantedWithCurrentUser(TrustGrantFlags flags) { final boolean isBouncerShowing = - mPrimaryBouncerIsOrWillBeShowing || mAlternateBouncerShowing; + isPrimaryBouncerShowingOrWillBeShowing() || isAlternateBouncerShowing(); return (flags.isInitiatedByUser() || flags.dismissKeyguardRequested()) && (mDeviceInteractive || flags.temporaryAndRenewable()) && (isBouncerShowing || flags.dismissKeyguardRequested()); @@ -1170,8 +1179,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Assert.isMainThread(); String reason = mKeyguardBypassController.canBypass() ? "bypass" - : mAlternateBouncerShowing ? "alternateBouncer" - : mPrimaryBouncerFullyShown ? "bouncer" + : isAlternateBouncerShowing() ? "alternateBouncer" + : isPrimaryBouncerFullyShown() ? "bouncer" : "udfpsFpDown"; requestActiveUnlock( ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL, @@ -2169,7 +2178,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Optional<FingerprintInteractiveToAuthProvider> interactiveToAuthProvider, TaskStackChangeListeners taskStackChangeListeners, SelectedUserInteractor selectedUserInteractor, - IActivityTaskManager activityTaskManagerService) { + IActivityTaskManager activityTaskManagerService, + Provider<AlternateBouncerInteractor> alternateBouncerInteractor, + Provider<JavaAdapter> javaAdapter, + Provider<SceneInteractor> sceneInteractor) { mContext = context; mSubscriptionManager = subscriptionManager; mUserTracker = userTracker; @@ -2214,6 +2226,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mFingerprintInteractiveToAuthProvider = interactiveToAuthProvider.orElse(null); mIsSystemUser = mUserManager.isSystemUser(); + mAlternateBouncerInteractor = alternateBouncerInteractor; + mJavaAdapter = javaAdapter; + mSceneInteractor = sceneInteractor; mHandler = new Handler(mainLooper) { @Override @@ -2470,6 +2485,30 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mContext.getContentResolver().registerContentObserver( Settings.System.getUriFor(Settings.System.TIME_12_24), false, mTimeFormatChangeObserver, UserHandle.USER_ALL); + + if (SceneContainerFlag.isEnabled()) { + mJavaAdapter.get().alwaysCollectFlow( + mAlternateBouncerInteractor.get().isVisible(), + this::onAlternateBouncerVisibilityChange); + mJavaAdapter.get().alwaysCollectFlow( + mSceneInteractor.get().getTransitionState(), + this::onTransitionStateChanged + ); + } + } + + @VisibleForTesting + void onAlternateBouncerVisibilityChange(boolean isAlternateBouncerVisible) { + setAlternateBouncerShowing(isAlternateBouncerVisible); + } + + + @VisibleForTesting + void onTransitionStateChanged(ObservableTransitionState transitionState) { + int primaryBouncerFullyShown = isPrimaryBouncerFullyShown(transitionState) ? 1 : 0; + int primaryBouncerIsOrWillBeShowing = + isPrimaryBouncerShowingOrWillBeShowing(transitionState) ? 1 : 0; + handlePrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing, primaryBouncerFullyShown); } private void initializeSimState() { @@ -2717,8 +2756,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab requestActiveUnlock( requestOrigin, extraReason, canFaceBypass - || mAlternateBouncerShowing - || mPrimaryBouncerFullyShown + || isAlternateBouncerShowing() + || isPrimaryBouncerFullyShown() || mAuthController.isUdfpsFingerDown()); } @@ -2739,7 +2778,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab */ public void setAlternateBouncerShowing(boolean showing) { mAlternateBouncerShowing = showing; - if (mAlternateBouncerShowing) { + if (isAlternateBouncerShowing()) { requestActiveUnlock( ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT, "alternateBouncer"); @@ -2747,6 +2786,45 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); } + private boolean isAlternateBouncerShowing() { + if (SceneContainerFlag.isEnabled()) { + return mAlternateBouncerInteractor.get().isVisibleState(); + } else { + return mAlternateBouncerShowing; + } + } + + private boolean isPrimaryBouncerShowingOrWillBeShowing() { + if (SceneContainerFlag.isEnabled()) { + return isPrimaryBouncerShowingOrWillBeShowing( + mSceneInteractor.get().getTransitionState().getValue()); + } else { + return mPrimaryBouncerIsOrWillBeShowing; + } + } + + private boolean isPrimaryBouncerFullyShown() { + if (SceneContainerFlag.isEnabled()) { + return isPrimaryBouncerFullyShown( + mSceneInteractor.get().getTransitionState().getValue()); + } else { + return mPrimaryBouncerFullyShown; + } + } + + private boolean isPrimaryBouncerShowingOrWillBeShowing( + ObservableTransitionState transitionState + ) { + SceneContainerFlag.assertInNewMode(); + return isPrimaryBouncerFullyShown(transitionState) + || transitionState.isTransitioning(null, Scenes.Bouncer); + } + + private boolean isPrimaryBouncerFullyShown(ObservableTransitionState transitionState) { + SceneContainerFlag.assertInNewMode(); + return transitionState.isIdle(Scenes.Bouncer); + } + /** * If the current state of the device allows for triggering active unlock. This does not * include active unlock availability. @@ -2762,7 +2840,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private boolean shouldTriggerActiveUnlock(boolean shouldLog) { // Triggers: final boolean triggerActiveUnlockForAssistant = shouldTriggerActiveUnlockForAssistant(); - final boolean awakeKeyguard = mPrimaryBouncerFullyShown || mAlternateBouncerShowing + final boolean awakeKeyguard = isPrimaryBouncerFullyShown() || isAlternateBouncerShowing() || (isKeyguardVisible() && !mGoingToSleep && mStatusBarState != StatusBarState.SHADE_LOCKED); @@ -2830,14 +2908,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab final boolean shouldListenKeyguardState = isKeyguardVisible() || !mDeviceInteractive - || (mPrimaryBouncerIsOrWillBeShowing && !mKeyguardGoingAway) + || (isPrimaryBouncerShowingOrWillBeShowing() && !mKeyguardGoingAway) || mGoingToSleep || shouldListenForFingerprintAssistant || (mKeyguardOccluded && mIsDreaming) || (mKeyguardOccluded && userDoesNotHaveTrust && mKeyguardShowing && (mOccludingAppRequestingFp || isUdfps - || mAlternateBouncerShowing + || isAlternateBouncerShowing() || mAllowFingerprintOnCurrentOccludingActivity ) ); @@ -2856,7 +2934,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab && !isUserInLockdown(user); final boolean strongerAuthRequired = !isUnlockingWithFingerprintAllowed(); final boolean shouldListenBouncerState = - !strongerAuthRequired || !mPrimaryBouncerIsOrWillBeShowing; + !strongerAuthRequired || !isPrimaryBouncerShowingOrWillBeShowing(); final boolean shouldListenUdfpsState = !isUdfps || (!userCanSkipBouncer @@ -2872,10 +2950,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab user, shouldListen, mAllowFingerprintOnCurrentOccludingActivity, - mAlternateBouncerShowing, + isAlternateBouncerShowing(), biometricEnabledForUser, mBiometricPromptShowing, - mPrimaryBouncerIsOrWillBeShowing, + isPrimaryBouncerShowingOrWillBeShowing(), userCanSkipBouncer, mCredentialAttempted, mDeviceInteractive, @@ -3614,6 +3692,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab */ public void sendPrimaryBouncerChanged(boolean primaryBouncerIsOrWillBeShowing, boolean primaryBouncerFullyShown) { + SceneContainerFlag.assertInLegacyMode(); mLogger.logSendPrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing, primaryBouncerFullyShown); Message message = mHandler.obtainMessage(MSG_KEYGUARD_BOUNCER_CHANGED); @@ -4031,10 +4110,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab if (isUdfpsSupported()) { pw.println(" udfpsEnrolled=" + isUdfpsEnrolled()); pw.println(" shouldListenForUdfps=" + shouldListenForFingerprint(true)); - pw.println(" mPrimaryBouncerIsOrWillBeShowing=" - + mPrimaryBouncerIsOrWillBeShowing); + pw.println(" isPrimaryBouncerShowingOrWillBeShowing=" + + isPrimaryBouncerShowingOrWillBeShowing()); pw.println(" mStatusBarState=" + StatusBarState.toString(mStatusBarState)); - pw.println(" mAlternateBouncerShowing=" + mAlternateBouncerShowing); + pw.println(" isAlternateBouncerShowing=" + isAlternateBouncerShowing()); } else if (isSfpsSupported()) { pw.println(" sfpsEnrolled=" + isSfpsEnrolled()); pw.println(" shouldListenForSfps=" + shouldListenForFingerprint(false)); diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index baf8f5aeba29..a30115568842 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -49,7 +49,6 @@ import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; -import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider; import com.android.systemui.statusbar.phone.SystemUIDialogManager; import com.android.systemui.statusbar.policy.BluetoothController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -140,7 +139,6 @@ public class Dependency { @Inject Lazy<SysUiState> mSysUiStateFlagsContainer; @Inject Lazy<CommandQueue> mCommandQueue; @Inject Lazy<UiEventLogger> mUiEventLogger; - @Inject Lazy<StatusBarContentInsetsProvider> mContentInsetsProviderLazy; @Inject Lazy<FeatureFlags> mFeatureFlagsLazy; @Inject Lazy<NotificationSectionsManager> mNotificationSectionsManagerLazy; @Inject Lazy<ScreenOffAnimationController> mScreenOffAnimationController; @@ -186,7 +184,6 @@ public class Dependency { mProviders.put(CommandQueue.class, mCommandQueue::get); mProviders.put(UiEventLogger.class, mUiEventLogger::get); mProviders.put(FeatureFlags.class, mFeatureFlagsLazy::get); - mProviders.put(StatusBarContentInsetsProvider.class, mContentInsetsProviderLazy::get); mProviders.put(NotificationSectionsManager.class, mNotificationSectionsManagerLazy::get); mProviders.put(ScreenOffAnimationController.class, mScreenOffAnimationController::get); mProviders.put(AmbientState.class, mAmbientStateLazy::get); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java index d81a6862c1c1..6c46318afe47 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java @@ -415,17 +415,13 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks @Override @MainThread public void showMagnificationButton(int displayId, int magnificationMode) { - if (Flags.delayShowMagnificationButton()) { - if (mHandler.hasMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL)) { - return; - } - mHandler.sendMessageDelayed( - mHandler.obtainMessage( - MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL, displayId, magnificationMode), - DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS); - } else { - showMagnificationButtonInternal(displayId, magnificationMode); + if (mHandler.hasMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL)) { + return; } + mHandler.sendMessageDelayed( + mHandler.obtainMessage( + MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL, displayId, magnificationMode), + DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS); } @MainThread @@ -441,9 +437,7 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks @Override @MainThread public void removeMagnificationButton(int displayId) { - if (Flags.delayShowMagnificationButton()) { - mHandler.removeMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL); - } + mHandler.removeMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL); mModeSwitchesController.removeButton(displayId); } diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt index b7c02ea91a6b..6e01393015ed 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt @@ -112,7 +112,11 @@ constructor( communalSceneInteractor.editModeState.value == EditModeState.STARTING || communalSceneInteractor.isLaunchingWidget.value if (!delaySceneTransition) { - communalSceneInteractor.changeScene(nextScene, nextTransition) + communalSceneInteractor.changeScene( + newScene = nextScene, + loggingReason = "KTF syncing", + transitionKey = nextTransition, + ) } } .launchIn(applicationScope) @@ -176,7 +180,10 @@ constructor( if (scene == CommunalScenes.Communal && isDreaming && timeoutJob == null) { // If dreaming starts after timeout has expired, ex. if dream restarts under // the hub, just close the hub immediately. - communalSceneInteractor.changeScene(CommunalScenes.Blank) + communalSceneInteractor.changeScene( + CommunalScenes.Blank, + "dream started after timeout", + ) } } } @@ -201,7 +208,10 @@ constructor( bgScope.launch { delay(screenTimeout.milliseconds) if (isDreaming) { - communalSceneInteractor.changeScene(CommunalScenes.Blank) + communalSceneInteractor.changeScene( + newScene = CommunalScenes.Blank, + loggingReason = "hub timeout", + ) } timeoutJob = null } diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index 99bcc12da576..7181b15138b9 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -329,8 +329,11 @@ constructor( @Deprecated( "Use com.android.systemui.communal.domain.interactor.CommunalSceneInteractor instead" ) - fun changeScene(newScene: SceneKey, transitionKey: TransitionKey? = null) = - communalSceneInteractor.changeScene(newScene, transitionKey) + fun changeScene( + newScene: SceneKey, + loggingReason: String, + transitionKey: TransitionKey? = null + ) = communalSceneInteractor.changeScene(newScene, loggingReason, transitionKey) fun setEditModeOpen(isOpen: Boolean) { _editModeOpen.value = isOpen diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt index e45a69599a68..a0b996675331 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt @@ -22,6 +22,7 @@ import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.TransitionKey import com.android.systemui.communal.data.repository.CommunalSceneRepository import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel +import com.android.systemui.communal.shared.log.CommunalSceneLogger import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.communal.shared.model.EditModeState @@ -29,6 +30,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf +import com.android.systemui.util.kotlin.pairwiseBy import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -42,8 +44,8 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton @@ -51,7 +53,8 @@ class CommunalSceneInteractor @Inject constructor( @Application private val applicationScope: CoroutineScope, - private val communalSceneRepository: CommunalSceneRepository, + private val repository: CommunalSceneRepository, + private val logger: CommunalSceneLogger, ) { private val _isLaunchingWidget = MutableStateFlow(false) @@ -80,25 +83,39 @@ constructor( */ fun changeScene( newScene: SceneKey, + loggingReason: String, transitionKey: TransitionKey? = null, keyguardState: KeyguardState? = null, ) { - applicationScope.launch { + applicationScope.launch("$TAG#changeScene") { + logger.logSceneChangeRequested( + from = currentScene.value, + to = newScene, + reason = loggingReason, + isInstant = false, + ) notifyListeners(newScene, keyguardState) - communalSceneRepository.changeScene(newScene, transitionKey) + repository.changeScene(newScene, transitionKey) } } /** Immediately snaps to the new scene. */ fun snapToScene( newScene: SceneKey, + loggingReason: String, delayMillis: Long = 0, keyguardState: KeyguardState? = null ) { applicationScope.launch("$TAG#snapToScene") { delay(delayMillis) + logger.logSceneChangeRequested( + from = currentScene.value, + to = newScene, + reason = loggingReason, + isInstant = true, + ) notifyListeners(newScene, keyguardState) - communalSceneRepository.snapToScene(newScene) + repository.snapToScene(newScene) } } @@ -113,13 +130,30 @@ constructor( if (_editModeState.value == EditModeState.STARTING) { return } - changeScene(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade) + changeScene( + CommunalScenes.Blank, + "activity start dismissing keyguard", + CommunalTransitionKeys.SimpleFade, + ) } /** * Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene]. */ - val currentScene: Flow<SceneKey> = communalSceneRepository.currentScene + val currentScene: StateFlow<SceneKey> = + repository.currentScene + .pairwiseBy(initialValue = repository.currentScene.value) { from, to -> + logger.logSceneChangeCommitted( + from = from, + to = to, + ) + to + } + .stateIn( + scope = applicationScope, + started = SharingStarted.Eagerly, + initialValue = repository.currentScene.value, + ) private val _editModeState = MutableStateFlow<EditModeState?>(null) /** @@ -134,7 +168,13 @@ constructor( /** Transition state of the hub mode. */ val transitionState: StateFlow<ObservableTransitionState> = - communalSceneRepository.transitionState + repository.transitionState + .onEach { logger.logSceneTransition(it) } + .stateIn( + scope = applicationScope, + started = SharingStarted.Eagerly, + initialValue = repository.transitionState.value, + ) /** * Updates the transition state of the hub [SceneTransitionLayout]. @@ -142,7 +182,7 @@ constructor( * Note that you must call is with `null` when the UI is done or risk a memory leak. */ fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) { - communalSceneRepository.setTransitionState(transitionState) + repository.setTransitionState(transitionState) } /** Returns a flow that tracks the progress of transitions to the given scene from 0-1. */ diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalSceneLogger.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalSceneLogger.kt new file mode 100644 index 000000000000..aed92156cfc3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalSceneLogger.kt @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.shared.log + +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.dagger.CommunalLog +import javax.inject.Inject + +class CommunalSceneLogger @Inject constructor(@CommunalLog private val logBuffer: LogBuffer) { + + fun logSceneChangeRequested( + from: SceneKey, + to: SceneKey, + reason: String, + isInstant: Boolean, + ) { + logBuffer.log( + tag = TAG, + level = LogLevel.INFO, + messageInitializer = { + str1 = from.toString() + str2 = to.toString() + str3 = reason + bool1 = isInstant + }, + messagePrinter = { + buildString { + append("Scene change requested: $str1 → $str2") + if (isInstant) { + append(" (instant)") + } + append(", reason: $str3") + } + }, + ) + } + + fun logSceneChangeCommitted( + from: SceneKey, + to: SceneKey, + ) { + logBuffer.log( + tag = TAG, + level = LogLevel.INFO, + messageInitializer = { + str1 = from.toString() + str2 = to.toString() + }, + messagePrinter = { "Scene change committed: $str1 → $str2" }, + ) + } + + fun logSceneTransition(transitionState: ObservableTransitionState) { + when (transitionState) { + is ObservableTransitionState.Transition -> { + logBuffer.log( + tag = TAG, + level = LogLevel.INFO, + messageInitializer = { + str1 = transitionState.fromScene.toString() + str2 = transitionState.toScene.toString() + }, + messagePrinter = { "Scene transition started: $str1 → $str2" }, + ) + } + is ObservableTransitionState.Idle -> { + logBuffer.log( + tag = TAG, + level = LogLevel.INFO, + messageInitializer = { str1 = transitionState.currentScene.toString() }, + messagePrinter = { "Scene transition idle on: $str1" }, + ) + } + } + } + + companion object { + private const val TAG = "CommunalSceneLogger" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt index d1a5a4b8641f..b8221332eadf 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt @@ -106,10 +106,11 @@ abstract class BaseCommunalViewModel( */ fun changeScene( scene: SceneKey, + loggingReason: String, transitionKey: TransitionKey? = null, keyguardState: KeyguardState? = null ) { - communalSceneInteractor.changeScene(scene, transitionKey, keyguardState) + communalSceneInteractor.changeScene(scene, loggingReason, transitionKey, keyguardState) } fun setEditModeState(state: EditModeState?) = communalSceneInteractor.setEditModeState(state) diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt index bbd8596a76bd..623937305921 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt @@ -67,7 +67,10 @@ constructor( * transition. */ fun snapToCommunal() { - communalSceneInteractor.snapToScene(CommunalScenes.Communal) + communalSceneInteractor.snapToScene( + newScene = CommunalScenes.Communal, + loggingReason = "transition view model", + ) } // Show UMO on glanceable hub immediately on transition into glanceable hub diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt index 08444623f24d..e7cedc61610f 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt @@ -42,6 +42,7 @@ class CommunalTransitionAnimatorController( // TODO(b/330672236): move this to onTransitionAnimationEnd() without the delay. communalSceneInteractor.snapToScene( CommunalScenes.Blank, + "CommunalTransitionAnimatorController", ActivityTransitionAnimator.TIMINGS.totalDuration ) } 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 668fef6130bb..6d7cdc472f0a 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt @@ -218,9 +218,10 @@ constructor( lifecycleScope.launch { communalViewModel.canShowEditMode.collect { communalViewModel.changeScene( - CommunalScenes.Blank, - CommunalTransitionKeys.ToEditMode, - KeyguardState.GONE, + scene = CommunalScenes.Blank, + loggingReason = "edit mode opening", + transitionKey = CommunalTransitionKeys.ToEditMode, + keyguardState = KeyguardState.GONE, ) // wait till transitioned to Blank scene, then animate in communal content in // edit mode @@ -252,8 +253,9 @@ constructor( communalViewModel.cleanupEditModeState() communalViewModel.changeScene( - CommunalScenes.Communal, - CommunalTransitionKeys.FromEditMode + scene = CommunalScenes.Communal, + loggingReason = "edit mode closing", + transitionKey = CommunalTransitionKeys.FromEditMode ) // Wait for the current scene to be idle on communal. diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java index 4b9e5a024393..0c1fb72cc581 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java @@ -442,7 +442,9 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ @Override public void onWakeRequested() { mUiEventLogger.log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_DREAM_AWAKE_START); - mCommunalInteractor.changeScene(CommunalScenes.Communal, null); + mCommunalInteractor.changeScene(CommunalScenes.Communal, + "dream wake requested", + null); } private Lifecycle.State getLifecycleStateLocked() { @@ -493,7 +495,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ mSystemDialogsCloser.closeSystemDialogs(); // Hide glanceable hub (this is a nop if glanceable hub is not open). - mCommunalInteractor.changeScene(CommunalScenes.Blank, null); + mCommunalInteractor.changeScene(CommunalScenes.Blank, "dream come to front", null); } /** diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt index 4b07f78eb825..5c0335a6dfae 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt @@ -20,9 +20,9 @@ import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.Flags.glanceableHubAllowKeyguardWhenDreaming import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.communal.domain.interactor.CommunalInteractor -import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager +import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING @@ -51,6 +51,7 @@ constructor( fromGlanceableHubTransitionInteractor: GlanceableHubToDreamingTransitionViewModel, toGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel, private val toLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel, + private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor, private val communalInteractor: CommunalInteractor, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val userTracker: UserTracker, @@ -61,11 +62,9 @@ constructor( val showGlanceableHub = communalInteractor.isCommunalEnabled.value && !keyguardUpdateMonitor.isEncryptedOrLockdown(userTracker.userId) - if (showGlanceableHub && !glanceableHubAllowKeyguardWhenDreaming()) { - communalInteractor.changeScene(CommunalScenes.Communal) - } else { - toLockscreenTransitionViewModel.startTransition() - } + fromDreamingTransitionInteractor.startToLockscreenOrGlanceableHubTransition( + showGlanceableHub && !glanceableHubAllowKeyguardWhenDreaming() + ) } val dreamOverlayTranslationX: Flow<Float> = 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 0c12f8cee943..90aaf0d617f7 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 @@ -273,9 +273,10 @@ constructor( private suspend fun transitionToGlanceableHub() { if (communalSceneKtfRefactor()) { communalSceneInteractor.changeScene( - CommunalScenes.Communal, + newScene = CommunalScenes.Communal, + loggingReason = "from dozing to hub", // Immediately show the hub when transitioning from dozing to hub. - CommunalTransitionKeys.Immediately, + transitionKey = CommunalTransitionKeys.Immediately, ) } else { startTransitionTo(KeyguardState.GLANCEABLE_HUB) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt index 7bf9c2f1dc7a..4666430398ec 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt @@ -20,7 +20,9 @@ import android.animation.ValueAnimator import com.android.app.animation.Interpolators import com.android.app.tracing.coroutines.launch import com.android.systemui.Flags.communalSceneKtfRefactor +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor +import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -58,6 +60,7 @@ constructor( @Main mainDispatcher: CoroutineDispatcher, keyguardInteractor: KeyguardInteractor, private val glanceableHubTransitions: GlanceableHubTransitions, + private val communalSceneInteractor: CommunalSceneInteractor, private val communalSettingsInteractor: CommunalSettingsInteractor, powerInteractor: PowerInteractor, keyguardOcclusionInteractor: KeyguardOcclusionInteractor, @@ -126,17 +129,24 @@ constructor( } } - fun startToLockscreenTransition() { + fun startToLockscreenOrGlanceableHubTransition(openHub: Boolean) { scope.launch { if ( transitionInteractor.startedKeyguardState.replayCache.last() == KeyguardState.DREAMING ) { if (powerInteractor.detailedWakefulness.value.isAwake()) { - startTransitionTo( - KeyguardState.LOCKSCREEN, - ownerReason = "Dream has ended and device is awake" - ) + if (openHub) { + communalSceneInteractor.changeScene( + newScene = CommunalScenes.Communal, + loggingReason = "FromDreamingTransitionInteractor", + ) + } else { + startTransitionTo( + KeyguardState.LOCKSCREEN, + ownerReason = "Dream has ended and device is awake" + ) + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt index befcc9e6c8ee..c9db26dca9e2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt @@ -159,6 +159,7 @@ constructor( if (communalSceneKtfRefactor()) { communalSceneInteractor.changeScene( newScene = CommunalScenes.Blank, + loggingReason = "hub to dozing", transitionKey = CommunalTransitionKeys.Immediately, keyguardState = KeyguardState.DOZING, ) @@ -182,6 +183,7 @@ constructor( if (communalSceneKtfRefactor()) { communalSceneInteractor.changeScene( newScene = CommunalScenes.Blank, + loggingReason = "hub to occluded (KeyguardWmStateRefactor)", transitionKey = CommunalTransitionKeys.SimpleFade, keyguardState = state, ) @@ -211,6 +213,7 @@ constructor( .collect { _ -> communalSceneInteractor.changeScene( newScene = CommunalScenes.Blank, + loggingReason = "hub to occluded", transitionKey = CommunalTransitionKeys.SimpleFade, keyguardState = KeyguardState.OCCLUDED, ) @@ -254,6 +257,7 @@ constructor( } else { communalSceneInteractor.changeScene( newScene = CommunalScenes.Blank, + loggingReason = "hub to gone", transitionKey = CommunalTransitionKeys.SimpleFade, keyguardState = KeyguardState.GONE ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt index 905ca8eda87f..7b6949fdaa2c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt @@ -146,8 +146,9 @@ constructor( if (SceneContainerFlag.isEnabled) return if (communalSceneKtfRefactor()) { communalSceneInteractor.changeScene( - CommunalScenes.Communal, - CommunalTransitionKeys.SimpleFade + newScene = CommunalScenes.Communal, + loggingReason = "occluded to hub", + transitionKey = CommunalTransitionKeys.SimpleFade ) } else { startTransitionTo(KeyguardState.GLANCEABLE_HUB) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt index 2823b93b4847..0118f8e21f79 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt @@ -175,7 +175,10 @@ constructor( !communalSceneInteractor.isLaunchingWidget.value && communalSceneInteractor.editModeState.value == null ) { - communalSceneInteractor.snapToScene(CommunalScenes.Blank) + communalSceneInteractor.snapToScene( + newScene = CommunalScenes.Blank, + loggingReason = "FromPrimaryBouncerTransitionInteractor", + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt index 15e6b1d78ea0..d119ed4f6580 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt @@ -92,8 +92,8 @@ constructor( val button = view as ImageView val configurationBasedDimensions = MutableStateFlow(loadFromResources(view)) val disposableHandle = - view.repeatWhenAttached(mainImmediateDispatcher) { - repeatOnLifecycle(Lifecycle.State.STARTED) { + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.CREATED) { launch("$TAG#viewModel") { viewModel.collect { buttonModel -> updateButton( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModel.kt index 34c1436c3235..38f5d3e76c7c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModel.kt @@ -50,5 +50,9 @@ constructor( } .distinctUntilChanged() - fun openCommunalHub() = communalInteractor.changeScene(CommunalScenes.Communal) + fun openCommunalHub() = + communalInteractor.changeScene( + newScene = CommunalScenes.Communal, + loggingReason = "accessibility", + ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt index b5ec7a6511f1..10605b28a862 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt @@ -18,7 +18,6 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.app.animation.Interpolators.EMPHASIZED import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING @@ -39,10 +38,8 @@ import kotlinx.coroutines.flow.Flow class DreamingToLockscreenTransitionViewModel @Inject constructor( - private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor, animationFlow: KeyguardTransitionAnimationFlow, ) : DeviceEntryIconTransition { - fun startTransition() = fromDreamingTransitionInteractor.startToLockscreenTransition() private val transitionAnimation = animationFlow.setup( diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt index 62a72184190d..c21301c62c8d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt @@ -223,7 +223,7 @@ constructor( if (!mediaFlags.isSceneContainerEnabled()) { MediaPlayerData.players().forEach { it.updateAnimatorDurationScale() } } else { - controllerByViewModel.values.forEach { it.updateAnimatorDurationScale() } + controllerById.values.forEach { it.updateAnimatorDurationScale() } } } } @@ -324,7 +324,7 @@ constructor( private var widthInSceneContainerPx = 0 private var heightInSceneContainerPx = 0 - private val controllerByViewModel = mutableMapOf<MediaCommonViewModel, MediaViewController>() + private val controllerById = mutableMapOf<String, MediaViewController>() private val commonViewModels = mutableListOf<MediaCommonViewModel>() init { @@ -738,7 +738,7 @@ constructor( viewController.heightInSceneContainerPx = heightInSceneContainerPx } viewController.attachPlayer(viewHolder) - viewController.mediaViewHolder.player.layoutParams = lp + viewController.mediaViewHolder?.player?.layoutParams = lp MediaControlViewBinder.bind( viewHolder, commonViewModel.controlViewModel, @@ -749,12 +749,13 @@ constructor( mediaFlags ) mediaContent.addView(viewHolder.player, position) + controllerById[commonViewModel.instanceId.toString()] = viewController } is MediaCommonViewModel.MediaRecommendations -> { val viewHolder = RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent) viewController.attachRecommendations(viewHolder) - viewController.recommendationViewHolder.recommendations.layoutParams = lp + viewController.recommendationViewHolder?.recommendations?.layoutParams = lp MediaRecommendationsViewBinder.bind( viewHolder, commonViewModel.recsViewModel, @@ -762,11 +763,11 @@ constructor( falsingManager, ) mediaContent.addView(viewHolder.recommendations, position) + controllerById[commonViewModel.key] = viewController } } onAddOrUpdateVisibleToUserCard(position, isMediaCardUpdate = false) viewController.setListening(mediaCarouselScrollHandler.visibleToUser && currentlyExpanded) - controllerByViewModel[commonViewModel] = viewController updateViewControllerToState(viewController, noAnimation = true) updatePageIndicator() if ( @@ -793,14 +794,19 @@ constructor( } private fun onRemoved(commonViewModel: MediaCommonViewModel) { - controllerByViewModel.remove(commonViewModel)?.let { + val id = + when (commonViewModel) { + is MediaCommonViewModel.MediaControl -> commonViewModel.instanceId.toString() + is MediaCommonViewModel.MediaRecommendations -> commonViewModel.key + } + controllerById.remove(id)?.let { when (commonViewModel) { is MediaCommonViewModel.MediaControl -> { - mediaCarouselScrollHandler.onPrePlayerRemoved(it.mediaViewHolder.player) - mediaContent.removeView(it.mediaViewHolder.player) + mediaCarouselScrollHandler.onPrePlayerRemoved(it.mediaViewHolder!!.player) + mediaContent.removeView(it.mediaViewHolder!!.player) } is MediaCommonViewModel.MediaRecommendations -> { - mediaContent.removeView(it.recommendationViewHolder.recommendations) + mediaContent.removeView(it.recommendationViewHolder!!.recommendations) } } it.onDestroy() @@ -811,14 +817,19 @@ constructor( } private fun onMoved(commonViewModel: MediaCommonViewModel, from: Int, to: Int) { - controllerByViewModel[commonViewModel]?.let { + val id = + when (commonViewModel) { + is MediaCommonViewModel.MediaControl -> commonViewModel.instanceId.toString() + is MediaCommonViewModel.MediaRecommendations -> commonViewModel.key + } + controllerById[id]?.let { mediaContent.removeViewAt(from) when (commonViewModel) { is MediaCommonViewModel.MediaControl -> { - mediaContent.addView(it.mediaViewHolder.player, to) + mediaContent.addView(it.mediaViewHolder!!.player, to) } is MediaCommonViewModel.MediaRecommendations -> { - mediaContent.addView(it.recommendationViewHolder.recommendations, to) + mediaContent.addView(it.recommendationViewHolder!!.recommendations, to) } } } @@ -855,15 +866,16 @@ constructor( } } .toHashSet() - controllerByViewModel - .filter { - when (val viewModel = it.key) { - is MediaCommonViewModel.MediaControl -> - !viewIds.contains(viewModel.instanceId.toString()) - is MediaCommonViewModel.MediaRecommendations -> !viewIds.contains(viewModel.key) - } + controllerById + .filter { !viewIds.contains(it.key) } + .forEach { + mediaCarouselScrollHandler.onPrePlayerRemoved(it.value.mediaViewHolder?.player) + mediaContent.removeView(it.value.mediaViewHolder?.player) + mediaContent.removeView(it.value.recommendationViewHolder?.recommendations) + it.value.onDestroy() + mediaCarouselScrollHandler.onPlayersChanged() + updatePageIndicator() } - .forEach { onRemoved(it.key) } } private suspend fun getMediaLockScreenSetting(): Boolean { @@ -1176,12 +1188,12 @@ constructor( commonViewModels.forEach { viewModel -> when (viewModel) { is MediaCommonViewModel.MediaControl -> { - controllerByViewModel[viewModel]?.mediaViewHolder?.let { + controllerById[viewModel.instanceId.toString()]?.mediaViewHolder?.let { mediaContent.addView(it.player) } } is MediaCommonViewModel.MediaRecommendations -> { - controllerByViewModel[viewModel]?.recommendationViewHolder?.let { + controllerById[viewModel.key]?.recommendationViewHolder?.let { mediaContent.addView(it.recommendations) } } @@ -1234,9 +1246,7 @@ constructor( updateViewControllerToState(mediaPlayer.mediaViewController, immediately) } } else { - controllerByViewModel.values.forEach { - updateViewControllerToState(it, immediately) - } + controllerById.values.forEach { updateViewControllerToState(it, immediately) } } maybeResetSettingsCog() updatePageIndicatorAlpha() @@ -1296,9 +1306,7 @@ constructor( player.setListening(visibleToUser && currentlyExpanded) } } else { - controllerByViewModel.values.forEach { - it.setListening(visibleToUser && currentlyExpanded) - } + controllerById.values.forEach { it.setListening(visibleToUser && currentlyExpanded) } } } @@ -1316,7 +1324,7 @@ constructor( Math.max(height, controller.currentHeight + controller.translationY.toInt()) } } else { - controllerByViewModel.values.forEach { + controllerById.values.forEach { // When transitioning the view to gone, the view gets smaller, but the translation // Doesn't, let's add the translation width = Math.max(width, it.currentWidth + it.translationX.toInt()) @@ -1413,7 +1421,7 @@ constructor( mediaPlayer.mediaViewController.onLocationPreChange(desiredLocation) } } else { - controllerByViewModel.values.forEach { controller -> + controllerById.values.forEach { controller -> if (animate) { controller.animatePendingStateChange(duration, startDelay) } @@ -1441,7 +1449,7 @@ constructor( if (!mediaFlags.isSceneContainerEnabled()) { MediaPlayerData.players().forEach { it.closeGuts(immediate) } } else { - controllerByViewModel.values.forEach { it.closeGuts(immediate) } + controllerById.values.forEach { it.closeGuts(immediate) } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt index 681bf390e3e9..584908ff2aad 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt @@ -196,8 +196,8 @@ constructor( private var isNextButtonAvailable = false /** View holders for controller */ - lateinit var recommendationViewHolder: RecommendationViewHolder - lateinit var mediaViewHolder: MediaViewHolder + var recommendationViewHolder: RecommendationViewHolder? = null + var mediaViewHolder: MediaViewHolder? = null private lateinit var seekBarObserver: SeekBarObserver private lateinit var turbulenceNoiseController: TurbulenceNoiseController @@ -752,16 +752,18 @@ constructor( private fun updateDisplayForScrubbingChange() { mainExecutor.execute { val isTimeVisible = canShowScrubbingTime && isScrubbing - MediaControlViewBinder.setVisibleAndAlpha( - expandedLayout, - mediaViewHolder.scrubbingTotalTimeView.id, - isTimeVisible - ) - MediaControlViewBinder.setVisibleAndAlpha( - expandedLayout, - mediaViewHolder.scrubbingElapsedTimeView.id, - isTimeVisible - ) + mediaViewHolder!!.let { + MediaControlViewBinder.setVisibleAndAlpha( + expandedLayout, + it.scrubbingTotalTimeView.id, + isTimeVisible + ) + MediaControlViewBinder.setVisibleAndAlpha( + expandedLayout, + it.scrubbingElapsedTimeView.id, + isTimeVisible + ) + } MediaControlViewModel.SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING.forEach { id -> val isButtonVisible: Boolean @@ -780,14 +782,16 @@ constructor( notVisibleValue = ConstraintSet.GONE } } - MediaControlViewBinder.setSemanticButtonVisibleAndAlpha( - mediaViewHolder.getAction(id), - expandedLayout, - collapsedLayout, - isButtonVisible, - notVisibleValue, - showInCollapsed = true - ) + mediaViewHolder!!.let { + MediaControlViewBinder.setSemanticButtonVisibleAndAlpha( + it.getAction(id), + expandedLayout, + collapsedLayout, + isButtonVisible, + notVisibleValue, + showInCollapsed = true + ) + } } if (!metadataAnimationHandler.isRunning) { @@ -813,39 +817,41 @@ constructor( fun setUpTurbulenceNoise() { if (!mediaFlags.isSceneContainerEnabled()) return - if (!this::turbulenceNoiseAnimationConfig.isInitialized) { - turbulenceNoiseAnimationConfig = - createTurbulenceNoiseConfig( - mediaViewHolder.loadingEffectView, - mediaViewHolder.turbulenceNoiseView, - colorSchemeTransition - ) - } - if (Flags.shaderlibLoadingEffectRefactor()) { - if (!this::loadingEffect.isInitialized) { - loadingEffect = - LoadingEffect( - TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE, - turbulenceNoiseAnimationConfig, - noiseDrawCallback, - stateChangedCallback + mediaViewHolder!!.let { + if (!this::turbulenceNoiseAnimationConfig.isInitialized) { + turbulenceNoiseAnimationConfig = + createTurbulenceNoiseConfig( + it.loadingEffectView, + it.turbulenceNoiseView, + colorSchemeTransition ) } - colorSchemeTransition.loadingEffect = loadingEffect - loadingEffect.play() - mainExecutor.executeDelayed( - loadingEffect::finish, - MediaControlViewModel.TURBULENCE_NOISE_PLAY_MS_DURATION - ) - } else { - turbulenceNoiseController.play( - TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE, - turbulenceNoiseAnimationConfig - ) - mainExecutor.executeDelayed( - turbulenceNoiseController::finish, - MediaControlViewModel.TURBULENCE_NOISE_PLAY_MS_DURATION - ) + if (Flags.shaderlibLoadingEffectRefactor()) { + if (!this::loadingEffect.isInitialized) { + loadingEffect = + LoadingEffect( + TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE, + turbulenceNoiseAnimationConfig, + noiseDrawCallback, + stateChangedCallback + ) + } + colorSchemeTransition.loadingEffect = loadingEffect + loadingEffect.play() + mainExecutor.executeDelayed( + loadingEffect::finish, + MediaControlViewModel.TURBULENCE_NOISE_PLAY_MS_DURATION + ) + } else { + turbulenceNoiseController.play( + TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE, + turbulenceNoiseAnimationConfig + ) + mainExecutor.executeDelayed( + turbulenceNoiseController::finish, + MediaControlViewModel.TURBULENCE_NOISE_PLAY_MS_DURATION + ) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt index 9fa6769fe5f3..bb238f2e20fe 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt @@ -19,6 +19,7 @@ package com.android.systemui.qs import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.fragments.FragmentService +import com.android.systemui.qs.composefragment.QSFragmentCompose import dagger.Binds import dagger.Module import dagger.multibindings.ClassKey @@ -31,13 +32,18 @@ class QSFragmentStartable @Inject constructor( private val fragmentService: FragmentService, - private val qsFragmentLegacyProvider: Provider<QSFragmentLegacy> + private val qsFragmentLegacyProvider: Provider<QSFragmentLegacy>, + private val qsFragmentComposeProvider: Provider<QSFragmentCompose>, ) : CoreStartable { override fun start() { fragmentService.addFragmentInstantiationProvider( QSFragmentLegacy::class.java, qsFragmentLegacyProvider ) + fragmentService.addFragmentInstantiationProvider( + QSFragmentCompose::class.java, + qsFragmentComposeProvider + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSModesEvent.kt b/packages/SystemUI/src/com/android/systemui/qs/QSModesEvent.kt new file mode 100644 index 000000000000..1891c41f364b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/QSModesEvent.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs + +import com.android.internal.logging.UiEvent +import com.android.internal.logging.UiEventLogger + +/** Events of user interactions with modes from the QS Modes dialog. {@see ModesDialogViewModel} */ +enum class QSModesEvent(private val _id: Int) : UiEventLogger.UiEventEnum { + @UiEvent(doc = "User turned manual Do Not Disturb on via modes dialog") QS_MODES_DND_ON(1870), + @UiEvent(doc = "User turned manual Do Not Disturb off via modes dialog") QS_MODES_DND_OFF(1871), + @UiEvent(doc = "User opened mode settings from the Do Not Disturb tile in the modes dialog") + QS_MODES_DND_SETTINGS(1872), + @UiEvent(doc = "User turned automatic mode on via modes dialog") QS_MODES_MODE_ON(1873), + @UiEvent(doc = "User turned automatic mode off via modes dialog") QS_MODES_MODE_OFF(1874), + @UiEvent(doc = "User opened mode settings from a mode tile in the modes dialog") + QS_MODES_MODE_SETTINGS(1875), + @UiEvent(doc = "User clicked on Settings from the modes dialog") QS_MODES_SETTINGS(1876), + @UiEvent(doc = "User clicked on Do Not Disturb tile, opening the time selection dialog") + QS_MODES_DURATION_DIALOG(1879); + + override fun getId() = _id +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java index f207b1de3cba..bc695bdd4e05 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java @@ -34,6 +34,7 @@ import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.statusbar.policy.SplitShadeStateController; import com.android.systemui.util.leak.RotationUtils; @@ -77,9 +78,11 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> @Override protected void onInit() { super.onInit(); - updateMediaExpansion(); - mMediaHost.setShowsOnlyActiveMedia(true); - mMediaHost.init(MediaHierarchyManager.LOCATION_QQS); + if (!SceneContainerFlag.isEnabled()) { + updateMediaExpansion(); + mMediaHost.setShowsOnlyActiveMedia(true); + mMediaHost.init(MediaHierarchyManager.LOCATION_QQS); + } } @Override @@ -125,7 +128,9 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> if (newMaxTiles != mView.getNumQuickTiles()) { setMaxTiles(newMaxTiles); } - updateMediaExpansion(); + if (!SceneContainerFlag.isEnabled()) { + updateMediaExpansion(); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt new file mode 100644 index 000000000000..5d81d4f3f9ec --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt @@ -0,0 +1,442 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.composefragment + +import android.annotation.SuppressLint +import android.graphics.Rect +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.activity.OnBackPressedDispatcher +import androidx.activity.OnBackPressedDispatcherOwner +import androidx.activity.setViewTreeOnBackPressedDispatcherOwner +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.layout +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.positionInRoot +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.unit.round +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.android.compose.modifiers.height +import com.android.compose.modifiers.padding +import com.android.compose.theme.PlatformTheme +import com.android.systemui.compose.modifiers.sysuiResTag +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.plugins.qs.QS +import com.android.systemui.plugins.qs.QSContainerController +import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel +import com.android.systemui.qs.flags.QSComposeFragment +import com.android.systemui.qs.footer.ui.compose.FooterActions +import com.android.systemui.qs.panels.ui.compose.QuickQuickSettings +import com.android.systemui.qs.ui.composable.QuickSettingsTheme +import com.android.systemui.qs.ui.composable.ShadeBody +import com.android.systemui.res.R +import com.android.systemui.util.LifecycleFragment +import java.util.function.Consumer +import javax.inject.Inject +import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch + +@SuppressLint("ValidFragment") +class QSFragmentCompose +@Inject +constructor( + private val qsFragmentComposeViewModelFactory: QSFragmentComposeViewModel.Factory, +) : LifecycleFragment(), QS { + + private val scrollListener = MutableStateFlow<QS.ScrollListener?>(null) + private val heightListener = MutableStateFlow<QS.HeightListener?>(null) + private val qsContainerController = MutableStateFlow<QSContainerController?>(null) + + private lateinit var viewModel: QSFragmentComposeViewModel + + // Starting with a non-zero value makes it so that it has a non-zero height on first expansion + // This is important for `QuickSettingsControllerImpl.mMinExpansionHeight` to detect a "change". + private val qqsHeight = MutableStateFlow(1) + private val qsHeight = MutableStateFlow(0) + private val qqsVisible = MutableStateFlow(false) + private val qqsPositionOnRoot = Rect() + private val composeViewPositionOnScreen = Rect() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + QSComposeFragment.isUnexpectedlyInLegacyMode() + viewModel = qsFragmentComposeViewModelFactory.create(lifecycleScope) + + setListenerCollections() + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val context = inflater.context + return ComposeView(context).apply { + setBackPressedDispatcher() + setContent { + PlatformTheme { + val visible by viewModel.qsVisible.collectAsStateWithLifecycle() + val qsState by viewModel.expansionState.collectAsStateWithLifecycle() + + AnimatedVisibility( + visible = visible, + modifier = Modifier.windowInsetsPadding(WindowInsets.navigationBars) + ) { + AnimatedContent(targetState = qsState) { + when (it) { + QSFragmentComposeViewModel.QSExpansionState.QQS -> { + QuickQuickSettingsElement() + } + QSFragmentComposeViewModel.QSExpansionState.QS -> { + QuickSettingsElement() + } + else -> {} + } + } + } + } + } + } + } + + override fun setPanelView(notificationPanelView: QS.HeightListener?) { + heightListener.value = notificationPanelView + } + + override fun hideImmediately() { + // view?.animate()?.cancel() + // view?.y = -qsMinExpansionHeight.toFloat() + } + + override fun getQsMinExpansionHeight(): Int { + // TODO (b/353253277) implement split screen + return qqsHeight.value + } + + override fun getDesiredHeight(): Int { + /* + * Looking at the code, it seems that + * * If customizing, then the height is that of the view post-layout, which is set by + * QSContainerImpl.calculateContainerHeight, which is the height the customizer takes + * * If not customizing, it's the measured height. So we may want to surface that. + */ + return view?.height ?: 0 + } + + override fun setHeightOverride(desiredHeight: Int) { + viewModel.heightOverrideValue = desiredHeight + } + + override fun setHeaderClickable(qsExpansionEnabled: Boolean) { + // Empty method + } + + override fun isCustomizing(): Boolean { + return viewModel.containerViewModel.editModeViewModel.isEditing.value + } + + override fun closeCustomizer() { + viewModel.containerViewModel.editModeViewModel.stopEditing() + } + + override fun setOverscrolling(overscrolling: Boolean) { + viewModel.stackScrollerOverscrollingValue = overscrolling + } + + override fun setExpanded(qsExpanded: Boolean) { + viewModel.isQSExpanded = qsExpanded + } + + override fun setListening(listening: Boolean) { + // Not needed, views start listening and collection when composed + } + + override fun setQsVisible(qsVisible: Boolean) { + viewModel.isQSVisible = qsVisible + } + + override fun isShowingDetail(): Boolean { + return isCustomizing + } + + override fun closeDetail() { + closeCustomizer() + } + + override fun animateHeaderSlidingOut() { + // TODO(b/353254353) + } + + override fun setQsExpansion( + qsExpansionFraction: Float, + panelExpansionFraction: Float, + headerTranslation: Float, + squishinessFraction: Float + ) { + viewModel.qsExpansionValue = qsExpansionFraction + viewModel.panelExpansionFractionValue = panelExpansionFraction + viewModel.squishinessFractionValue = squishinessFraction + + // TODO(b/353254353) Handle header translation + } + + override fun setHeaderListening(listening: Boolean) { + // Not needed, header will start listening as soon as it's composed + } + + override fun notifyCustomizeChanged() { + // Not needed, only called from inside customizer + } + + override fun setContainerController(controller: QSContainerController?) { + qsContainerController.value = controller + } + + override fun setCollapseExpandAction(action: Runnable?) { + // Nothing to do yet. But this should be wired to a11y + } + + override fun getHeightDiff(): Int { + return 0 // For now TODO(b/353254353) + } + + override fun getHeader(): View? { + QSComposeFragment.isUnexpectedlyInLegacyMode() + return null + } + + override fun setShouldUpdateSquishinessOnMedia(shouldUpdate: Boolean) { + super.setShouldUpdateSquishinessOnMedia(shouldUpdate) + // TODO (b/353253280) + } + + override fun setInSplitShade(shouldTranslate: Boolean) { + // TODO (b/356435605) + } + + override fun setTransitionToFullShadeProgress( + isTransitioningToFullShade: Boolean, + qsTransitionFraction: Float, + qsSquishinessFraction: Float + ) { + super.setTransitionToFullShadeProgress( + isTransitioningToFullShade, + qsTransitionFraction, + qsSquishinessFraction + ) + } + + override fun setFancyClipping( + leftInset: Int, + top: Int, + rightInset: Int, + bottom: Int, + cornerRadius: Int, + visible: Boolean, + fullWidth: Boolean + ) {} + + override fun isFullyCollapsed(): Boolean { + return !viewModel.isQSVisible + } + + override fun setCollapsedMediaVisibilityChangedListener(listener: Consumer<Boolean>?) { + // TODO (b/353253280) + } + + override fun setScrollListener(scrollListener: QS.ScrollListener?) { + this.scrollListener.value = scrollListener + } + + override fun setOverScrollAmount(overScrollAmount: Int) { + super.setOverScrollAmount(overScrollAmount) + } + + override fun setIsNotificationPanelFullWidth(isFullWidth: Boolean) { + viewModel.isSmallScreenValue = isFullWidth + } + + override fun getHeaderTop(): Int { + return viewModel.qqsHeaderHeight.value + } + + override fun getHeaderBottom(): Int { + return headerTop + qqsHeight.value + } + + override fun getHeaderLeft(): Int { + return qqsPositionOnRoot.left + } + + override fun getHeaderBoundsOnScreen(outBounds: Rect) { + outBounds.set(qqsPositionOnRoot) + view?.getBoundsOnScreen(composeViewPositionOnScreen) + ?: run { composeViewPositionOnScreen.setEmpty() } + qqsPositionOnRoot.offset(composeViewPositionOnScreen.left, composeViewPositionOnScreen.top) + } + + override fun isHeaderShown(): Boolean { + return qqsVisible.value + } + + private fun setListenerCollections() { + lifecycleScope.launch { + lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + // TODO + // setListenerJob( + // scrollListener, + // + // ) + } + launch { + setListenerJob( + heightListener, + viewModel.containerViewModel.editModeViewModel.isEditing + ) { + onQsHeightChanged() + } + } + launch { + setListenerJob( + qsContainerController, + viewModel.containerViewModel.editModeViewModel.isEditing + ) { + setCustomizerShowing(it) + } + } + } + } + } + + @Composable + private fun QuickQuickSettingsElement() { + val qqsPadding by viewModel.qqsHeaderHeight.collectAsStateWithLifecycle() + DisposableEffect(Unit) { + qqsVisible.value = true + + onDispose { qqsVisible.value = false } + } + Column(modifier = Modifier.sysuiResTag("quick_qs_panel")) { + QuickQuickSettings( + viewModel = viewModel.containerViewModel.quickQuickSettingsViewModel, + modifier = + Modifier.onGloballyPositioned { coordinates -> + val (leftFromRoot, topFromRoot) = coordinates.positionInRoot().round() + val (width, height) = coordinates.size + qqsPositionOnRoot.set( + leftFromRoot, + topFromRoot, + leftFromRoot + width, + topFromRoot + height + ) + } + .layout { measurable, constraints -> + val placeable = measurable.measure(constraints) + qqsHeight.value = placeable.height + + layout(placeable.width, placeable.height) { placeable.place(0, 0) } + } + .padding(top = { qqsPadding }) + ) + Spacer(modifier = Modifier.weight(1f)) + } + } + + @Composable + private fun QuickSettingsElement() { + val qqsPadding by viewModel.qqsHeaderHeight.collectAsStateWithLifecycle() + val qsExtraPadding = dimensionResource(R.dimen.qs_panel_padding_top) + Column { + Box(modifier = Modifier.fillMaxSize().weight(1f)) { + Column { + Spacer(modifier = Modifier.height { qqsPadding + qsExtraPadding.roundToPx() }) + ShadeBody(viewModel = viewModel.containerViewModel) + } + } + QuickSettingsTheme { + FooterActions( + viewModel = viewModel.footerActionsViewModel, + qsVisibilityLifecycleOwner = this@QSFragmentCompose, + modifier = Modifier.sysuiResTag("qs_footer_actions") + ) + } + } + } +} + +private fun View.setBackPressedDispatcher() { + repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.CREATED) { + setViewTreeOnBackPressedDispatcherOwner( + object : OnBackPressedDispatcherOwner { + override val onBackPressedDispatcher = + OnBackPressedDispatcher().apply { + setOnBackInvokedDispatcher(it.viewRootImpl.onBackInvokedDispatcher) + } + + override val lifecycle: Lifecycle = this@repeatWhenAttached.lifecycle + } + ) + } + } +} + +private suspend inline fun <Listener : Any, Data> setListenerJob( + listenerFlow: MutableStateFlow<Listener?>, + dataFlow: Flow<Data>, + crossinline onCollect: suspend Listener.(Data) -> Unit +) { + coroutineScope { + try { + listenerFlow.collectLatest { listenerOrNull -> + listenerOrNull?.let { currentListener -> + launch { + // Called when editing mode changes + dataFlow.collect { currentListener.onCollect(it) } + } + } + } + awaitCancellation() + } finally { + listenerFlow.value = null + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt new file mode 100644 index 000000000000..9e109e436226 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.composefragment.viewmodel + +import android.content.res.Resources +import android.graphics.Rect +import androidx.lifecycle.LifecycleCoroutineScope +import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.qs.FooterActionsController +import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel +import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel +import com.android.systemui.shade.LargeScreenHeaderHelper +import com.android.systemui.shade.transition.LargeScreenShadeInterpolator +import com.android.systemui.statusbar.SysuiStatusBarStateController +import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository +import com.android.systemui.statusbar.phone.KeyguardBypassController +import com.android.systemui.util.LargeScreenUtils +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch + +class QSFragmentComposeViewModel +@AssistedInject +constructor( + val containerViewModel: QuickSettingsContainerViewModel, + @Main private val resources: Resources, + private val footerActionsViewModelFactory: FooterActionsViewModel.Factory, + private val footerActionsController: FooterActionsController, + private val sysuiStatusBarStateController: SysuiStatusBarStateController, + private val keyguardBypassController: KeyguardBypassController, + private val disableFlagsRepository: DisableFlagsRepository, + private val largeScreenShadeInterpolator: LargeScreenShadeInterpolator, + private val configurationInteractor: ConfigurationInteractor, + private val largeScreenHeaderHelper: LargeScreenHeaderHelper, + @Assisted private val lifecycleScope: LifecycleCoroutineScope, +) { + val footerActionsViewModel = + footerActionsViewModelFactory.create(lifecycleScope).also { + lifecycleScope.launch { footerActionsController.init() } + } + + private val _qsBounds = MutableStateFlow(Rect()) + + private val _qsExpanded = MutableStateFlow(false) + var isQSExpanded: Boolean + get() = _qsExpanded.value + set(value) { + _qsExpanded.value = value + } + + private val _qsVisible = MutableStateFlow(false) + val qsVisible = _qsVisible.asStateFlow() + var isQSVisible: Boolean + get() = qsVisible.value + set(value) { + _qsVisible.value = value + } + + private val _qsExpansion = MutableStateFlow(0f) + var qsExpansionValue: Float + get() = _qsExpansion.value + set(value) { + _qsExpansion.value = value + } + + private val _panelFraction = MutableStateFlow(0f) + var panelExpansionFractionValue: Float + get() = _panelFraction.value + set(value) { + _panelFraction.value = value + } + + private val _squishinessFraction = MutableStateFlow(0f) + var squishinessFractionValue: Float + get() = _squishinessFraction.value + set(value) { + _squishinessFraction.value = value + } + + val qqsHeaderHeight = + configurationInteractor.onAnyConfigurationChange + .map { + if (LargeScreenUtils.shouldUseLargeScreenShadeHeader(resources)) { + 0 + } else { + largeScreenHeaderHelper.getLargeScreenHeaderHeight() + } + } + .stateIn(lifecycleScope, SharingStarted.WhileSubscribed(), 0) + + private val _headerAnimating = MutableStateFlow(false) + + private val _stackScrollerOverscrolling = MutableStateFlow(false) + var stackScrollerOverscrollingValue: Boolean + get() = _stackScrollerOverscrolling.value + set(value) { + _stackScrollerOverscrolling.value = value + } + + private val qsDisabled = + disableFlagsRepository.disableFlags + .map { !it.isQuickSettingsEnabled() } + .stateIn( + lifecycleScope, + SharingStarted.WhileSubscribed(), + !disableFlagsRepository.disableFlags.value.isQuickSettingsEnabled() + ) + + private val _showCollapsedOnKeyguard = MutableStateFlow(false) + + private val _keyguardAndExpanded = MutableStateFlow(false) + + private val _statusBarState = MutableStateFlow(-1) + + private val _viewHeight = MutableStateFlow(0) + + private val _headerTranslation = MutableStateFlow(0f) + + private val _inSplitShade = MutableStateFlow(false) + + private val _transitioningToFullShade = MutableStateFlow(false) + + private val _lockscreenToShadeProgress = MutableStateFlow(false) + + private val _overscrolling = MutableStateFlow(false) + + private val _isSmallScreen = MutableStateFlow(false) + var isSmallScreenValue: Boolean + get() = _isSmallScreen.value + set(value) { + _isSmallScreen.value = value + } + + private val _shouldUpdateMediaSquishiness = MutableStateFlow(false) + + private val _heightOverride = MutableStateFlow(-1) + val heightOverride = _heightOverride.asStateFlow() + var heightOverrideValue: Int + get() = heightOverride.value + set(value) { + _heightOverride.value = value + } + + val expansionState: StateFlow<QSExpansionState> = + combine( + _stackScrollerOverscrolling, + _qsExpanded, + _qsExpansion, + ) { args: Array<Any> -> + val expansion = args[2] as Float + if (expansion > 0.5f) { + QSExpansionState.QS + } else { + QSExpansionState.QQS + } + } + .stateIn(lifecycleScope, SharingStarted.WhileSubscribed(), QSExpansionState.QQS) + + @AssistedFactory + interface Factory { + fun create(lifecycleScope: LifecycleCoroutineScope): QSFragmentComposeViewModel + } + + sealed interface QSExpansionState { + data object QQS : QSExpansionState + + data object QS : QSExpansionState + + @JvmInline value class Expanding(val progress: Float) : QSExpansionState + + @JvmInline value class Collapsing(val progress: Float) : QSExpansionState + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt index ba45d172b082..6dc101a63f09 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt @@ -21,6 +21,7 @@ import android.util.Log import android.view.ContextThemeWrapper import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleCoroutineScope import androidx.lifecycle.LifecycleOwner import com.android.settingslib.Utils import com.android.systemui.animation.Expandable @@ -41,6 +42,7 @@ import javax.inject.Inject import javax.inject.Named import javax.inject.Provider import kotlin.math.max +import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -48,6 +50,8 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch private const val TAG = "FooterActionsViewModel" @@ -140,6 +144,30 @@ class FooterActionsViewModel( showPowerButton, ) } + + fun create(lifecycleCoroutineScope: LifecycleCoroutineScope): FooterActionsViewModel { + val globalActionsDialogLite = globalActionsDialogLiteProvider.get() + if (lifecycleCoroutineScope.isActive) { + lifecycleCoroutineScope.launch { + try { + awaitCancellation() + } finally { + globalActionsDialogLite.destroy() + } + } + } else { + globalActionsDialogLite.destroy() + } + + return FooterActionsViewModel( + context, + footerActionsInteractor, + falsingManager, + globalActionsDialogLite, + activityStarter, + showPowerButton, + ) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt index 2ee957e89f48..08a56bf29f66 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt @@ -39,6 +39,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.qs.panels.dagger.PaginatedBaseLayoutType import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout.Dimensions.FooterHeight import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout.Dimensions.InterPageSpacing @@ -77,7 +78,7 @@ constructor( Column { HorizontalPager( state = pagerState, - modifier = Modifier, + modifier = Modifier.sysuiResTag("qs_pager"), pageSpacing = if (pages.size > 1) InterPageSpacing else 0.dp, beyondViewportPageCount = 1, verticalAlignment = Alignment.Top, diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt index af3803b6ff34..a9027ff92996 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt @@ -25,6 +25,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel import com.android.systemui.res.R @@ -44,7 +45,10 @@ fun QuickQuickSettings( } val columns by viewModel.columns.collectAsStateWithLifecycle() - TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) { + TileLazyGrid( + modifier = modifier.sysuiResTag("qqs_tile_layout"), + columns = GridCells.Fixed(columns) + ) { items( tiles.size, key = { index -> sizedTiles[index].tile.spec.spec }, diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt index 7e6ccd635a96..9c0701e974ec 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt @@ -22,7 +22,6 @@ import android.graphics.drawable.Animatable import android.service.quicksettings.Tile.STATE_ACTIVE import android.service.quicksettings.Tile.STATE_INACTIVE import android.text.TextUtils -import androidx.appcompat.content.res.AppCompatResources import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn @@ -593,15 +592,15 @@ enum class ClickAction { } @Composable -private fun getTileIcon(icon: Supplier<QSTile.Icon>): Icon { +private fun getTileIcon(icon: Supplier<QSTile.Icon?>): Icon { val context = LocalContext.current - return icon.get().let { + return icon.get()?.let { if (it is QSTileImpl.ResourceIcon) { Icon.Resource(it.resId, null) } else { Icon.Loaded(it.getDrawable(context), null) } - } + } ?: Icon.Resource(R.drawable.ic_error_outline, null) } @OptIn(ExperimentalAnimationGraphicsApi::class) @@ -618,7 +617,7 @@ private fun TileIcon( remember(icon, context) { when (icon) { is Icon.Loaded -> icon.drawable - is Icon.Resource -> AppCompatResources.getDrawable(context, icon.res) + is Icon.Resource -> context.getDrawable(icon.res) } } if (loadedDrawable !is Animatable) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt index 4ec59c969a59..c83e3b2a0e06 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt @@ -25,7 +25,7 @@ data class TileUiState( val label: String, val secondaryLabel: String, val state: Int, - val icon: Supplier<QSTile.Icon>, + val icon: Supplier<QSTile.Icon?>, ) fun QSTile.State.toUiState(): TileUiState { @@ -33,6 +33,6 @@ fun QSTile.State.toUiState(): TileUiState { label?.toString() ?: "", secondaryLabel?.toString() ?: "", state, - icon?.let { Supplier { icon } } ?: iconSupplier, + icon?.let { Supplier { icon } } ?: iconSupplier ?: Supplier { null }, ) } diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt index f6924f222e11..8aa601f3ecf0 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt @@ -26,13 +26,12 @@ class SceneWindowRootView( attrs, ) { - private lateinit var viewModel: SceneContainerViewModel - + private var motionEventHandler: SceneContainerViewModel.MotionEventHandler? = null // TODO(b/298525212): remove once Compose exposes window inset bounds. private val windowInsets: MutableStateFlow<WindowInsets?> = MutableStateFlow(null) fun init( - viewModel: SceneContainerViewModel, + viewModelFactory: SceneContainerViewModel.Factory, containerConfig: SceneContainerConfig, sharedNotificationContainer: SharedNotificationContainer, scenes: Set<Scene>, @@ -40,11 +39,13 @@ class SceneWindowRootView( sceneDataSourceDelegator: SceneDataSourceDelegator, alternateBouncerDependencies: AlternateBouncerDependencies, ) { - this.viewModel = viewModel setLayoutInsetsController(layoutInsetController) SceneWindowRootViewBinder.bind( view = this@SceneWindowRootView, - viewModel = viewModel, + viewModelFactory = viewModelFactory, + motionEventHandlerReceiver = { motionEventHandler -> + this.motionEventHandler = motionEventHandler + }, windowInsets = windowInsets, containerConfig = containerConfig, sharedNotificationContainer = sharedNotificationContainer, @@ -69,10 +70,10 @@ class SceneWindowRootView( } override fun dispatchTouchEvent(ev: MotionEvent): Boolean { - viewModel.onMotionEvent(ev) + motionEventHandler?.onMotionEvent(ev) return super.dispatchTouchEvent(ev).also { TouchLogger.logDispatchTouch(TAG, ev, it) - viewModel.onMotionEventComplete() + motionEventHandler?.onMotionEventComplete() } } 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 73a8e4c24578..ad68f17720c5 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 @@ -29,8 +29,6 @@ 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.compose.animation.scene.SceneKey import com.android.compose.theme.PlatformTheme import com.android.internal.policy.ScreenDecorationsUtils @@ -39,7 +37,9 @@ import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout import com.android.systemui.common.ui.compose.windowinsets.ScreenDecorProvider import com.android.systemui.keyguard.ui.composable.AlternateBouncer import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies +import com.android.systemui.lifecycle.WindowLifecycleState import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.lifecycle.viewModel import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scene @@ -51,6 +51,7 @@ import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map @@ -63,7 +64,8 @@ object SceneWindowRootViewBinder { /** Binds between the view and view-model pertaining to a specific scene container. */ fun bind( view: ViewGroup, - viewModel: SceneContainerViewModel, + viewModelFactory: SceneContainerViewModel.Factory, + motionEventHandlerReceiver: (SceneContainerViewModel.MotionEventHandler?) -> Unit, windowInsets: StateFlow<WindowInsets?>, containerConfig: SceneContainerConfig, sharedNotificationContainer: SharedNotificationContainer, @@ -85,8 +87,11 @@ object SceneWindowRootViewBinder { } view.repeatWhenAttached { - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.CREATED) { + view.viewModel( + minWindowLifecycleState = WindowLifecycleState.ATTACHED, + factory = { viewModelFactory.create(motionEventHandlerReceiver) }, + ) { viewModel -> + try { view.setViewTreeOnBackPressedDispatcherOwner( object : OnBackPressedDispatcherOwner { override val onBackPressedDispatcher = @@ -140,10 +145,11 @@ object SceneWindowRootViewBinder { onVisibilityChangedInternal(isVisible) } } + awaitCancellation() + } finally { + // Here when destroyed. + view.removeAllViews() } - - // Here when destroyed. - view.removeAllViews() } } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt index a28222e9cea0..2d02f5a5b79d 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt @@ -23,25 +23,26 @@ import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.systemui.classifier.Classifier import com.android.systemui.classifier.domain.interactor.FalsingInteractor -import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.lifecycle.SysUiViewModel import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.Scenes -import javax.inject.Inject +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.map /** Models UI state for the scene container. */ -@SysUISingleton class SceneContainerViewModel -@Inject +@AssistedInject constructor( private val sceneInteractor: SceneInteractor, private val falsingInteractor: FalsingInteractor, private val powerInteractor: PowerInteractor, -) { + @Assisted private val motionEventHandlerReceiver: (MotionEventHandler?) -> Unit, +) : SysUiViewModel() { /** * Keys of all scenes in the container. * @@ -56,6 +57,29 @@ constructor( /** Whether the container is visible. */ val isVisible: StateFlow<Boolean> = sceneInteractor.isVisible + override suspend fun onActivated() { + try { + // Sends a MotionEventHandler to the owner of the view-model so they can report + // MotionEvents into the view-model. + motionEventHandlerReceiver( + object : MotionEventHandler { + override fun onMotionEvent(motionEvent: MotionEvent) { + this@SceneContainerViewModel.onMotionEvent(motionEvent) + } + + override fun onMotionEventComplete() { + this@SceneContainerViewModel.onMotionEventComplete() + } + } + ) + awaitCancellation() + } finally { + // Clears the previously-sent MotionEventHandler so the owner of the view-model releases + // their reference to it. + motionEventHandlerReceiver(null) + } + } + /** * Binds the given flow so the system remembers it. * @@ -136,21 +160,22 @@ constructor( } } - private fun replaceSceneFamilies( - destinationScenes: Map<UserAction, UserActionResult>, - ): Flow<Map<UserAction, UserActionResult>> { - return destinationScenes - .mapValues { (_, actionResult) -> - sceneInteractor.resolveSceneFamily(actionResult.toScene).map { scene -> - actionResult.copy(toScene = scene) - } - } - .combineValueFlows() + /** Defines interface for classes that can handle externally-reported [MotionEvent]s. */ + interface MotionEventHandler { + /** Notifies that a [MotionEvent] has occurred. */ + fun onMotionEvent(motionEvent: MotionEvent) + + /** + * Notifies that the previous [MotionEvent] reported by [onMotionEvent] has finished + * processing. + */ + fun onMotionEventComplete() } -} -private fun <K, V> Map<K, Flow<V>>.combineValueFlows(): Flow<Map<K, V>> = - combine( - asIterable().map { (k, fv) -> fv.map { k to it } }, - transform = Array<Pair<K, V>>::toMap, - ) + @AssistedFactory + interface Factory { + fun create( + motionEventHandlerReceiver: (MotionEventHandler?) -> Unit, + ): SceneContainerViewModel + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt index 21bbaa5a41f2..606fef0bff62 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt @@ -79,7 +79,7 @@ abstract class ShadeViewProviderModule { @SysUISingleton fun providesWindowRootView( layoutInflater: LayoutInflater, - viewModelProvider: Provider<SceneContainerViewModel>, + viewModelFactory: SceneContainerViewModel.Factory, containerConfigProvider: Provider<SceneContainerConfig>, scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>, layoutInsetController: NotificationInsetsController, @@ -91,7 +91,7 @@ abstract class ShadeViewProviderModule { val sceneWindowRootView = layoutInflater.inflate(R.layout.scene_window_root, null) as SceneWindowRootView sceneWindowRootView.init( - viewModel = viewModelProvider.get(), + viewModelFactory = viewModelFactory, containerConfig = containerConfigProvider.get(), sharedNotificationContainer = sceneWindowRootView.requireViewById(R.id.shared_notification_container), diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt index 2b7df7dc937d..67c53d46b4d0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt @@ -142,14 +142,15 @@ class NotificationTransitionAnimatorController( } override fun onIntentStarted(willAnimate: Boolean) { + val reason = "onIntentStarted(willAnimate=$willAnimate)" if (ActivityTransitionAnimator.DEBUG_TRANSITION_ANIMATION) { - Log.d(TAG, "onIntentStarted(willAnimate=$willAnimate)") + Log.d(TAG, reason) } notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(willAnimate) notificationEntry.isExpandAnimationRunning = willAnimate if (!willAnimate) { - removeHun(animate = true) + removeHun(animate = true, reason) onFinishAnimationCallback?.run() } } @@ -166,13 +167,18 @@ class NotificationTransitionAnimatorController( } } - private fun removeHun(animate: Boolean) { + private fun removeHun(animate: Boolean, reason: String) { val row = headsUpNotificationRow ?: return // TODO: b/297247841 - Call on the row we're removing, which may differ from notification. HeadsUpUtil.setNeedsHeadsUpDisappearAnimationAfterClick(notification, animate) - headsUpManager.removeNotification(row.entry.key, true /* releaseImmediately */, animate) + headsUpManager.removeNotification( + row.entry.key, + true /* releaseImmediately */, + animate, + reason + ) } override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) { @@ -184,7 +190,7 @@ class NotificationTransitionAnimatorController( // here? notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(false) notificationEntry.isExpandAnimationRunning = false - removeHun(animate = true) + removeHun(animate = true, "onLaunchAnimationCancelled()") onFinishAnimationCallback?.run() } @@ -206,7 +212,7 @@ class NotificationTransitionAnimatorController( notificationEntry.isExpandAnimationRunning = false notificationListContainer.setExpandingNotification(null) applyParams(null) - removeHun(animate = false) + removeHun(animate = false, "onLaunchAnimationEnd()") onFinishAnimationCallback?.run() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt index e50d64bcb8f9..ec8566b82aea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt @@ -496,7 +496,11 @@ constructor( if (posted?.shouldHeadsUpEver == false) { if (posted.isHeadsUpEntry) { // We don't want this to be interrupting anymore, let's remove it - mHeadsUpManager.removeNotification(posted.key, false /*removeImmediately*/) + mHeadsUpManager.removeNotification( + posted.key, + /* removeImmediately= */ false, + "onEntryUpdated" + ) } else if (posted.isBinding) { // Don't let the bind finish cancelHeadsUpBind(posted.entry) @@ -520,7 +524,11 @@ constructor( val removeImmediatelyForRemoteInput = (mRemoteInputManager.isSpinning(entryKey) && !NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY) - mHeadsUpManager.removeNotification(entry.key, removeImmediatelyForRemoteInput) + mHeadsUpManager.removeNotification( + entry.key, + removeImmediatelyForRemoteInput, + "onEntryRemoved, reason: $reason" + ) } } @@ -721,7 +729,9 @@ constructor( { mHeadsUpManager.removeNotification( entry.key, /* releaseImmediately */ - true + true, + "cancel lifetime extension - extended for reason: " + + "$reason, isSticky: true" ) }, removeAfterMillis @@ -730,7 +740,9 @@ constructor( mExecutor.execute { mHeadsUpManager.removeNotification( entry.key, /* releaseImmediately */ - false + false, + "lifetime extension - extended for reason: $reason" + + ", isSticky: false" ) } mNotifsExtendingLifetime[entry] = null @@ -902,7 +914,7 @@ private class HunMutatorImpl(private val headsUpManager: HeadsUpManager) : HunMu fun commitModifications() { deferred.forEach { (key, releaseImmediately) -> - headsUpManager.removeNotification(key, releaseImmediately) + headsUpManager.removeNotification(key, releaseImmediately, "commitModifications") } deferred.clear() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 41195aa0f72e..fa12bb99eac2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -638,8 +638,11 @@ public class NotificationStackScrollLayoutController implements Dumpable { if (row.isPinned() && !canChildBeDismissed(row) && row.getEntry().getSbn().getNotification().fullScreenIntent == null) { - mHeadsUpManager.removeNotification(row.getEntry().getSbn().getKey(), - true /* removeImmediately */); + mHeadsUpManager.removeNotification( + row.getEntry().getSbn().getKey(), + /* removeImmediately= */ true , + /* reason= */ "onChildSnappedBack" + ); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt index 107bf1edb74f..d4ef42c687e5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt @@ -613,8 +613,9 @@ constructor( super.onTransitionAnimationStart(isExpandingFullyAbove) if (Flags.communalHub()) { communalSceneInteractor.snapToScene( - CommunalScenes.Blank, - ActivityTransitionAnimator.TIMINGS.totalDuration + newScene = CommunalScenes.Blank, + loggingReason = "ActivityStarterInternalImpl", + delayMillis = ActivityTransitionAnimator.TIMINGS.totalDuration ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index c4fbc37b2dd5..94dd9bbefaa3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -160,6 +160,8 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.qs.QSFragmentLegacy; import com.android.systemui.qs.QSPanelController; +import com.android.systemui.qs.composefragment.QSFragmentCompose; +import com.android.systemui.qs.flags.QSComposeFragment; import com.android.systemui.res.R; import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; import com.android.systemui.scene.shared.flag.SceneContainerFlag; @@ -1432,9 +1434,15 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } protected QS createDefaultQSFragment() { + Class<? extends QS> klass; + if (QSComposeFragment.isEnabled()) { + klass = QSFragmentCompose.class; + } else { + klass = QSFragmentLegacy.class; + } return mFragmentService .getFragmentHostManager(getNotificationShadeWindowView()) - .create(QSFragmentLegacy.class); + .create(klass); } private void setUpPresenter() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java index ac1015521502..ec92990441ce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java @@ -193,7 +193,11 @@ public final class DozeServiceHost implements DozeHost { void fireNotificationPulse(NotificationEntry entry) { Runnable pulseSuppressedListener = () -> { mHeadsUpManager.removeNotification( - entry.getKey(), /* releaseImmediately= */ true, /* animate= */ false); + entry.getKey(), + /* releaseImmediately= */ true, + /* animate= */ false, + "fireNotificationPulse" + ); }; Assert.isMainThread(); for (Callback callback : mCallbacks) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index 25d9cc76fe3d..544a8a5e5c67 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -60,11 +60,6 @@ import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.settings.GlobalSettings; import com.android.systemui.util.time.SystemClock; -import kotlinx.coroutines.flow.Flow; -import kotlinx.coroutines.flow.MutableStateFlow; -import kotlinx.coroutines.flow.StateFlow; -import kotlinx.coroutines.flow.StateFlowKt; - import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashSet; @@ -75,6 +70,11 @@ import java.util.Stack; import javax.inject.Inject; +import kotlinx.coroutines.flow.Flow; +import kotlinx.coroutines.flow.MutableStateFlow; +import kotlinx.coroutines.flow.StateFlow; +import kotlinx.coroutines.flow.StateFlowKt; + /** A implementation of HeadsUpManager for phone. */ @SysUISingleton public class HeadsUpManagerPhone extends BaseHeadsUpManager implements @@ -365,12 +365,14 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements @Override public boolean removeNotification(@NonNull String key, boolean releaseImmediately, - boolean animate) { + boolean animate, @NonNull String reason) { if (animate) { - return removeNotification(key, releaseImmediately); + return removeNotification(key, releaseImmediately, + "removeNotification(animate: true), reason: " + reason); } else { mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false); - boolean removed = removeNotification(key, releaseImmediately); + final boolean removed = removeNotification(key, releaseImmediately, + "removeNotification(animate: false), reason: " + reason); mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(true); return removed; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index 04604e0bb3f9..dda02db3f2ef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -32,6 +32,8 @@ import android.view.accessibility.AccessibilityEvent; import android.widget.FrameLayout; import android.widget.LinearLayout; +import androidx.annotation.NonNull; + import com.android.internal.policy.SystemBarUtils; import com.android.systemui.Dependency; import com.android.systemui.Flags; @@ -47,7 +49,6 @@ import java.util.Objects; public class PhoneStatusBarView extends FrameLayout { private static final String TAG = "PhoneStatusBarView"; - private final StatusBarContentInsetsProvider mContentInsetsProvider; private final StatusBarWindowController mStatusBarWindowController; private int mRotationOrientation = -1; @@ -60,6 +61,10 @@ public class PhoneStatusBarView extends FrameLayout { private int mStatusBarHeight; @Nullable private Gefingerpoken mTouchEventHandler; + @Nullable + private HasCornerCutoutFetcher mHasCornerCutoutFetcher; + @Nullable + private InsetsFetcher mInsetsFetcher; private int mDensity; private float mFontScale; @@ -70,7 +75,6 @@ public class PhoneStatusBarView extends FrameLayout { public PhoneStatusBarView(Context context, AttributeSet attrs) { super(context, attrs); - mContentInsetsProvider = Dependency.get(StatusBarContentInsetsProvider.class); mStatusBarWindowController = Dependency.get(StatusBarWindowController.class); } @@ -78,6 +82,14 @@ public class PhoneStatusBarView extends FrameLayout { mTouchEventHandler = handler; } + void setHasCornerCutoutFetcher(@NonNull HasCornerCutoutFetcher cornerCutoutFetcher) { + mHasCornerCutoutFetcher = cornerCutoutFetcher; + } + + void setInsetsFetcher(@NonNull InsetsFetcher insetsFetcher) { + mInsetsFetcher = insetsFetcher; + } + void init(StatusBarUserChipViewModel viewModel) { StatusBarUserSwitcherContainer container = findViewById(R.id.user_switcher_container); StatusBarUserChipViewBinder.bind(container, viewModel); @@ -270,7 +282,14 @@ public class PhoneStatusBarView extends FrameLayout { return; } - boolean hasCornerCutout = mContentInsetsProvider.currentRotationHasCornerCutout(); + boolean hasCornerCutout; + if (mHasCornerCutoutFetcher != null) { + hasCornerCutout = mHasCornerCutoutFetcher.fetchHasCornerCutout(); + } else { + Log.e(TAG, "mHasCornerCutoutFetcher unexpectedly null"); + hasCornerCutout = true; + } + if (mDisplayCutout == null || mDisplayCutout.isEmpty() || hasCornerCutout) { mCutoutSpace.setVisibility(View.GONE); return; @@ -288,8 +307,12 @@ public class PhoneStatusBarView extends FrameLayout { } private void updateSafeInsets() { - Insets insets = mContentInsetsProvider - .getStatusBarContentInsetsForCurrentRotation(); + if (mInsetsFetcher == null) { + Log.e(TAG, "mInsetsFetcher unexpectedly null"); + return; + } + + Insets insets = mInsetsFetcher.fetchInsets(); setPadding( insets.left, insets.top, @@ -298,6 +321,17 @@ public class PhoneStatusBarView extends FrameLayout { } private void updateWindowHeight() { + if (Flags.statusBarStopUpdatingWindowHeight()) { + return; + } mStatusBarWindowController.refreshStatusBarHeight(); } + + interface HasCornerCutoutFetcher { + boolean fetchHasCornerCutout(); + } + + interface InsetsFetcher { + Insets fetchInsets(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt index 468a3c3a49a5..456265b27004 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt @@ -73,6 +73,7 @@ private constructor( private val configurationController: ConfigurationController, private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory, private val darkIconDispatcher: DarkIconDispatcher, + private val statusBarContentInsetsProvider: StatusBarContentInsetsProvider, ) : ViewController<PhoneStatusBarView>(view) { private lateinit var battery: BatteryMeterView @@ -155,7 +156,14 @@ private constructor( } init { + // These should likely be done in `onInit`, not `init`. mView.setTouchEventHandler(PhoneStatusBarViewTouchHandler()) + mView.setHasCornerCutoutFetcher { + statusBarContentInsetsProvider.currentRotationHasCornerCutout() + } + mView.setInsetsFetcher { + statusBarContentInsetsProvider.getStatusBarContentInsetsForCurrentRotation() + } mView.init(userChipViewModel) } @@ -310,6 +318,7 @@ private constructor( private val configurationController: ConfigurationController, private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory, private val darkIconDispatcher: DarkIconDispatcher, + private val statusBarContentInsetsProvider: StatusBarContentInsetsProvider, ) { fun create(view: PhoneStatusBarView): PhoneStatusBarViewController { val statusBarMoveFromCenterAnimationController = @@ -335,6 +344,7 @@ private constructor( configurationController, statusOverlayHoverListenerFactory, darkIconDispatcher, + statusBarContentInsetsProvider, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 5486abba9987..de4d14d31da3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -1007,7 +1007,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mKeyguardMessageAreaController.setIsVisible(isShowingAlternateBouncer); mKeyguardMessageAreaController.setMessage(""); } - mKeyguardUpdateManager.setAlternateBouncerShowing(isShowingAlternateBouncer); + if (!SceneContainerFlag.isEnabled()) { + mKeyguardUpdateManager.setAlternateBouncerShowing(isShowingAlternateBouncer); + } if (updateScrim) { mCentralSurfaces.updateScrimController(); @@ -1449,10 +1451,13 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mNotificationShadeWindowController.setBouncerShowing(primaryBouncerShowing); mCentralSurfaces.setBouncerShowing(primaryBouncerShowing); } - if (primaryBouncerIsOrWillBeShowing != mLastPrimaryBouncerIsOrWillBeShowing || mFirstUpdate - || isPrimaryBouncerShowingChanged) { - mKeyguardUpdateManager.sendPrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing, - primaryBouncerShowing); + if (!SceneContainerFlag.isEnabled()) { + if (primaryBouncerIsOrWillBeShowing != mLastPrimaryBouncerIsOrWillBeShowing + || mFirstUpdate + || isPrimaryBouncerShowingChanged) { + mKeyguardUpdateManager.sendPrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing, + primaryBouncerShowing); + } } mFirstUpdate = false; 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 e92058bf034a..0a6e7f59e24e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -230,7 +230,8 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit Runnable action = () -> { mBubblesManagerOptional.ifPresent(bubblesManager -> bubblesManager.onUserChangedBubble(entry, !entry.isBubble())); - mHeadsUpManager.removeNotification(entry.getKey(), /* releaseImmediately= */ true); + mHeadsUpManager.removeNotification(entry.getKey(), /* releaseImmediately= */ true, + /* reason= */ "onNotificationBubbleIconClicked"); }; if (entry.isBubble()) { // entry is being un-bubbled, no need to unlock @@ -621,7 +622,8 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit // In most cases, when FLAG_AUTO_CANCEL is set, the notification will // become canceled shortly by NoMan, but we can't assume that. - mHeadsUpManager.removeNotification(key, true /* releaseImmediately */); + mHeadsUpManager.removeNotification(key, /* releaseImmediately= */ true, + "removeHunAfterClick"); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java index 37869587528e..f37393ac6729 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java @@ -40,13 +40,14 @@ import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun; -import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor; import com.android.systemui.statusbar.phone.ExpandHeadsUpOnInlineReply; import com.android.systemui.util.ListenerSet; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.settings.GlobalSettings; import com.android.systemui.util.time.SystemClock; +import org.jetbrains.annotations.NotNull; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -191,12 +192,14 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { * enough and needs to be kept around. * @param key the key of the notification to remove * @param releaseImmediately force a remove regardless of earliest removal time + * @param reason reason for removing the notification * @return true if notification is removed, false otherwise */ @Override - public boolean removeNotification(@NonNull String key, boolean releaseImmediately) { + public boolean removeNotification(@NotNull String key, boolean releaseImmediately, + @NonNull String reason) { final boolean isWaiting = mAvalancheController.isWaiting(key); - mLogger.logRemoveNotification(key, releaseImmediately, isWaiting); + mLogger.logRemoveNotification(key, releaseImmediately, isWaiting, reason); if (mAvalancheController.isWaiting(key)) { removeEntry(key, "removeNotification (isWaiting)"); @@ -204,6 +207,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { } HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key); if (headsUpEntry == null) { + mLogger.logNullEntry(key, reason); return true; } if (releaseImmediately) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt index fcf77d5526d4..04fe6b3e9eb7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt @@ -96,9 +96,10 @@ interface HeadsUpManager : Dumpable { * * @param key the key of the notification to remove * @param releaseImmediately force a remove regardless of earliest removal time + * @param reason reason for removing the notification * @return true if notification is removed, false otherwise */ - fun removeNotification(key: String, releaseImmediately: Boolean): Boolean + fun removeNotification(key: String, releaseImmediately: Boolean, reason: String): Boolean /** * Try to remove the notification. May not succeed if the notification has not been shown long @@ -107,9 +108,15 @@ interface HeadsUpManager : Dumpable { * @param key the key of the notification to remove * @param releaseImmediately force a remove regardless of earliest removal time * @param animate if true, animate the removal + * @param reason reason for removing the notification * @return true if notification is removed, false otherwise */ - fun removeNotification(key: String, releaseImmediately: Boolean, animate: Boolean): Boolean + fun removeNotification( + key: String, + releaseImmediately: Boolean, + animate: Boolean, + reason: String + ): Boolean /** Clears all managed notifications. */ fun releaseAllImmediately() @@ -246,11 +253,16 @@ class HeadsUpManagerEmptyImpl @Inject constructor() : HeadsUpManager { override fun removeListener(listener: OnHeadsUpChangedListener) {} - override fun removeNotification(key: String, releaseImmediately: Boolean) = false - - override fun removeNotification(key: String, releaseImmediately: Boolean, animate: Boolean) = + override fun removeNotification(key: String, releaseImmediately: Boolean, reason: String) = false + override fun removeNotification( + key: String, + releaseImmediately: Boolean, + animate: Boolean, + reason: String + ) = false + override fun setAnimationStateHandler(handler: AnimationStateHandler) {} override fun setExpanded(entry: NotificationEntry, expanded: Boolean) {} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt index 80c595fb638b..c6fc54748cbd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt @@ -16,244 +16,283 @@ package com.android.systemui.statusbar.policy -import com.android.systemui.log.dagger.NotificationHeadsUpLog import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel.INFO import com.android.systemui.log.core.LogLevel.VERBOSE +import com.android.systemui.log.dagger.NotificationHeadsUpLog import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject /** Logger for [HeadsUpManager]. */ -class HeadsUpManagerLogger @Inject constructor( - @NotificationHeadsUpLog private val buffer: LogBuffer -) { +class HeadsUpManagerLogger +@Inject +constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) { fun logPackageSnoozed(snoozeKey: String) { - buffer.log(TAG, INFO, { - str1 = snoozeKey - }, { - "package snoozed $str1" - }) + buffer.log(TAG, INFO, { str1 = snoozeKey }, { "package snoozed $str1" }) } fun logPackageUnsnoozed(snoozeKey: String) { - buffer.log(TAG, INFO, { - str1 = snoozeKey - }, { - "package unsnoozed $str1" - }) + buffer.log(TAG, INFO, { str1 = snoozeKey }, { "package unsnoozed $str1" }) } fun logIsSnoozedReturned(snoozeKey: String) { - buffer.log(TAG, INFO, { - str1 = snoozeKey - }, { - "package snoozed when queried $str1" - }) + buffer.log(TAG, INFO, { str1 = snoozeKey }, { "package snoozed when queried $str1" }) } fun logReleaseAllImmediately() { - buffer.log(TAG, INFO, { }, { - "release all immediately" - }) + buffer.log(TAG, INFO, {}, { "release all immediately" }) } fun logShowNotificationRequest(entry: NotificationEntry) { - buffer.log(TAG, INFO, { - str1 = entry.logKey - }, { - "request: show notification $str1" - }) + buffer.log(TAG, INFO, { str1 = entry.logKey }, { "request: show notification $str1" }) } - fun logAvalancheUpdate(caller: String, isEnabled: Boolean, notifEntryKey: String, - outcome: String) { - buffer.log(TAG, INFO, { - str1 = caller - str2 = notifEntryKey - str3 = outcome - bool1 = isEnabled - }, { - "$str1\n\t=> AC[isEnabled:$bool1] update: $str2\n\t=> $str3" - }) + fun logAvalancheUpdate( + caller: String, + isEnabled: Boolean, + notifEntryKey: String, + outcome: String + ) { + buffer.log( + TAG, + INFO, + { + str1 = caller + str2 = notifEntryKey + str3 = outcome + bool1 = isEnabled + }, + { "$str1\n\t=> AC[isEnabled:$bool1] update: $str2\n\t=> $str3" } + ) } - fun logAvalancheDelete(caller: String, isEnabled: Boolean, notifEntryKey: String, - outcome: String) { - buffer.log(TAG, INFO, { - str1 = caller - str2 = notifEntryKey - str3 = outcome - bool1 = isEnabled - }, { - "$str1\n\t=> AC[isEnabled:$bool1] delete: $str2\n\t=> $str3" - }) + fun logAvalancheDelete( + caller: String, + isEnabled: Boolean, + notifEntryKey: String, + outcome: String + ) { + buffer.log( + TAG, + INFO, + { + str1 = caller + str2 = notifEntryKey + str3 = outcome + bool1 = isEnabled + }, + { "$str1\n\t=> AC[isEnabled:$bool1] delete: $str2\n\t=> $str3" } + ) } fun logShowNotification(entry: NotificationEntry) { - buffer.log(TAG, INFO, { - str1 = entry.logKey - }, { - "show notification $str1" - }) + buffer.log(TAG, INFO, { str1 = entry.logKey }, { "show notification $str1" }) } fun logAutoRemoveScheduled(entry: NotificationEntry, delayMillis: Long, reason: String) { - buffer.log(TAG, INFO, { - str1 = entry.logKey - long1 = delayMillis - str2 = reason - }, { - "schedule auto remove of $str1 in $long1 ms reason: $str2" - }) + buffer.log( + TAG, + INFO, + { + str1 = entry.logKey + long1 = delayMillis + str2 = reason + }, + { "schedule auto remove of $str1 in $long1 ms reason: $str2" } + ) } fun logAutoRemoveRequest(entry: NotificationEntry, reason: String) { - buffer.log(TAG, INFO, { - str1 = entry.logKey - str2 = reason - }, { - "request: reschedule auto remove of $str1 reason: $str2" - }) + buffer.log( + TAG, + INFO, + { + str1 = entry.logKey + str2 = reason + }, + { "request: reschedule auto remove of $str1 reason: $str2" } + ) } fun logAutoRemoveRescheduled(entry: NotificationEntry, delayMillis: Long, reason: String) { - buffer.log(TAG, INFO, { - str1 = entry.logKey - long1 = delayMillis - str2 = reason - }, { - "reschedule auto remove of $str1 in $long1 ms reason: $str2" - }) + buffer.log( + TAG, + INFO, + { + str1 = entry.logKey + long1 = delayMillis + str2 = reason + }, + { "reschedule auto remove of $str1 in $long1 ms reason: $str2" } + ) } fun logAutoRemoveCancelRequest(entry: NotificationEntry, reason: String?) { - buffer.log(TAG, INFO, { - str1 = entry.logKey - str2 = reason ?: "unknown" - }, { - "request: cancel auto remove of $str1 reason: $str2" - }) + buffer.log( + TAG, + INFO, + { + str1 = entry.logKey + str2 = reason ?: "unknown" + }, + { "request: cancel auto remove of $str1 reason: $str2" } + ) } fun logAutoRemoveCanceled(entry: NotificationEntry, reason: String?) { - buffer.log(TAG, INFO, { - str1 = entry.logKey - str2 = reason ?: "unknown" - }, { - "cancel auto remove of $str1 reason: $str2" - }) + buffer.log( + TAG, + INFO, + { + str1 = entry.logKey + str2 = reason ?: "unknown" + }, + { "cancel auto remove of $str1 reason: $str2" } + ) } fun logRemoveEntryRequest(key: String, reason: String, isWaiting: Boolean) { - buffer.log(TAG, INFO, { - str1 = logKey(key) - str2 = reason - bool1 = isWaiting - }, { - "request: $str2 => remove entry $str1 isWaiting: $isWaiting" - }) + buffer.log( + TAG, + INFO, + { + str1 = logKey(key) + str2 = reason + bool1 = isWaiting + }, + { "request: $str2 => remove entry $str1 isWaiting: $isWaiting" } + ) } fun logRemoveEntry(key: String, reason: String, isWaiting: Boolean) { - buffer.log(TAG, INFO, { - str1 = logKey(key) - str2 = reason - bool1 = isWaiting - }, { - "$str2 => remove entry $str1 isWaiting: $isWaiting" - }) + buffer.log( + TAG, + INFO, + { + str1 = logKey(key) + str2 = reason + bool1 = isWaiting + }, + { "$str2 => remove entry $str1 isWaiting: $isWaiting" } + ) } fun logUnpinEntryRequest(key: String) { - buffer.log(TAG, INFO, { - str1 = logKey(key) - }, { - "request: unpin entry $str1" - }) + buffer.log(TAG, INFO, { str1 = logKey(key) }, { "request: unpin entry $str1" }) } fun logUnpinEntry(key: String) { - buffer.log(TAG, INFO, { - str1 = logKey(key) - }, { - "unpin entry $str1" - }) + buffer.log(TAG, INFO, { str1 = logKey(key) }, { "unpin entry $str1" }) + } + + fun logRemoveNotification( + key: String, + releaseImmediately: Boolean, + isWaiting: Boolean, + reason: String + ) { + buffer.log( + TAG, + INFO, + { + str1 = logKey(key) + bool1 = releaseImmediately + bool2 = isWaiting + str2 = reason + }, + { + "remove notification $str1 releaseImmediately: $bool1 isWaiting: $bool2 " + + "reason: $str2" + } + ) } - fun logRemoveNotification(key: String, releaseImmediately: Boolean, isWaiting: Boolean) { - buffer.log(TAG, INFO, { - str1 = logKey(key) - bool1 = releaseImmediately - bool2 = isWaiting - }, { - "remove notification $str1 releaseImmediately: $bool1 isWaiting: $bool2" - }) + fun logNullEntry(key: String, reason: String) { + buffer.log( + TAG, + INFO, + { + str1 = logKey(key) + str2 = reason + }, + { "remove notification $str1 when headsUpEntry is null, reason: $str2" } + ) } fun logNotificationActuallyRemoved(entry: NotificationEntry) { - buffer.log(TAG, INFO, { - str1 = entry.logKey - }, { - "notification removed $str1 " - }) + buffer.log(TAG, INFO, { str1 = entry.logKey }, { "notification removed $str1 " }) } fun logUpdateNotificationRequest(key: String, alert: Boolean, hasEntry: Boolean) { - buffer.log(TAG, INFO, { - str1 = logKey(key) - bool1 = alert - bool2 = hasEntry - }, { - "request: update notification $str1 alert: $bool1 hasEntry: $bool2" - }) + buffer.log( + TAG, + INFO, + { + str1 = logKey(key) + bool1 = alert + bool2 = hasEntry + }, + { "request: update notification $str1 alert: $bool1 hasEntry: $bool2" } + ) } fun logUpdateNotification(key: String, alert: Boolean, hasEntry: Boolean) { - buffer.log(TAG, INFO, { - str1 = logKey(key) - bool1 = alert - bool2 = hasEntry - }, { - "update notification $str1 alert: $bool1 hasEntry: $bool2" - }) + buffer.log( + TAG, + INFO, + { + str1 = logKey(key) + bool1 = alert + bool2 = hasEntry + }, + { "update notification $str1 alert: $bool1 hasEntry: $bool2" } + ) } fun logUpdateEntry(entry: NotificationEntry, updatePostTime: Boolean, reason: String?) { - buffer.log(TAG, INFO, { - str1 = entry.logKey - bool1 = updatePostTime - str2 = reason ?: "unknown" - }, { - "update entry $str1 updatePostTime: $bool1 reason: $str2" - }) + buffer.log( + TAG, + INFO, + { + str1 = entry.logKey + bool1 = updatePostTime + str2 = reason ?: "unknown" + }, + { "update entry $str1 updatePostTime: $bool1 reason: $str2" } + ) } fun logSnoozeLengthChange(packageSnoozeLengthMs: Int) { - buffer.log(TAG, INFO, { - int1 = packageSnoozeLengthMs - }, { - "snooze length changed: ${int1}ms" - }) + buffer.log( + TAG, + INFO, + { int1 = packageSnoozeLengthMs }, + { "snooze length changed: ${int1}ms" } + ) } fun logSetEntryPinned(entry: NotificationEntry, isPinned: Boolean, reason: String) { - buffer.log(TAG, VERBOSE, { - str1 = entry.logKey - bool1 = isPinned - str2 = reason - }, { - "$str2 => set entry pinned $str1 pinned: $bool1" - }) + buffer.log( + TAG, + VERBOSE, + { + str1 = entry.logKey + bool1 = isPinned + str2 = reason + }, + { "$str2 => set entry pinned $str1 pinned: $bool1" } + ) } fun logUpdatePinnedMode(hasPinnedNotification: Boolean) { - buffer.log(TAG, INFO, { - bool1 = hasPinnedNotification - }, { - "has pinned notification changed to $bool1" - }) + buffer.log( + TAG, + INFO, + { bool1 = hasPinnedNotification }, + { "has pinned notification changed to $bool1" } + ) } } -private const val TAG = "HeadsUpManager"
\ No newline at end of file +private const val TAG = "HeadsUpManager" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt index 8aa989ff390f..4f7749b9bd57 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt @@ -57,6 +57,7 @@ constructor( private val activityStarter: ActivityStarter, // Using a provider to avoid a circular dependency. private val viewModel: Provider<ModesDialogViewModel>, + private val dialogEventLogger: ModesDialogEventLogger, @Main private val mainCoroutineContext: CoroutineContext, ) : SystemUIDialog.Delegate { // NOTE: This should only be accessed/written from the main thread. @@ -102,7 +103,9 @@ constructor( ) } - private fun openSettings(dialog: SystemUIDialog) { + @VisibleForTesting + fun openSettings(dialog: SystemUIDialog) { + dialogEventLogger.logDialogSettings() val animationController = dialogTransitionAnimator.createActivityTransitionController(dialog) if (animationController == null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLogger.kt new file mode 100644 index 000000000000..33ed419afef2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLogger.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy.ui.dialog + +import com.android.internal.logging.UiEventLogger +import com.android.settingslib.notification.modes.ZenMode +import com.android.systemui.qs.QSModesEvent +import javax.inject.Inject + +class ModesDialogEventLogger +@Inject +constructor( + private val uiEventLogger: UiEventLogger, +) { + + fun logModeOn(mode: ZenMode) { + val id = + if (mode.isManualDnd) QSModesEvent.QS_MODES_DND_ON else QSModesEvent.QS_MODES_MODE_ON + uiEventLogger.log(id, /* uid= */ 0, mode.rule.packageName) + } + + fun logModeOff(mode: ZenMode) { + val id = + if (mode.isManualDnd) QSModesEvent.QS_MODES_DND_OFF else QSModesEvent.QS_MODES_MODE_OFF + uiEventLogger.log(id, /* uid= */ 0, mode.rule.packageName) + } + + fun logModeSettings(mode: ZenMode) { + val id = + if (mode.isManualDnd) QSModesEvent.QS_MODES_DND_SETTINGS + else QSModesEvent.QS_MODES_MODE_SETTINGS + uiEventLogger.log(id, /* uid= */ 0, mode.rule.packageName) + } + + fun logOpenDurationDialog(mode: ZenMode) { + // should only occur for manual Do Not Disturb. + if (!mode.isManualDnd) { + return + } + uiEventLogger.log(QSModesEvent.QS_MODES_DURATION_DIALOG) + } + + fun logDialogSettings() { + uiEventLogger.log(QSModesEvent.QS_MODES_SETTINGS) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt index 44b692fcb786..5772099706b5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt @@ -30,6 +30,7 @@ import com.android.systemui.res.R import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate +import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogEventLogger import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow @@ -49,6 +50,7 @@ constructor( zenModeInteractor: ZenModeInteractor, @Background val bgDispatcher: CoroutineDispatcher, private val dialogDelegate: ModesDialogDelegate, + private val dialogEventLogger: ModesDialogEventLogger, ) { private val zenDialogMetricsLogger = QSZenModeDialogMetricsLogger(context) @@ -94,14 +96,17 @@ constructor( if (!mode.rule.isEnabled) { openSettings(mode) } else if (mode.isActive) { + dialogEventLogger.logModeOff(mode) zenModeInteractor.deactivateMode(mode) } else { if (mode.rule.isManualInvocationAllowed) { if (zenModeInteractor.shouldAskForZenDuration(mode)) { + dialogEventLogger.logOpenDurationDialog(mode) // NOTE: The dialog handles turning on the mode itself. val dialog = makeZenModeDialog() dialog.show() } else { + dialogEventLogger.logModeOn(mode) zenModeInteractor.activateMode(mode) } } @@ -114,6 +119,7 @@ constructor( .flowOn(bgDispatcher) private fun openSettings(mode: ZenMode) { + dialogEventLogger.logModeSettings(mode) val intent: Intent = Intent(ACTION_AUTOMATIC_ZEN_RULE_SETTINGS) .putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, mode.id) diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt index 154737c71b9e..4f77cd04f5fa 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt @@ -26,7 +26,6 @@ import com.android.settingslib.media.MediaDevice import com.android.settingslib.media.MediaDevice.MediaDeviceType import com.android.settingslib.media.PhoneMediaDevice import com.android.settingslib.volume.data.repository.AudioRepository -import com.android.settingslib.volume.data.repository.AudioSharingRepository import com.android.settingslib.volume.domain.interactor.AudioModeInteractor import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background @@ -37,7 +36,6 @@ import javax.inject.Inject import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.flatMapLatest @@ -60,7 +58,6 @@ constructor( private val bluetoothAdapter: BluetoothAdapter?, private val deviceIconInteractor: DeviceIconInteractor, private val mediaOutputInteractor: MediaOutputInteractor, - audioSharingRepository: AudioSharingRepository, ) { val currentAudioDevice: StateFlow<AudioOutputDevice> = @@ -80,9 +77,6 @@ constructor( .flowOn(backgroundCoroutineContext) .stateIn(scope, SharingStarted.Eagerly, AudioOutputDevice.Unknown) - /** Whether the device is in audio sharing */ - val isInAudioSharing: Flow<Boolean> = audioSharingRepository.inAudioSharing - private fun AudioDeviceInfo.toAudioOutputDevice(): AudioOutputDevice { if ( BluetoothAdapter.checkBluetoothAddress(address) && diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt index 2170c36ec019..9aed8ab8f2e2 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt @@ -36,11 +36,15 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.withContext interface AudioSharingInteractor { + /** Audio sharing state on the device. */ + val isInAudioSharing: Flow<Boolean> + /** Audio sharing secondary headset volume changes. */ val volume: Flow<Int?> @@ -76,6 +80,7 @@ constructor( private val audioVolumeInteractor: AudioVolumeInteractor, private val audioSharingRepository: AudioSharingRepository ) : AudioSharingInteractor { + override val isInAudioSharing: Flow<Boolean> = audioSharingRepository.inAudioSharing override val volume: Flow<Int?> = combine(audioSharingRepository.secondaryGroupId, audioSharingRepository.volumeMap) { @@ -125,13 +130,13 @@ constructor( } private companion object { - const val TAG = "AudioSharingInteractor" const val DEFAULT_VOLUME = 20 } } @SysUISingleton class AudioSharingInteractorEmptyImpl @Inject constructor() : AudioSharingInteractor { + override val isInAudioSharing: Flow<Boolean> = flowOf(false) override val volume: Flow<Int?> = emptyFlow() override val volumeMin: Int = EMPTY_VOLUME override val volumeMax: Int = EMPTY_VOLUME diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt index ed25129ff082..a270d5ffa9de 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt @@ -18,6 +18,7 @@ package com.android.systemui.volume.panel.component.mediaoutput.domain.interacto import com.android.settingslib.volume.domain.interactor.AudioModeInteractor import com.android.systemui.volume.domain.interactor.AudioOutputInteractor +import com.android.systemui.volume.domain.interactor.AudioSharingInteractor import com.android.systemui.volume.domain.model.AudioOutputDevice import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaOutputComponentModel import com.android.systemui.volume.panel.component.mediaoutput.shared.model.SessionWithPlaybackState @@ -49,11 +50,12 @@ constructor( private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor, audioOutputInteractor: AudioOutputInteractor, audioModeInteractor: AudioModeInteractor, - interactor: MediaOutputInteractor, + mediaOutputInteractor: MediaOutputInteractor, + audioSharingInteractor: AudioSharingInteractor, ) { private val sessionWithPlaybackState: StateFlow<Result<SessionWithPlaybackState?>> = - interactor.defaultActiveMediaSession + mediaOutputInteractor.defaultActiveMediaSession .filterData() .flatMapLatest { session -> if (session == null) { @@ -77,7 +79,7 @@ constructor( val mediaOutputModel: StateFlow<Result<MediaOutputComponentModel>> = audioModeInteractor.isOngoingCall .flatMapLatest { isOngoingCall -> - audioOutputInteractor.isInAudioSharing.flatMapLatest { isInAudioSharing -> + audioSharingInteractor.isInAudioSharing.flatMapLatest { isInAudioSharing -> if (isOngoingCall) { currentAudioDevice.map { MediaOutputComponentModel.Calling(it, isInAudioSharing) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 075d8ae0adac..7aa415b64316 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -102,6 +102,7 @@ import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.flag.junit.FlagsParameterization; import android.service.dreams.IDreamManager; import android.service.trust.TrustAgentService; import android.telephony.ServiceState; @@ -111,9 +112,9 @@ import android.telephony.TelephonyManager; import android.testing.TestableLooper; import android.text.TextUtils; -import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.compose.animation.scene.ObservableTransitionState; import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.internal.foldables.FoldGracePeriodProvider; import com.android.internal.jank.InteractionJankMonitor; @@ -129,6 +130,7 @@ import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider; +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfig; import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigImpl; @@ -138,9 +140,13 @@ import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStat import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus; import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.SceneContainerFlagParameterizationKt; import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.scene.domain.interactor.SceneInteractor; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; +import com.android.systemui.scene.shared.model.Scenes; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; @@ -149,6 +155,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; +import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.settings.GlobalSettings; import org.junit.After; @@ -175,8 +182,11 @@ import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + @SmallTest -@RunWith(AndroidJUnit4.class) +@RunWith(ParameterizedAndroidJunit4.class) @TestableLooper.RunWithLooper public class KeyguardUpdateMonitorTest extends SysuiTestCase { private static final String PKG_ALLOWING_FP_LISTEN_ON_OCCLUDING_ACTIVITY = @@ -277,6 +287,12 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { private SelectedUserInteractor mSelectedUserInteractor; @Mock private DeviceEntryFaceAuthInteractor mFaceAuthInteractor; + @Mock + private AlternateBouncerInteractor mAlternateBouncerInteractor; + @Mock + private JavaAdapter mJavaAdapter; + @Mock + private SceneInteractor mSceneInteractor; @Captor private ArgumentCaptor<FaceAuthenticationListener> mFaceAuthenticationListener; @@ -301,6 +317,16 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mFingerprintAuthenticatorsRegisteredCallback; private final InstanceId mKeyguardInstanceId = InstanceId.fakeInstanceId(999); + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag(); + } + + public KeyguardUpdateMonitorTest(FlagsParameterization flags) { + super(); + mSetFlagsRule.setFlagsParameterization(flags); + } + @Before public void setup() throws RemoteException { mKosmos = new KosmosJavaAdapter(this); @@ -993,7 +1019,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { verifyFingerprintAuthenticateNeverCalled(); // WHEN alternate bouncer is shown mKeyguardUpdateMonitor.setKeyguardShowing(true, true); - mKeyguardUpdateMonitor.setAlternateBouncerShowing(true); + mKeyguardUpdateMonitor.setAlternateBouncerVisibility(true); // THEN make sure FP listening begins verifyFingerprintAuthenticateCall(); @@ -1489,7 +1515,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testShouldNotListenForUdfps_whenInLockDown() { // GIVEN a "we should listen for udfps" state - setKeyguardBouncerVisibility(false /* isVisible */); + mKeyguardUpdateMonitor.setPrimaryBouncerVisibility(false /* isVisible */); mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD); when(mStrongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true); @@ -2124,7 +2150,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { verifyFingerprintAuthenticateNeverCalled(); mKeyguardUpdateMonitor.setKeyguardShowing(true, true); - mKeyguardUpdateMonitor.setAlternateBouncerShowing(true); + mKeyguardUpdateMonitor.setAlternateBouncerVisibility(true); verifyFingerprintAuthenticateCall(); } @@ -2323,12 +2349,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } private void bouncerFullyVisible() { - setKeyguardBouncerVisibility(true); - } - - private void setKeyguardBouncerVisibility(boolean isVisible) { - mKeyguardUpdateMonitor.sendPrimaryBouncerChanged(isVisible, isVisible); - mTestableLooper.processAllMessages(); + mKeyguardUpdateMonitor.setPrimaryBouncerVisibility(true); } private void setBroadcastReceiverPendingResult(BroadcastReceiver receiver) { @@ -2434,7 +2455,12 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mPackageManager, mFingerprintManager, mBiometricManager, mFaceWakeUpTriggersConfig, mDevicePostureController, Optional.of(mInteractiveToAuthProvider), - mTaskStackChangeListeners, mSelectedUserInteractor, mActivityTaskManager); + mTaskStackChangeListeners, mSelectedUserInteractor, mActivityTaskManager, + () -> mAlternateBouncerInteractor, + () -> mJavaAdapter, + () -> mSceneInteractor); + setAlternateBouncerVisibility(false); + setPrimaryBouncerVisibility(false); setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker); start(); } @@ -2458,5 +2484,25 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { protected int getBiometricLockoutDelay() { return 0; } + + private void setPrimaryBouncerVisibility(boolean isVisible) { + if (SceneContainerFlag.isEnabled()) { + ObservableTransitionState transitionState = new ObservableTransitionState.Idle( + isVisible ? Scenes.Bouncer : Scenes.Lockscreen); + when(mSceneInteractor.getTransitionState()).thenReturn( + MutableStateFlow(transitionState)); + onTransitionStateChanged(transitionState); + } else { + sendPrimaryBouncerChanged(isVisible, isVisible); + mTestableLooper.processAllMessages(); + } + } + + private void setAlternateBouncerVisibility(boolean isVisible) { + if (SceneContainerFlag.isEnabled()) { + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(isVisible); + } + onAlternateBouncerVisibilityChange(isVisible); + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java index ff47fd1106bb..c74d340ee325 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java @@ -32,10 +32,6 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.hardware.display.DisplayManager; import android.os.RemoteException; -import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; import android.testing.TestableLooper; import android.view.Display; @@ -49,7 +45,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.app.viewcapture.ViewCaptureAwareWindowManager; -import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.model.SysUiState; import com.android.systemui.recents.OverviewProxyService; @@ -58,7 +53,6 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.util.settings.SecureSettings; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -73,9 +67,6 @@ import org.mockito.MockitoAnnotations; @TestableLooper.RunWithLooper(setAsMainLooper = true) public class IMagnificationConnectionTest extends SysuiTestCase { - @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); - private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY; @Mock private AccessibilityManager mAccessibilityManager; @@ -198,22 +189,7 @@ public class IMagnificationConnectionTest extends SysuiTestCase { } @Test - @RequiresFlagsDisabled(Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON) - public void showMagnificationButton_flagOff_directlyShowButton() throws RemoteException { - // magnification settings panel should not be showing - assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY)); - - mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY, - Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); - processAllPendingMessages(); - - verify(mModeSwitchesController).showButton(TEST_DISPLAY, - Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); - } - - @Test - @RequiresFlagsEnabled(Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON) - public void showMagnificationButton_flagOn_delayedShowButton() throws RemoteException { + public void showMagnificationButton_delayedShowButton() throws RemoteException { // magnification settings panel should not be showing assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY)); @@ -243,12 +219,9 @@ public class IMagnificationConnectionTest extends SysuiTestCase { // showMagnificationButton request to Magnification. processAllPendingMessages(); - // If the flag is on, the isMagnificationSettingsShowing will be checked after timeout, so + // The isMagnificationSettingsShowing will be checked after timeout, so // process all message after a timeout here to verify the showButton will not be called. - int timeout = Flags.delayShowMagnificationButton() - ? DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS + 100 - : 0; - processAllPendingMessages(timeout); + processAllPendingMessages(DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS + 100); verify(mModeSwitchesController, never()).showButton(TEST_DISPLAY, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); } @@ -262,7 +235,6 @@ public class IMagnificationConnectionTest extends SysuiTestCase { } @Test - @RequiresFlagsEnabled(Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON) public void removeMagnificationButton_delayingShowButton_doNotShowButtonAfterTimeout() throws RemoteException { // magnification settings panel should not be showing diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java index 9507077a89ae..7c0c5c209363 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java @@ -22,6 +22,9 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; import static android.view.WindowInsets.Type.systemBars; +import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MAX_VALUE; +import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MIN_VALUE; + import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; @@ -429,7 +432,7 @@ public class WindowMagnificationSettingsTest extends SysuiTestCase { mSettingView = mWindowMagnificationSettings.getSettingView(); mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_slider); assertThat(mZoomSeekbar.getProgress()).isEqualTo(10); - assertThat(mZoomSeekbar.getMax()).isEqualTo(70); + assertThat(mZoomSeekbar.getMax()).isEqualTo(getSeekBarMax()); } @Test @@ -473,29 +476,26 @@ public class WindowMagnificationSettingsTest extends SysuiTestCase { @Test public void seekbarProgress_maxMagnificationBefore_seekbarProgressIsMax() { - mWindowMagnificationSettings.setMagnificationScale(8f); + mWindowMagnificationSettings.setMagnificationScale(SCALE_MAX_VALUE); setupMagnificationCapabilityAndMode( /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); mWindowMagnificationSettings.showSettingPanel(); - // 8.0f is max magnification {@link MagnificationScaleProvider#MAX_SCALE}. - // Max zoom seek bar is 70. - assertThat(mZoomSeekbar.getProgress()).isEqualTo(70); + assertThat(mZoomSeekbar.getProgress()).isEqualTo(getSeekBarMax()); } @Test public void seekbarProgress_aboveMaxMagnificationBefore_seekbarProgressIsMax() { - mWindowMagnificationSettings.setMagnificationScale(9f); + mWindowMagnificationSettings.setMagnificationScale(SCALE_MAX_VALUE + 1f); setupMagnificationCapabilityAndMode( /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); mWindowMagnificationSettings.showSettingPanel(); - // Max zoom seek bar is 70. - assertThat(mZoomSeekbar.getProgress()).isEqualTo(70); + assertThat(mZoomSeekbar.getProgress()).isEqualTo(getSeekBarMax()); } @Test @@ -589,4 +589,11 @@ public class WindowMagnificationSettingsTest extends SysuiTestCase { anyInt(), eq(UserHandle.USER_CURRENT))).thenReturn(mode); } + + private int getSeekBarMax() { + // Calculates the maximum index (or positions) the seekbar can have. + // This is achieved by multiplying the range of possible scales with the magnitude of + // change per each movement on the seekbar. + return (int) ((SCALE_MAX_VALUE - SCALE_MIN_VALUE) * mZoomSeekbar.getChangeMagnitude()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java index 206bbbfba753..4ce2d7c6d78b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java @@ -51,6 +51,7 @@ import android.widget.FrameLayout; import androidx.compose.ui.platform.ComposeView; import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleOwner; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -611,7 +612,8 @@ public class QSImplTest extends SysuiTestCase { when(mQSContainerImplController.getView()).thenReturn(mContainer); when(mQSPanelController.getTileLayout()).thenReturn(mQQsTileLayout); when(mQuickQSPanelController.getTileLayout()).thenReturn(mQsTileLayout); - when(mFooterActionsViewModelFactory.create(any())).thenReturn(mFooterActionsViewModel); + when(mFooterActionsViewModelFactory.create(any(LifecycleOwner.class))) + .thenReturn(mFooterActionsViewModel); } private void setUpMedia() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt index 3abdf6212f22..cb92b7745961 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt @@ -91,7 +91,12 @@ class NotificationTransitionAnimatorControllerTest : SysuiTestCase() { assertFalse(isExpandAnimationRunning!!) verify(headsUpManager) - .removeNotification(notificationKey, true /* releaseImmediately */, true /* animate */) + .removeNotification( + notificationKey, + /* releaseImmediately= */ true, + /* animate= */ true, + /* reason= */ "onIntentStarted(willAnimate=false)" + ) verify(onFinishAnimationCallback).run() } @@ -109,7 +114,12 @@ class NotificationTransitionAnimatorControllerTest : SysuiTestCase() { assertFalse(isExpandAnimationRunning!!) verify(headsUpManager) - .removeNotification(notificationKey, true /* releaseImmediately */, true /* animate */) + .removeNotification( + notificationKey, + /* releaseImmediately= */ true, + /* animate= */ true, + /* reason= */ "onLaunchAnimationCancelled()" + ) verify(onFinishAnimationCallback).run() } @@ -127,7 +137,12 @@ class NotificationTransitionAnimatorControllerTest : SysuiTestCase() { assertFalse(isExpandAnimationRunning!!) verify(headsUpManager) - .removeNotification(notificationKey, true /* releaseImmediately */, false /* animate */) + .removeNotification( + notificationKey, + /* releaseImmediately= */ true, + /* animate= */ false, + /* reason= */ "onLaunchAnimationEnd()" + ) verify(onFinishAnimationCallback).run() } @@ -161,12 +176,18 @@ class NotificationTransitionAnimatorControllerTest : SysuiTestCase() { controller.onTransitionAnimationEnd(isExpandingFullyAbove = true) verify(headsUpManager) - .removeNotification(summary.key, true /* releaseImmediately */, false /* animate */) + .removeNotification( + summary.key, + /* releaseImmediately= */ true, + /* animate= */ false, + /* reason= */ "onLaunchAnimationEnd()" + ) verify(headsUpManager, never()) .removeNotification( notification.entry.key, - true /* releaseImmediately */, - false /* animate */ + /* releaseImmediately= */ true, + /* animate= */ false, + /* reason= */ "onLaunchAnimationEnd()" ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt index 8e9323fead92..b4f4138fd409 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt @@ -108,30 +108,31 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { private val executor = FakeExecutor(systemClock) private val huns: ArrayList<NotificationEntry> = ArrayList() private lateinit var helper: NotificationGroupTestHelper + @Before fun setUp() { MockitoAnnotations.initMocks(this) helper = NotificationGroupTestHelper(mContext) - coordinator = HeadsUpCoordinator( - logger, - systemClock, - headsUpManager, - headsUpViewBinder, - visualInterruptionDecisionProvider, - remoteInputManager, - launchFullScreenIntentProvider, - flags, - headerController, - executor) + coordinator = + HeadsUpCoordinator( + logger, + systemClock, + headsUpManager, + headsUpViewBinder, + visualInterruptionDecisionProvider, + remoteInputManager, + launchFullScreenIntentProvider, + flags, + headerController, + executor + ) coordinator.attach(notifPipeline) // capture arguments: collectionListener = withArgCaptor { verify(notifPipeline).addCollectionListener(capture()) } - notifPromoter = withArgCaptor { - verify(notifPipeline).addPromoter(capture()) - } + notifPromoter = withArgCaptor { verify(notifPipeline).addPromoter(capture()) } notifLifetimeExtender = withArgCaptor { verify(notifPipeline).addNotificationLifetimeExtender(capture()) } @@ -141,9 +142,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { beforeFinalizeFilterListener = withArgCaptor { verify(notifPipeline).addOnBeforeFinalizeFilterListener(capture()) } - onHeadsUpChangedListener = withArgCaptor { - verify(headsUpManager).addListener(capture()) - } + onHeadsUpChangedListener = withArgCaptor { verify(headsUpManager).addListener(capture()) } actionPressListener = withArgCaptor { verify(remoteInputManager).addActionPressListener(capture()) } @@ -187,8 +186,8 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, 0)) executor.advanceClockToLast() executor.runAllReady() - verify(headsUpManager, times(0)).removeNotification(anyString(), eq(false)) - verify(headsUpManager, times(1)).removeNotification(anyString(), eq(true)) + verify(headsUpManager, times(0)).removeNotification(anyString(), eq(false), anyString()) + verify(headsUpManager, times(1)).removeNotification(anyString(), eq(true), anyString()) } @Test @@ -203,8 +202,8 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { executor.advanceClockToLast() executor.runAllReady() assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, 0)) - verify(headsUpManager, times(0)).removeNotification(anyString(), eq(false)) - verify(headsUpManager, times(0)).removeNotification(anyString(), eq(true)) + verify(headsUpManager, times(0)).removeNotification(anyString(), eq(false), anyString()) + verify(headsUpManager, times(0)).removeNotification(anyString(), eq(true), anyString()) } @Test @@ -217,7 +216,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { notifLifetimeExtender.cancelLifetimeExtension(entry) executor.advanceClockToLast() executor.runAllReady() - verify(headsUpManager, times(0)).removeNotification(anyString(), any()) + verify(headsUpManager, never()).removeNotification(anyString(), any(), anyString()) } @Test @@ -227,14 +226,14 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { whenever(headsUpManager.canRemoveImmediately(anyString())).thenReturn(false) whenever(headsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L) - assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, /* reason = */ 0)) + assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, /* reason= */ 0)) actionPressListener.accept(entry) executor.runAllReady() verify(endLifetimeExtension, times(1)).onEndLifetimeExtension(notifLifetimeExtender, entry) - collectionListener.onEntryRemoved(entry, /* reason = */ 0) - verify(headsUpManager, times(1)).removeNotification(eq(entry.key), any()) + collectionListener.onEntryRemoved(entry, /* reason= */ 0) + verify(headsUpManager, times(1)).removeNotification(eq(entry.key), any(), anyString()) } @Test @@ -248,8 +247,8 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { whenever(headsUpManager.canRemoveImmediately(anyString())).thenReturn(true) assertFalse(notifLifetimeExtender.maybeExtendLifetime(entry, 0)) - collectionListener.onEntryRemoved(entry, /* reason = */ 0) - verify(headsUpManager, times(1)).removeNotification(eq(entry.key), any()) + collectionListener.onEntryRemoved(entry, /* reason= */ 0) + verify(headsUpManager, times(1)).removeNotification(eq(entry.key), any(), anyString()) } @Test @@ -261,8 +260,8 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { addHUN(entry) executor.advanceClockToLast() executor.runAllReady() - verify(headsUpManager, times(0)).removeNotification(anyString(), eq(false)) - verify(headsUpManager, times(0)).removeNotification(anyString(), eq(true)) + verify(headsUpManager, never()).removeNotification(anyString(), eq(false), anyString()) + verify(headsUpManager, never()).removeNotification(anyString(), eq(true), anyString()) } @Test @@ -273,8 +272,8 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, 0)) executor.advanceClockToLast() executor.runAllReady() - verify(headsUpManager, times(1)).removeNotification(anyString(), eq(false)) - verify(headsUpManager, times(0)).removeNotification(anyString(), eq(true)) + verify(headsUpManager, times(1)).removeNotification(anyString(), eq(false), anyString()) + verify(headsUpManager, never()).removeNotification(anyString(), eq(true), anyString()) } @Test @@ -326,9 +325,8 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { // THEN only promote the current HUN, mEntry assertTrue(notifPromoter.shouldPromoteToTopLevel(entry)) - assertFalse(notifPromoter.shouldPromoteToTopLevel(NotificationEntryBuilder() - .setPkg("test-package2") - .build())) + val testPackage2 = NotificationEntryBuilder().setPkg("test-package2").build() + assertFalse(notifPromoter.shouldPromoteToTopLevel(testPackage2)) } @Test @@ -338,9 +336,9 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { // THEN only section the current HUN, mEntry assertTrue(notifSectioner.isInSection(entry)) - assertFalse(notifSectioner.isInSection(NotificationEntryBuilder() - .setPkg("test-package") - .build())) + assertFalse( + notifSectioner.isInSection(NotificationEntryBuilder().setPkg("test-package").build()) + ) } @Test @@ -350,10 +348,12 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { // THEN only the current HUN, mEntry, should be lifetimeExtended assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, /* cancellationReason */ 0)) - assertFalse(notifLifetimeExtender.maybeExtendLifetime( - NotificationEntryBuilder() - .setPkg("test-package") - .build(), /* cancellationReason */ 0)) + assertFalse( + notifLifetimeExtender.maybeExtendLifetime( + NotificationEntryBuilder().setPkg("test-package").build(), + /* reason= */ 0 + ) + ) } @Test @@ -366,8 +366,9 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry)) verify(headsUpManager, never()).showNotification(entry) withArgCaptor<BindCallback> { - verify(headsUpViewBinder).bindHeadsUpView(eq(entry), capture()) - }.onBindFinished(entry) + verify(headsUpViewBinder).bindHeadsUpView(eq(entry), capture()) + } + .onBindFinished(entry) // THEN we tell the HeadsUpManager to show the notification verify(headsUpManager).showNotification(entry) @@ -430,7 +431,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { whenever(remoteInputManager.isSpinning(any())).thenReturn(false) // THEN heads up manager should remove the entry - verify(headsUpManager).removeNotification(entry.key, false) + verify(headsUpManager).removeNotification(eq(entry.key), eq(false), anyString()) } private fun addHUN(entry: NotificationEntry) { @@ -545,19 +546,22 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { collectionListener.onEntryAdded(groupSibling1) collectionListener.onEntryAdded(groupSibling2) - val beforeTransformGroup = GroupEntryBuilder() - .setSummary(groupSummary) - .setChildren(listOf(groupSibling1, groupPriority, groupSibling2)) - .build() + val beforeTransformGroup = + GroupEntryBuilder() + .setSummary(groupSummary) + .setChildren(listOf(groupSibling1, groupPriority, groupSibling2)) + .build() beforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup)) verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) - val afterTransformGroup = GroupEntryBuilder() - .setSummary(groupSummary) - .setChildren(listOf(groupSibling1, groupSibling2)) - .build() - beforeFinalizeFilterListener - .onBeforeFinalizeFilter(listOf(groupPriority, afterTransformGroup)) + val afterTransformGroup = + GroupEntryBuilder() + .setSummary(groupSummary) + .setChildren(listOf(groupSibling1, groupSibling2)) + .build() + beforeFinalizeFilterListener.onBeforeFinalizeFilter( + listOf(groupPriority, afterTransformGroup) + ) verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any()) finishBind(groupPriority) @@ -583,19 +587,22 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { collectionListener.onEntryUpdated(groupSibling1) collectionListener.onEntryUpdated(groupSibling2) - val beforeTransformGroup = GroupEntryBuilder() - .setSummary(groupSummary) - .setChildren(listOf(groupSibling1, groupPriority, groupSibling2)) - .build() + val beforeTransformGroup = + GroupEntryBuilder() + .setSummary(groupSummary) + .setChildren(listOf(groupSibling1, groupPriority, groupSibling2)) + .build() beforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup)) verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) - val afterTransformGroup = GroupEntryBuilder() - .setSummary(groupSummary) - .setChildren(listOf(groupSibling1, groupSibling2)) - .build() - beforeFinalizeFilterListener - .onBeforeFinalizeFilter(listOf(groupPriority, afterTransformGroup)) + val afterTransformGroup = + GroupEntryBuilder() + .setSummary(groupSummary) + .setChildren(listOf(groupSibling1, groupSibling2)) + .build() + beforeFinalizeFilterListener.onBeforeFinalizeFilter( + listOf(groupPriority, afterTransformGroup) + ) verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any()) finishBind(groupPriority) @@ -618,19 +625,22 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { collectionListener.onEntryUpdated(groupSummary) collectionListener.onEntryUpdated(groupPriority) - val beforeTransformGroup = GroupEntryBuilder() - .setSummary(groupSummary) - .setChildren(listOf(groupSibling1, groupPriority, groupSibling2)) - .build() + val beforeTransformGroup = + GroupEntryBuilder() + .setSummary(groupSummary) + .setChildren(listOf(groupSibling1, groupPriority, groupSibling2)) + .build() beforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup)) verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) - val afterTransformGroup = GroupEntryBuilder() - .setSummary(groupSummary) - .setChildren(listOf(groupSibling1, groupSibling2)) - .build() - beforeFinalizeFilterListener - .onBeforeFinalizeFilter(listOf(groupPriority, afterTransformGroup)) + val afterTransformGroup = + GroupEntryBuilder() + .setSummary(groupSummary) + .setChildren(listOf(groupSibling1, groupSibling2)) + .build() + beforeFinalizeFilterListener.onBeforeFinalizeFilter( + listOf(groupPriority, afterTransformGroup) + ) verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any()) finishBind(groupPriority) @@ -654,19 +664,22 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { collectionListener.onEntryUpdated(groupSibling1) collectionListener.onEntryUpdated(groupSibling2) - val beforeTransformGroup = GroupEntryBuilder() - .setSummary(groupSummary) - .setChildren(listOf(groupSibling1, groupPriority, groupSibling2)) - .build() + val beforeTransformGroup = + GroupEntryBuilder() + .setSummary(groupSummary) + .setChildren(listOf(groupSibling1, groupPriority, groupSibling2)) + .build() beforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup)) verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) - val afterTransformGroup = GroupEntryBuilder() - .setSummary(groupSummary) - .setChildren(listOf(groupSibling1, groupSibling2)) - .build() - beforeFinalizeFilterListener - .onBeforeFinalizeFilter(listOf(groupPriority, afterTransformGroup)) + val afterTransformGroup = + GroupEntryBuilder() + .setSummary(groupSummary) + .setChildren(listOf(groupSibling1, groupSibling2)) + .build() + beforeFinalizeFilterListener.onBeforeFinalizeFilter( + listOf(groupPriority, afterTransformGroup) + ) finishBind(groupSummary) verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupPriority), any()) @@ -688,10 +701,11 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { collectionListener.onEntryAdded(groupSummary) collectionListener.onEntryAdded(groupSibling1) collectionListener.onEntryAdded(groupSibling2) - val groupEntry = GroupEntryBuilder() - .setSummary(groupSummary) - .setChildren(listOf(groupSibling1, groupSibling2)) - .build() + val groupEntry = + GroupEntryBuilder() + .setSummary(groupSummary) + .setChildren(listOf(groupSibling1, groupSibling2)) + .build() beforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry)) verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) @@ -708,16 +722,16 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { @Test fun testNoTransferTwoChildAlert_withGroupAlertAll() { setShouldHeadsUp(groupSummary) - whenever(notifPipeline.allNotifs) - .thenReturn(listOf(groupSummary, groupChild1, groupChild2)) + whenever(notifPipeline.allNotifs).thenReturn(listOf(groupSummary, groupChild1, groupChild2)) collectionListener.onEntryAdded(groupSummary) collectionListener.onEntryAdded(groupChild1) collectionListener.onEntryAdded(groupChild2) - val groupEntry = GroupEntryBuilder() - .setSummary(groupSummary) - .setChildren(listOf(groupChild1, groupChild2)) - .build() + val groupEntry = + GroupEntryBuilder() + .setSummary(groupSummary) + .setChildren(listOf(groupChild1, groupChild2)) + .build() beforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry)) verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) @@ -742,10 +756,11 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { collectionListener.onEntryAdded(groupSummary) collectionListener.onEntryAdded(groupChild1) collectionListener.onEntryAdded(groupChild2) - val groupEntry = GroupEntryBuilder() - .setSummary(groupSummary) - .setChildren(listOf(groupChild1, groupChild2)) - .build() + val groupEntry = + GroupEntryBuilder() + .setSummary(groupSummary) + .setChildren(listOf(groupChild1, groupChild2)) + .build() beforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry)) verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) @@ -1045,9 +1060,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { .thenReturn(DecisionImpl.of(should)) } - private fun setDefaultShouldFullScreen( - originalDecision: FullScreenIntentDecision - ) { + private fun setDefaultShouldFullScreen(originalDecision: FullScreenIntentDecision) { val provider = visualInterruptionDecisionProvider whenever(provider.makeUnloggedFullScreenIntentDecision(any())).thenAnswer { val entry: NotificationEntry = it.getArgument(0) @@ -1059,11 +1072,8 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { entry: NotificationEntry, originalDecision: FullScreenIntentDecision ) { - whenever( - visualInterruptionDecisionProvider.makeUnloggedFullScreenIntentDecision(entry) - ).thenAnswer { - FullScreenIntentDecisionImpl(entry, originalDecision) - } + whenever(visualInterruptionDecisionProvider.makeUnloggedFullScreenIntentDecision(entry)) + .thenAnswer { FullScreenIntentDecisionImpl(entry, originalDecision) } } private fun verifyLoggedFullScreenIntentDecision( @@ -1089,7 +1099,8 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { private fun finishBind(entry: NotificationEntry) { verify(headsUpManager, never()).showNotification(entry) withArgCaptor<BindCallback> { - verify(headsUpViewBinder).bindHeadsUpView(eq(entry), capture()) - }.onBindFinished(entry) + verify(headsUpViewBinder).bindHeadsUpView(eq(entry), capture()) + } + .onBindFinished(entry) } } 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 30e7247b325e..70ac31d99559 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 @@ -391,6 +391,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { configurationController, mStatusOverlayHoverListenerFactory, fakeDarkIconDispatcher, + mock(StatusBarContentInsetsProvider::class.java), ) .create(view) .also { it.init() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt index ed5ec7b2160a..648ddf847ea0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt @@ -32,6 +32,7 @@ import android.view.View import android.view.WindowInsets import android.widget.FrameLayout import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT import com.android.systemui.Flags.FLAG_STATUS_BAR_SWIPE_OVER_CHIP import com.android.systemui.Gefingerpoken import com.android.systemui.SysuiTestCase @@ -42,6 +43,7 @@ import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test +import org.mockito.Mockito.never import org.mockito.Mockito.spy import org.mockito.Mockito.times import org.mockito.Mockito.verify @@ -54,21 +56,14 @@ class PhoneStatusBarViewTest : SysuiTestCase() { private val systemIconsContainer: View get() = view.requireViewById(R.id.system_icons) - private val contentInsetsProvider = mock<StatusBarContentInsetsProvider>() private val windowController = mock<StatusBarWindowController>() @Before fun setUp() { - mDependency.injectTestDependency( - StatusBarContentInsetsProvider::class.java, - contentInsetsProvider - ) mDependency.injectTestDependency(StatusBarWindowController::class.java, windowController) context.ensureTestableResources() view = spy(createStatusBarView()) whenever(view.rootWindowInsets).thenReturn(emptyWindowInsets()) - whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) - .thenReturn(Insets.NONE) } @Test @@ -183,21 +178,40 @@ class PhoneStatusBarViewTest : SysuiTestCase() { } @Test - fun onAttachedToWindow_updatesWindowHeight() { + @DisableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT) + fun onAttachedToWindow_flagOff_updatesWindowHeight() { view.onAttachedToWindow() verify(windowController).refreshStatusBarHeight() } @Test - fun onConfigurationChanged_updatesWindowHeight() { + @EnableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT) + fun onAttachedToWindow_flagOn_doesNotUpdateWindowHeight() { + view.onAttachedToWindow() + + verify(windowController, never()).refreshStatusBarHeight() + } + + @Test + @DisableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT) + fun onConfigurationChanged_flagOff_updatesWindowHeight() { view.onConfigurationChanged(Configuration()) verify(windowController).refreshStatusBarHeight() } @Test - fun onConfigurationChanged_multipleCalls_updatesWindowHeightMultipleTimes() { + @EnableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT) + fun onConfigurationChanged_flagOn_doesNotUpdateWindowHeight() { + view.onConfigurationChanged(Configuration()) + + verify(windowController, never()).refreshStatusBarHeight() + } + + @Test + @DisableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT) + fun onConfigurationChanged_multipleCalls_flagOff_updatesWindowHeightMultipleTimes() { view.onConfigurationChanged(Configuration()) view.onConfigurationChanged(Configuration()) view.onConfigurationChanged(Configuration()) @@ -207,10 +221,20 @@ class PhoneStatusBarViewTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT) + fun onConfigurationChanged_multipleCalls_flagOn_neverUpdatesWindowHeight() { + view.onConfigurationChanged(Configuration()) + view.onConfigurationChanged(Configuration()) + view.onConfigurationChanged(Configuration()) + view.onConfigurationChanged(Configuration()) + + verify(windowController, never()).refreshStatusBarHeight() + } + + @Test fun onAttachedToWindow_updatesLeftTopRightPaddingsBasedOnInsets() { val insets = Insets.of(/* left= */ 10, /* top= */ 20, /* right= */ 30, /* bottom= */ 40) - whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) - .thenReturn(insets) + view.setInsetsFetcher { insets } view.onAttachedToWindow() @@ -221,10 +245,23 @@ class PhoneStatusBarViewTest : SysuiTestCase() { } @Test + fun onAttachedToWindow_noInsetsFetcher_noCrash() { + // Don't call `PhoneStatusBarView.setInsetsFetcher` + + // WHEN the view is attached + view.onAttachedToWindow() + + // THEN there's no crash, and the padding stays as it was + assertThat(view.paddingLeft).isEqualTo(0) + assertThat(view.paddingTop).isEqualTo(0) + assertThat(view.paddingRight).isEqualTo(0) + assertThat(view.paddingBottom).isEqualTo(0) + } + + @Test fun onConfigurationChanged_updatesLeftTopRightPaddingsBasedOnInsets() { val insets = Insets.of(/* left= */ 40, /* top= */ 30, /* right= */ 20, /* bottom= */ 10) - whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) - .thenReturn(insets) + view.setInsetsFetcher { insets } view.onConfigurationChanged(Configuration()) @@ -235,17 +272,31 @@ class PhoneStatusBarViewTest : SysuiTestCase() { } @Test + fun onConfigurationChanged_noInsetsFetcher_noCrash() { + // Don't call `PhoneStatusBarView.setInsetsFetcher` + + // WHEN the view is attached + view.onConfigurationChanged(Configuration()) + + // THEN there's no crash, and the padding stays as it was + assertThat(view.paddingLeft).isEqualTo(0) + assertThat(view.paddingTop).isEqualTo(0) + assertThat(view.paddingRight).isEqualTo(0) + assertThat(view.paddingBottom).isEqualTo(0) + } + + @Test fun onConfigurationChanged_noRelevantChange_doesNotUpdateInsets() { val previousInsets = Insets.of(/* left= */ 40, /* top= */ 30, /* right= */ 20, /* bottom= */ 10) - whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) - .thenReturn(previousInsets) + view.setInsetsFetcher { previousInsets } + context.orCreateTestableResources.overrideConfiguration(Configuration()) view.onAttachedToWindow() val newInsets = Insets.NONE - whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) - .thenReturn(newInsets) + view.setInsetsFetcher { newInsets } + view.onConfigurationChanged(Configuration()) assertThat(view.paddingLeft).isEqualTo(previousInsets.left) @@ -258,16 +309,14 @@ class PhoneStatusBarViewTest : SysuiTestCase() { fun onConfigurationChanged_densityChanged_updatesInsets() { val previousInsets = Insets.of(/* left= */ 40, /* top= */ 30, /* right= */ 20, /* bottom= */ 10) - whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) - .thenReturn(previousInsets) + view.setInsetsFetcher { previousInsets } val configuration = Configuration() configuration.densityDpi = 123 context.orCreateTestableResources.overrideConfiguration(configuration) view.onAttachedToWindow() val newInsets = Insets.NONE - whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) - .thenReturn(newInsets) + view.setInsetsFetcher { newInsets } configuration.densityDpi = 456 view.onConfigurationChanged(configuration) @@ -281,16 +330,14 @@ class PhoneStatusBarViewTest : SysuiTestCase() { fun onConfigurationChanged_fontScaleChanged_updatesInsets() { val previousInsets = Insets.of(/* left= */ 40, /* top= */ 30, /* right= */ 20, /* bottom= */ 10) - whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) - .thenReturn(previousInsets) + view.setInsetsFetcher { previousInsets } val configuration = Configuration() configuration.fontScale = 1f context.orCreateTestableResources.overrideConfiguration(configuration) view.onAttachedToWindow() val newInsets = Insets.NONE - whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) - .thenReturn(newInsets) + view.setInsetsFetcher { newInsets } configuration.fontScale = 2f view.onConfigurationChanged(configuration) @@ -316,8 +363,7 @@ class PhoneStatusBarViewTest : SysuiTestCase() { @Test fun onApplyWindowInsets_updatesLeftTopRightPaddingsBasedOnInsets() { val insets = Insets.of(/* left= */ 90, /* top= */ 10, /* right= */ 45, /* bottom= */ 50) - whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) - .thenReturn(insets) + view.setInsetsFetcher { insets } view.onApplyWindowInsets(WindowInsets(Rect())) @@ -358,7 +404,7 @@ class PhoneStatusBarViewTest : SysuiTestCase() { /* typeVisibilityMap = */ booleanArrayOf(), /* isRound = */ false, /* forceConsumingTypes = */ 0, - /* forceConsumingCaptionBar = */ false, + /* forceConsumingOpaqueCaptionBar = */ false, /* suppressScrimTypes = */ 0, /* displayCutout = */ DisplayCutout.NO_CUTOUT, /* roundedCorners = */ RoundedCorners.NO_ROUNDED_CORNERS, 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 9fa392f3a337..7a34e94ab362 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 @@ -434,7 +434,11 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { // Then verify(mBubblesManager).onUserChangedBubble(entry, false); - verify(mHeadsUpManager).removeNotification(entry.getKey(), true); + verify(mHeadsUpManager).removeNotification( + entry.getKey(), + /* releaseImmediately= */ true, + /* reason= */ "onNotificationBubbleIconClicked" + ); verifyNoMoreInteractions(mContentIntent); verifyNoMoreInteractions(mShadeController); @@ -456,7 +460,11 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { // Then verify(mBubblesManager).onUserChangedBubble(entry, true); - verify(mHeadsUpManager).removeNotification(entry.getKey(), true); + verify(mHeadsUpManager).removeNotification( + entry.getKey(), + /* releaseImmediately= */ true, + /* reason= */ "onNotificationBubbleIconClicked" + ); verify(mContentIntent, atLeastOnce()).isActivity(); verifyNoMoreInteractions(mContentIntent); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt index bf0a39be044b..06b3b57bd133 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt @@ -57,6 +57,7 @@ class ModesDialogDelegateTest : SysuiTestCase() { private val activityStarter = kosmos.activityStarter private val mockDialogTransitionAnimator = kosmos.mockDialogTransitionAnimator private val mockAnimationController = kosmos.mockActivityTransitionAnimatorController + private val mockDialogEventLogger = kosmos.mockModesDialogEventLogger private lateinit var underTest: ModesDialogDelegate @Before @@ -75,6 +76,7 @@ class ModesDialogDelegateTest : SysuiTestCase() { mockDialogTransitionAnimator, activityStarter, { kosmos.modesDialogViewModel }, + mockDialogEventLogger, kosmos.mainCoroutineContext, ) } @@ -121,4 +123,12 @@ class ModesDialogDelegateTest : SysuiTestCase() { assertThat(underTest.currentDialog).isNull() } + + @Test + fun openSettings_logsEvent() = + testScope.runTest { + val dialog: SystemUIDialog = mock() + underTest.openSettings(dialog) + verify(mockDialogEventLogger).logDialogSettings() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt index 2021f02e5a8a..55aff130f691 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt @@ -123,11 +123,12 @@ class BubbleEducationControllerTest : SysUiStateTest() { /* taskId= */ 0, "locus", /* isDismissable= */ true, + directExecutor(), directExecutor() ) {} } else { val intent = Intent(Intent.ACTION_VIEW).setPackage(mContext.packageName) - Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) + Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor()) } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index e5e04dc9b82f..9dd3e53efa63 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -459,7 +459,7 @@ public class BubblesTest extends SysuiTestCase { mContext.getSystemService(WindowManager.class)); mPositioner.setMaxBubbles(5); mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, mEducationController, - syncExecutor); + syncExecutor, syncExecutor); when(mUserManager.getProfiles(ActivityManager.getCurrentUser())).thenReturn( Collections.singletonList(mock(UserInfo.class))); @@ -2465,9 +2465,10 @@ public class BubblesTest extends SysuiTestCase { workEntry.setBubbleMetadata(getMetadata()); workEntry.setFlagBubble(true); + SyncExecutor executor = new SyncExecutor(); return new Bubble(mBubblesManager.notifToBubbleEntry(workEntry), null, - mock(Bubbles.PendingIntentCanceledListener.class), new SyncExecutor()); + mock(Bubbles.PendingIntentCanceledListener.class), executor, executor); } private BubbleEntry createBubbleEntry(boolean isConversation) { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorKosmos.kt index ee48c105a987..2ab82214e497 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorKosmos.kt @@ -17,6 +17,7 @@ package com.android.systemui.communal.domain.interactor import com.android.systemui.communal.data.repository.communalSceneRepository +import com.android.systemui.communal.shared.log.communalSceneLogger import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope @@ -24,6 +25,7 @@ val Kosmos.communalSceneInteractor: CommunalSceneInteractor by Kosmos.Fixture { CommunalSceneInteractor( applicationScope = applicationCoroutineScope, - communalSceneRepository = communalSceneRepository, + repository = communalSceneRepository, + logger = communalSceneLogger, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/shared/log/CommunalSceneLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/shared/log/CommunalSceneLoggerKosmos.kt new file mode 100644 index 000000000000..b560ee882929 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/shared/log/CommunalSceneLoggerKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.shared.log + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.log.logcatLogBuffer + +val Kosmos.communalSceneLogger: CommunalSceneLogger by + Kosmos.Fixture { CommunalSceneLogger(logcatLogBuffer("CommunalSceneLogger")) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt index f1625948880f..64ae05131b5a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.domain.interactor +import com.android.systemui.communal.domain.interactor.communalSceneInteractor import com.android.systemui.communal.domain.interactor.communalSettingsInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository @@ -38,6 +39,7 @@ var Kosmos.fromDreamingTransitionInteractor by mainDispatcher = testDispatcher, keyguardInteractor = keyguardInteractor, glanceableHubTransitions = glanceableHubTransitions, + communalSceneInteractor = communalSceneInteractor, communalSettingsInteractor = communalSettingsInteractor, powerInteractor = powerInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt index 450dcc25c903..d06bab2f5345 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt @@ -19,13 +19,11 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import com.android.systemui.util.mockito.mock import kotlinx.coroutines.ExperimentalCoroutinesApi @ExperimentalCoroutinesApi val Kosmos.dreamingToLockscreenTransitionViewModel by Fixture { DreamingToLockscreenTransitionViewModel( - fromDreamingTransitionInteractor = mock(), animationFlow = keyguardTransitionAnimationFlow, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt index 9ff7dd590781..ffe6918a56f8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt @@ -27,6 +27,9 @@ class FakeFgsManagerController( numRunningPackages: Int = 0, ) : FgsManagerController { + var initialized = false + private set + override var numRunningPackages = numRunningPackages set(value) { if (value != field) { @@ -53,7 +56,9 @@ class FakeFgsManagerController( dialogDismissedListeners.forEach { it.onDialogDismissed() } } - override fun init() {} + override fun init() { + initialized = true + } override fun showDialog(expandable: Expandable?) {} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt new file mode 100644 index 000000000000..d37d8f39b9ee --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.composefragment.viewmodel + +import android.content.res.mainResources +import androidx.lifecycle.LifecycleCoroutineScope +import com.android.systemui.common.ui.domain.interactor.configurationInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.footerActionsController +import com.android.systemui.qs.footerActionsViewModelFactory +import com.android.systemui.qs.ui.viewmodel.quickSettingsContainerViewModel +import com.android.systemui.shade.largeScreenHeaderHelper +import com.android.systemui.shade.transition.largeScreenShadeInterpolator +import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository +import com.android.systemui.statusbar.phone.keyguardBypassController +import com.android.systemui.statusbar.sysuiStatusBarStateController + +val Kosmos.qsFragmentComposeViewModelFactory by + Kosmos.Fixture { + object : QSFragmentComposeViewModel.Factory { + override fun create( + lifecycleScope: LifecycleCoroutineScope + ): QSFragmentComposeViewModel { + return QSFragmentComposeViewModel( + quickSettingsContainerViewModel, + mainResources, + footerActionsViewModelFactory, + footerActionsController, + sysuiStatusBarStateController, + keyguardBypassController, + disableFlagsRepository, + largeScreenShadeInterpolator, + configurationInteractor, + largeScreenHeaderHelper, + lifecycleScope, + ) + } + } + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt index 99bb47976c87..932e768676cb 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt @@ -33,6 +33,7 @@ var Kosmos.modesDialogDelegate: ModesDialogDelegate by dialogTransitionAnimator, activityStarter, { modesDialogViewModel }, + modesDialogEventLogger, mainCoroutineContext, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerKosmos.kt new file mode 100644 index 000000000000..24e7a872d641 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy.ui.dialog + +import com.android.internal.logging.uiEventLogger +import com.android.systemui.kosmos.Kosmos +import org.mockito.kotlin.mock + +var Kosmos.modesDialogEventLogger by Kosmos.Fixture { ModesDialogEventLogger(uiEventLogger) } +var Kosmos.mockModesDialogEventLogger by Kosmos.Fixture { mock<ModesDialogEventLogger>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerTest.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerTest.kt new file mode 100644 index 000000000000..5146f77bbcf6 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerTest.kt @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy.ui.dialog + +import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.settingslib.notification.modes.TestModeBuilder +import com.android.systemui.SysuiTestCase +import com.android.systemui.qs.QSModesEvent +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +@TestableLooper.RunWithLooper +class ModesDialogEventLoggerTest : SysuiTestCase() { + + private val uiEventLogger = UiEventLoggerFake() + private val underTest = ModesDialogEventLogger(uiEventLogger) + + @Test + fun testLogModeOn_manual() { + underTest.logModeOn(TestModeBuilder.MANUAL_DND_INACTIVE) + + assertThat(uiEventLogger.numLogs()).isEqualTo(1) + uiEventLogger[0].match(QSModesEvent.QS_MODES_DND_ON, "android") + } + + @Test + fun testLogModeOff_manual() { + underTest.logModeOff(TestModeBuilder.MANUAL_DND_ACTIVE) + + assertThat(uiEventLogger.numLogs()).isEqualTo(1) + uiEventLogger[0].match(QSModesEvent.QS_MODES_DND_OFF, "android") + } + + @Test + fun testLogModeSettings_manual() { + underTest.logModeSettings(TestModeBuilder.MANUAL_DND_ACTIVE) + + assertThat(uiEventLogger.numLogs()).isEqualTo(1) + uiEventLogger[0].match(QSModesEvent.QS_MODES_DND_SETTINGS, "android") + } + + @Test + fun testLogModeOn_automatic() { + underTest.logModeOn(TestModeBuilder().setActive(true).setPackage("pkg1").build()) + + assertThat(uiEventLogger.numLogs()).isEqualTo(1) + uiEventLogger[0].match(QSModesEvent.QS_MODES_MODE_ON, "pkg1") + } + + @Test + fun testLogModeOff_automatic() { + underTest.logModeOff(TestModeBuilder().setActive(false).setPackage("pkg2").build()) + + assertThat(uiEventLogger.numLogs()).isEqualTo(1) + uiEventLogger[0].match(QSModesEvent.QS_MODES_MODE_OFF, "pkg2") + } + + @Test + fun testLogModeSettings_automatic() { + underTest.logModeSettings(TestModeBuilder().setPackage("pkg3").build()) + + assertThat(uiEventLogger.numLogs()).isEqualTo(1) + uiEventLogger[0].match(QSModesEvent.QS_MODES_MODE_SETTINGS, "pkg3") + } + + @Test + fun testLogOpenDurationDialog_manual() { + underTest.logOpenDurationDialog(TestModeBuilder.MANUAL_DND_INACTIVE) + + assertThat(uiEventLogger.numLogs()).isEqualTo(1) + // package not logged for duration dialog as it only applies to manual mode + uiEventLogger[0].match(QSModesEvent.QS_MODES_DURATION_DIALOG, null) + } + + @Test + fun testLogOpenDurationDialog_automatic_doesNotLog() { + underTest.logOpenDurationDialog( + TestModeBuilder().setActive(false).setPackage("mypkg").build() + ) + + // ignore calls to open dialog on something other than the manual rule (shouldn't happen) + assertThat(uiEventLogger.numLogs()).isEqualTo(0) + } + + @Test + fun testLogDialogSettings() { + underTest.logDialogSettings() + assertThat(uiEventLogger.numLogs()).isEqualTo(1) + uiEventLogger[0].match(QSModesEvent.QS_MODES_SETTINGS, null) + } + + private fun UiEventLoggerFake.FakeUiEvent.match(event: QSModesEvent, modePackage: String?) { + assertThat(eventId).isEqualTo(event.id) + assertThat(packageName).isEqualTo(modePackage) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelKosmos.kt index 00020f8bb391..3571a737704b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelKosmos.kt @@ -21,6 +21,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testDispatcher import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor import com.android.systemui.statusbar.policy.ui.dialog.modesDialogDelegate +import com.android.systemui.statusbar.policy.ui.dialog.modesDialogEventLogger import javax.inject.Provider val Kosmos.modesDialogViewModel: ModesDialogViewModel by @@ -30,5 +31,6 @@ val Kosmos.modesDialogViewModel: ModesDialogViewModel by zenModeInteractor, testDispatcher, Provider { modesDialogDelegate }.get(), + modesDialogEventLogger, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt index 295e150e00d8..2e1ecfd3666e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt @@ -219,7 +219,7 @@ inline fun <reified T : Any> kotlinArgumentCaptor(): KotlinArgumentCaptor<T> = * * NOTE: this uses the KotlinArgumentCaptor to avoid the NullPointerException. */ -@Deprecated("Replace with mockito-kotlin", level = WARNING) +// TODO(359670968): rewrite this to use mockito-kotlin inline fun <reified T : Any> withArgCaptor(block: KotlinArgumentCaptor<T>.() -> Unit): T = kotlinArgumentCaptor<T>().apply { block() }.value diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/OWNERS b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/OWNERS new file mode 100644 index 000000000000..1f07df9f1e8e --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/OWNERS @@ -0,0 +1 @@ +include /packages/SystemUI/src/com/android/systemui/volume/OWNERS
\ No newline at end of file diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt index 0a617d17b033..a4719e5a2492 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt @@ -18,7 +18,6 @@ package com.android.systemui.volume.data.repository import com.android.settingslib.volume.data.repository.AudioSharingRepository import com.android.settingslib.volume.data.repository.GroupIdToVolumes -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -30,7 +29,7 @@ class FakeAudioSharingRepository : AudioSharingRepository { MutableStateFlow(TEST_GROUP_ID_INVALID) private val mutableVolumeMap: MutableStateFlow<GroupIdToVolumes> = MutableStateFlow(emptyMap()) - override val inAudioSharing: Flow<Boolean> = mutableInAudioSharing + override val inAudioSharing: StateFlow<Boolean> = mutableInAudioSharing override val primaryGroupId: StateFlow<Int> = mutablePrimaryGroupId override val secondaryGroupId: StateFlow<Int> = mutableSecondaryGroupId override val volumeMap: StateFlow<GroupIdToVolumes> = mutableVolumeMap diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt index e2d414e23abd..3ac565aeacf9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt @@ -22,7 +22,6 @@ import com.android.systemui.bluetooth.localBluetoothManager import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.volume.data.repository.audioRepository -import com.android.systemui.volume.data.repository.audioSharingRepository import com.android.systemui.volume.mediaOutputInteractor val Kosmos.audioOutputInteractor by @@ -37,6 +36,5 @@ val Kosmos.audioOutputInteractor by bluetoothAdapter, deviceIconInteractor, mediaOutputInteractor, - audioSharingRepository, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt index 9f11822adc0c..63a132565177 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt @@ -20,6 +20,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.volume.domain.interactor.audioModeInteractor import com.android.systemui.volume.domain.interactor.audioOutputInteractor +import com.android.systemui.volume.domain.interactor.audioSharingInteractor import com.android.systemui.volume.mediaDeviceSessionInteractor import com.android.systemui.volume.mediaOutputInteractor @@ -31,5 +32,6 @@ val Kosmos.mediaOutputComponentInteractor by audioOutputInteractor, audioModeInteractor, mediaOutputInteractor, + audioSharingInteractor, ) } diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java index 2c4bc7cb0d47..531fa4546bf9 100644 --- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java +++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java @@ -78,8 +78,8 @@ import android.util.Range; import android.util.Size; import android.view.Surface; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; +import android.annotation.NonNull; +import android.annotation.Nullable; import androidx.camera.extensions.impl.AutoImageCaptureExtenderImpl; import androidx.camera.extensions.impl.AutoPreviewExtenderImpl; import androidx.camera.extensions.impl.BeautyImageCaptureExtenderImpl; diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java index b052d23971ab..5c6f99a3e5a9 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java @@ -16,8 +16,6 @@ package com.android.server.accessibility.magnification; -import static android.view.InputDevice.SOURCE_MOUSE; -import static android.view.InputDevice.SOURCE_STYLUS; import static android.view.InputDevice.SOURCE_TOUCHSCREEN; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; @@ -342,8 +340,12 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH cancelFling(); } handleTouchEventWith(mCurrentState, event, rawEvent, policyFlags); - } else if (Flags.enableMagnificationFollowsMouse() - && (event.getSource() == SOURCE_MOUSE || event.getSource() == SOURCE_STYLUS)) { + } + } + + @Override + void handleMouseOrStylusEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + if (Flags.enableMagnificationFollowsMouse()) { if (mFullScreenMagnificationController.isActivated(mDisplayId)) { // TODO(b/354696546): Allow mouse/stylus to activate whichever display they are // over, rather than only interacting with the current display. @@ -351,8 +353,6 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH // Send through the mouse/stylus event handler. mMouseEventHandler.onEvent(event, mDisplayId); } - // Dispatch to normal event handling flow. - dispatchTransformedEvent(event, rawEvent, policyFlags); } } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java index 08411c22b943..446123f07f64 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java @@ -127,49 +127,41 @@ public abstract class MagnificationGestureHandler extends BaseEventStreamTransfo if (DEBUG_EVENT_STREAM) { storeEventInto(mDebugInputEventHistory, event); } - if (shouldDispatchTransformedEvent(event)) { - dispatchTransformedEvent(event, rawEvent, policyFlags); - } else { - onMotionEventInternal(event, rawEvent, policyFlags); - - final int action = event.getAction(); - if (action == MotionEvent.ACTION_DOWN) { - mCallback.onTouchInteractionStart(mDisplayId, getMode()); - } else if (action == ACTION_UP || action == ACTION_CANCEL) { - mCallback.onTouchInteractionEnd(mDisplayId, getMode()); + switch (event.getSource()) { + case SOURCE_TOUCHSCREEN: { + if (magnificationShortcutExists()) { + // Observe touchscreen events while magnification activation is detected. + onMotionEventInternal(event, rawEvent, policyFlags); + + final int action = event.getAction(); + if (action == MotionEvent.ACTION_DOWN) { + mCallback.onTouchInteractionStart(mDisplayId, getMode()); + } else if (action == ACTION_UP || action == ACTION_CANCEL) { + mCallback.onTouchInteractionEnd(mDisplayId, getMode()); + } + // Return early: Do not dispatch event through normal eventing + // flow, it has been fully consumed by the magnifier. + return; + } + } break; + case SOURCE_MOUSE: + case SOURCE_STYLUS: { + if (magnificationShortcutExists() && Flags.enableMagnificationFollowsMouse()) { + handleMouseOrStylusEvent(event, rawEvent, policyFlags); + } } + break; + default: + break; } + // Dispatch event through normal eventing flow. + dispatchTransformedEvent(event, rawEvent, policyFlags); } - /** - * Some touchscreen, mouse and stylus events may modify magnifier state. Checks for whether the - * event should not be dispatched to the magnifier. - * - * @param event The event to check. - * @return `true` if the event should be sent through the normal event flow or `false` if it - * should be observed by magnifier. - */ - private boolean shouldDispatchTransformedEvent(MotionEvent event) { - if (event.getSource() == SOURCE_TOUCHSCREEN) { - if (mDetectSingleFingerTripleTap - || mDetectTwoFingerTripleTap - || mDetectShortcutTrigger) { - // Observe touchscreen events while magnification activation is detected. - return false; - } - } - if (Flags.enableMagnificationFollowsMouse()) { - if (event.isFromSource(SOURCE_MOUSE) || event.isFromSource(SOURCE_STYLUS)) { - // Note that mouse events include other mouse-like pointing devices - // such as touchpads and pointing sticks. - // Observe any mouse or stylus movement. - // We observe all movement to ensure that events continue to come in order, - // even though only some movement types actually move the viewport. - return false; - } - } - // Magnification dispatches (ignores) all other events - return true; + private boolean magnificationShortcutExists() { + return (mDetectSingleFingerTripleTap + || mDetectTwoFingerTripleTap + || mDetectShortcutTrigger); } final void dispatchTransformedEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { @@ -202,6 +194,13 @@ public abstract class MagnificationGestureHandler extends BaseEventStreamTransfo abstract void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags); /** + * Called when this MagnificationGestureHandler should handle a mouse or stylus motion event, + * but not re-dispatch it when completed. + */ + abstract void handleMouseOrStylusEvent( + MotionEvent event, MotionEvent rawEvent, int policyFlags); + + /** * Called when the shortcut target is magnification. */ public void notifyShortcutTriggered() { diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java index 1818cddbcf4c..a84140419166 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java @@ -147,9 +147,13 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl } @Override + void handleMouseOrStylusEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + // Window Magnification viewport doesn't move with mouse events (yet). + } + + @Override void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) { if (event.getSource() != SOURCE_TOUCHSCREEN) { - // Window Magnification viewport doesn't move with mouse events (yet). return; } // To keep InputEventConsistencyVerifiers within GestureDetectors happy. diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java index 930af5e7f056..5044e93b293d 100644 --- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java +++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java @@ -71,6 +71,7 @@ import android.util.ArraySet; import android.util.Slog; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; +import android.view.autofill.AutofillValue; import com.android.internal.util.FrameworkStatsLog; @@ -244,10 +245,18 @@ public final class PresentationStatsEventLogger { Slog.e(TAG, "Failed to start new event because already have active event."); return; } + Slog.d(TAG, "Started new PresentationStatsEvent"); mEventInternal = Optional.of(new PresentationStatsEventInternal()); } /** + * Test use only, returns a copy of the events object + */ + Optional<PresentationStatsEventInternal> getInternalEvent() { + return mEventInternal; + } + + /** * Set request_id */ public void maybeSetRequestId(int requestId) { @@ -339,10 +348,16 @@ public final class PresentationStatsEventLogger { }); } - public void maybeSetCountShown(int datasets) { + /** + * This is called when a dataset is shown to the user. Will set the count shown, + * related timestamps and presentation reason. + */ + public void logWhenDatasetShown(int datasets) { mEventInternal.ifPresent( event -> { + maybeSetSuggestionPresentedTimestampMs(); event.mCountShown = datasets; + event.mNoPresentationReason = NOT_SHOWN_REASON_ANY_SHOWN; }); } @@ -405,7 +420,12 @@ public final class PresentationStatsEventLogger { public void maybeSetDisplayPresentationType(@UiType int uiType) { mEventInternal.ifPresent(event -> { - event.mDisplayPresentationType = getDisplayPresentationType(uiType); + // There are cases in which another UI type will show up after selects a dataset + // such as with Inline after Fill Dialog. Set as the first presentation type only. + if (event.mDisplayPresentationType + == AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__UNKNOWN_AUTOFILL_DISPLAY_PRESENTATION_TYPE) { + event.mDisplayPresentationType = getDisplayPresentationType(uiType); + } }); } @@ -430,9 +450,12 @@ public final class PresentationStatsEventLogger { } public void maybeSetSuggestionSentTimestampMs(int timestamp) { - mEventInternal.ifPresent(event -> { - event.mSuggestionSentTimestampMs = timestamp; - }); + mEventInternal.ifPresent( + event -> { + if (event.mSuggestionSentTimestampMs == DEFAULT_VALUE_INT) { + event.mSuggestionSentTimestampMs = timestamp; + } + }); } public void maybeSetSuggestionSentTimestampMs() { @@ -481,8 +504,6 @@ public final class PresentationStatsEventLogger { public void maybeSetInlinePresentationAndSuggestionHostUid(Context context, int userId) { mEventInternal.ifPresent(event -> { - event.mDisplayPresentationType = - AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__INLINE; String imeString = Settings.Secure.getStringForUser(context.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD, userId); if (TextUtils.isEmpty(imeString)) { @@ -602,40 +623,56 @@ public final class PresentationStatsEventLogger { } /** + * Sets the field length whenever the text changes. Will keep track of the first + * and last modification lengths. + */ + public void updateTextFieldLength(AutofillValue value) { + mEventInternal.ifPresent(event -> { + if (value == null || !value.isText()) { + return; + } + + int length = value.getTextValue().length(); + + if (event.mFieldFirstLength == DEFAULT_VALUE_INT) { + event.mFieldFirstLength = length; + } + event.mFieldLastLength = length; + }); + } + + /** * Set various timestamps whenever the ViewState is modified * * <p>If the ViewState contains ViewState.STATE_AUTOFILLED, sets field_autofilled_timestamp_ms * else, set field_first_modified_timestamp_ms (if unset) and field_last_modified_timestamp_ms */ - public void onFieldTextUpdated(ViewState state, int length) { + public void onFieldTextUpdated(ViewState state, AutofillValue value) { mEventInternal.ifPresent(event -> { - int timestamp = getElapsedTime(); - // Focused id should be set before this is called - if (state == null || state.id == null || state.id.getViewId() != event.mFocusedId) { - // if these don't match, the currently field different than before - Slog.w( - TAG, - "Bad view state for: " + event.mFocusedId); - return; - } + int timestamp = getElapsedTime(); + // Focused id should be set before this is called + if (state == null || state.id == null || state.id.getViewId() != event.mFocusedId) { + // if these don't match, the currently field different than before + Slog.w( + TAG, + "Bad view state for: " + event.mFocusedId + ", state: " + state); + return; + } - // Text changed because filling into form, just log Autofill timestamp - if ((state.getState() & ViewState.STATE_AUTOFILLED) != 0) { - event.mAutofilledTimestampMs = timestamp; - return; - } + updateTextFieldLength(value); - // Set length variables - if (event.mFieldFirstLength == DEFAULT_VALUE_INT) { - event.mFieldFirstLength = length; - } - event.mFieldLastLength = length; + // Text changed because filling into form, just log Autofill timestamp + if ((state.getState() & ViewState.STATE_AUTOFILLED) != 0) { + event.mAutofilledTimestampMs = timestamp; + return; + } - // Set timestamp variables - if (event.mFieldModifiedFirstTimestampMs == DEFAULT_VALUE_INT) { - event.mFieldModifiedFirstTimestampMs = timestamp; - } - event.mFieldModifiedLastTimestampMs = timestamp; + + // Set timestamp variables + if (event.mFieldModifiedFirstTimestampMs == DEFAULT_VALUE_INT) { + event.mFieldModifiedFirstTimestampMs = timestamp; + } + event.mFieldModifiedLastTimestampMs = timestamp; }); } @@ -796,7 +833,10 @@ public final class PresentationStatsEventLogger { }); } - public void logAndEndEvent() { + /** + * Finish and log the event. + */ + public void logAndEndEvent(String caller) { if (!mEventInternal.isPresent()) { Slog.w(TAG, "Shouldn't be logging AutofillPresentationEventReported again for same " + "event"); @@ -804,7 +844,8 @@ public final class PresentationStatsEventLogger { } PresentationStatsEventInternal event = mEventInternal.get(); if (sVerbose) { - Slog.v(TAG, "Log AutofillPresentationEventReported:" + Slog.v(TAG, "(" + caller + ") " + + "Log AutofillPresentationEventReported:" + " requestId=" + event.mRequestId + " sessionId=" + mSessionId + " mNoPresentationEventReason=" + event.mNoPresentationReason @@ -926,7 +967,7 @@ public final class PresentationStatsEventLogger { mEventInternal = Optional.empty(); } - private static final class PresentationStatsEventInternal { + static final class PresentationStatsEventInternal { int mRequestId; @NotShownReason int mNoPresentationReason = NOT_SHOWN_REASON_UNKNOWN; boolean mIsDatasetAvailable; diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 6dea8b052173..c75fd0b7e025 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -28,7 +28,6 @@ import static android.service.autofill.Dataset.PICK_REASON_PROVIDER_DETECTION_ON import static android.service.autofill.Dataset.PICK_REASON_PROVIDER_DETECTION_PREFERRED_WITH_PCC; import static android.service.autofill.Dataset.PICK_REASON_UNKNOWN; import static android.service.autofill.FillEventHistory.Event.UI_TYPE_CREDMAN_BOTTOM_SHEET; -import static android.service.autofill.FillEventHistory.Event.UI_TYPE_DIALOG; import static android.service.autofill.FillEventHistory.Event.UI_TYPE_INLINE; import static android.service.autofill.FillEventHistory.Event.UI_TYPE_UNKNOWN; import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST; @@ -67,10 +66,10 @@ import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATU import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_SUCCESS; import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_TIMEOUT; import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_TRANSACTION_TOO_LARGE; +import static com.android.server.autofill.Helper.SaveInfoStats; import static com.android.server.autofill.Helper.containsCharsInOrder; import static com.android.server.autofill.Helper.createSanitizers; import static com.android.server.autofill.Helper.getNumericValue; -import static com.android.server.autofill.Helper.SaveInfoStats; import static com.android.server.autofill.Helper.sDebug; import static com.android.server.autofill.Helper.sVerbose; import static com.android.server.autofill.Helper.toArray; @@ -78,6 +77,7 @@ import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTIC import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTICATION_RESULT_SUCCESS; import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTICATION_TYPE_DATASET_AUTHENTICATION; import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTICATION_TYPE_FULL_AUTHENTICATION; +import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_ANY_SHOWN; import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_NO_FOCUS; import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_REQUEST_FAILED; import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_REQUEST_TIMEOUT; @@ -1288,11 +1288,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * Clears the existing response for the partition, reads a new structure, and then requests a * new fill response from the fill service. * - * <p> Also asks the IME to make an inline suggestions request if it's enabled. + * <p>Also asks the IME to make an inline suggestions request if it's enabled. */ @GuardedBy("mLock") - private void requestNewFillResponseLocked(@NonNull ViewState viewState, int newState, - int flags) { + private Optional<Integer> requestNewFillResponseLocked( + @NonNull ViewState viewState, int newState, int flags) { boolean isSecondary = shouldRequestSecondaryProvider(flags); final FillResponse existingResponse = isSecondary ? viewState.getSecondaryResponse() : viewState.getResponse(); @@ -1333,7 +1333,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mFillRequestEventLogger.maybeSetIsAugmented(true); mFillRequestEventLogger.logAndEndEvent(); triggerAugmentedAutofillLocked(flags); - return; + return Optional.empty(); } viewState.setState(newState); @@ -1353,11 +1353,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState + ", flags=" + flags); } boolean isCredmanRequested = (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0; - mPresentationStatsEventLogger.maybeSetRequestId(requestId); - mPresentationStatsEventLogger.maybeSetIsCredentialRequest(isCredmanRequested); - mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId( - mFieldClassificationIdSnapshot); - mPresentationStatsEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid()); mFillRequestEventLogger.maybeSetRequestId(requestId); mFillRequestEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid()); mSaveEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid()); @@ -1417,6 +1412,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // Now request the assist structure data. requestAssistStructureLocked(requestId, flags); + + return Optional.of(requestId); } private boolean isRequestSupportFillDialog(int flags) { @@ -1662,6 +1659,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final LogMaker requestLog; synchronized (mLock) { + mPresentationStatsEventLogger.maybeSetRequestId(requestId); // Start a new FillResponse logger for the success case. mFillResponseEventLogger.startLogForNewResponse(); mFillResponseEventLogger.maybeSetRequestId(requestId); @@ -2419,7 +2417,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState NOT_SHOWN_REASON_REQUEST_FAILED); mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_FAILURE); } - mPresentationStatsEventLogger.logAndEndEvent(); + mPresentationStatsEventLogger.logAndEndEvent("fill request failure"); mFillResponseEventLogger.maybeSetLatencyResponseProcessingMillis(); mFillResponseEventLogger.logAndEndEvent(); } @@ -2642,6 +2640,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState public void onShown(int uiType, int numDatasetsShown) { synchronized (mLock) { mPresentationStatsEventLogger.maybeSetDisplayPresentationType(uiType); + mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( + NOT_SHOWN_REASON_ANY_SHOWN); if (uiType == UI_TYPE_INLINE) { // Inline Suggestions are inflated one at a time @@ -2657,7 +2657,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } mLoggedInlineDatasetShown = true; } else { - mPresentationStatsEventLogger.maybeSetCountShown(numDatasetsShown); + mPresentationStatsEventLogger.logWhenDatasetShown(numDatasetsShown); // Explicitly sets maybeSetSuggestionPresentedTimestampMs mPresentationStatsEventLogger.maybeSetSuggestionPresentedTimestampMs(); mService.logDatasetShown(this.id, mClientState, uiType); @@ -2800,7 +2800,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (mCurrentViewId == null) { return; } + mPresentationStatsEventLogger.logAndEndEvent("fallback from fill dialog"); + startNewEventForPresentationStatsEventLogger(); final ViewState currentView = mViewStates.get(mCurrentViewId); + logPresentationStatsOnViewEnteredLocked(currentView.getResponse(), false); currentView.maybeCallOnFillReady(mFlags); } } @@ -2850,7 +2853,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (requestId == AUGMENTED_AUTOFILL_REQUEST_ID) { setAuthenticationResultForAugmentedAutofillLocked(data, authenticationId); // Augmented autofill is not logged. - mPresentationStatsEventLogger.logAndEndEvent(); + mPresentationStatsEventLogger.logAndEndEvent("authentication - augmented"); return; } if (mResponses == null) { @@ -2859,7 +2862,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.w(TAG, "setAuthenticationResultLocked(" + authenticationId + "): no responses"); mPresentationStatsEventLogger.maybeSetAuthenticationResult( AUTHENTICATION_RESULT_FAILURE); - mPresentationStatsEventLogger.logAndEndEvent(); + mPresentationStatsEventLogger.logAndEndEvent("authentication - no response"); removeFromService(); return; } @@ -2870,7 +2873,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.w(TAG, "no authenticated response"); mPresentationStatsEventLogger.maybeSetAuthenticationResult( AUTHENTICATION_RESULT_FAILURE); - mPresentationStatsEventLogger.logAndEndEvent(); + mPresentationStatsEventLogger.logAndEndEvent("authentication - bad response"); removeFromService(); return; } @@ -2885,7 +2888,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.w(TAG, "no dataset with index " + datasetIdx + " on fill response"); mPresentationStatsEventLogger.maybeSetAuthenticationResult( AUTHENTICATION_RESULT_FAILURE); - mPresentationStatsEventLogger.logAndEndEvent(); + mPresentationStatsEventLogger.logAndEndEvent("authentication - no datasets"); removeFromService(); return; } @@ -3330,7 +3333,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( PresentationStatsEventLogger.getNoPresentationEventReason(commitReason)); - mPresentationStatsEventLogger.logAndEndEvent(); + mPresentationStatsEventLogger.logAndEndEvent("Context committed"); final int flags = lastResponse.getFlags(); if ((flags & FillResponse.FLAG_TRACK_CONTEXT_COMMITED) == 0) { @@ -4299,6 +4302,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * Starts (if necessary) a new fill request upon entering a view. * * <p>A new request will be started in 2 scenarios: + * * <ol> * <li>If the user manually requested autofill. * <li>If the view is part of a new partition. @@ -4307,18 +4311,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * @param id The id of the view that is entered. * @param viewState The view that is entered. * @param flags The flag that was passed by the AutofillManager. - * * @return {@code true} if a new fill response is requested. */ @GuardedBy("mLock") - private boolean requestNewFillResponseOnViewEnteredIfNecessaryLocked(@NonNull AutofillId id, - @NonNull ViewState viewState, int flags) { + private Optional<Integer> requestNewFillResponseOnViewEnteredIfNecessaryLocked( + @NonNull AutofillId id, @NonNull ViewState viewState, int flags) { // Force new response for manual request if ((flags & FLAG_MANUAL_REQUEST) != 0) { mSessionFlags.mAugmentedAutofillOnly = false; if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags); - requestNewFillResponseLocked(viewState, ViewState.STATE_RESTARTED_SESSION, flags); - return true; + return requestNewFillResponseLocked( + viewState, ViewState.STATE_RESTARTED_SESSION, flags); } // If it's not, then check if it should start a partition. @@ -4331,15 +4334,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // Sometimes activity contain IMPORTANT_FOR_AUTOFILL_NO fields which marks session as // augmentedOnly, but other fields are still fillable by standard autofill. mSessionFlags.mAugmentedAutofillOnly = false; - requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_PARTITION, flags); - return true; + return requestNewFillResponseLocked( + viewState, ViewState.STATE_STARTED_PARTITION, flags); } if (sVerbose) { Slog.v(TAG, "Not starting new partition for view " + id + ": " + viewState.getStateAsString()); } - return false; + return Optional.empty(); } /** @@ -4428,31 +4431,32 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int action, int flags) { if (mDestroyed) { - Slog.w(TAG, "Call to Session#updateLocked() rejected - session: " - + id + " destroyed"); + Slog.w(TAG, "updateLocked(" + id + "): rejected - session: destroyed"); return; } if (action == ACTION_RESPONSE_EXPIRED) { mSessionFlags.mExpiredResponse = true; if (sDebug) { - Slog.d(TAG, "Set the response has expired."); + Slog.d(TAG, "updateLocked(" + id + "): Set the response has expired."); } mPresentationStatsEventLogger.maybeSetNoPresentationEventReasonIfNoReasonExists( NOT_SHOWN_REASON_VIEW_CHANGED); - mPresentationStatsEventLogger.logAndEndEvent(); + mPresentationStatsEventLogger.logAndEndEvent("ACTION_RESPONSE_EXPIRED"); return; } id.setSessionId(this.id); - if (sVerbose) { - Slog.v(TAG, "updateLocked(" + this.id + "): id=" + id + ", action=" - + actionAsString(action) + ", flags=" + flags); - } ViewState viewState = mViewStates.get(id); if (sVerbose) { - Slog.v(TAG, "updateLocked(" + this.id + "): mCurrentViewId=" + mCurrentViewId - + ", mExpiredResponse=" + mSessionFlags.mExpiredResponse - + ", viewState=" + viewState); + Slog.v( + TAG, + "updateLocked(" + id + "): " + + "id=" + this.id + + ", action=" + actionAsString(action) + + ", flags=" + flags + + ", mCurrentViewId=" + mCurrentViewId + + ", mExpiredResponse=" + mSessionFlags.mExpiredResponse + + ", viewState=" + viewState); } if (viewState == null) { @@ -4505,14 +4509,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mSessionFlags.mFillDialogDisabled = true; mPreviouslyFillDialogPotentiallyStarted = false; } else { - // Set the default reason for now if the user doesn't trigger any focus event - // on the autofillable view. This can be changed downstream when more - // information is available or session is committed. - mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( - NOT_SHOWN_REASON_NO_FOCUS); mPreviouslyFillDialogPotentiallyStarted = true; } - requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_SESSION, flags); + Optional<Integer> maybeRequestId = + requestNewFillResponseLocked( + viewState, ViewState.STATE_STARTED_SESSION, flags); + if (maybeRequestId.isPresent()) { + mPresentationStatsEventLogger.maybeSetRequestId(maybeRequestId.get()); + } break; case ACTION_VALUE_CHANGED: if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) { @@ -4577,8 +4581,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } boolean isCredmanRequested = (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0; if (shouldRequestSecondaryProvider(flags)) { - if (requestNewFillResponseOnViewEnteredIfNecessaryLocked( - id, viewState, flags)) { + Optional<Integer> maybeRequestIdCred = + requestNewFillResponseOnViewEnteredIfNecessaryLocked( + id, viewState, flags); + if (maybeRequestIdCred.isPresent()) { Slog.v(TAG, "Started a new fill request for secondary provider."); return; } @@ -4622,17 +4628,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mLogViewEntered = true; } - // Previously, fill request will only start whenever a view is entered. - // With Fill Dialog, request starts prior to view getting entered. So, we can't end - // the event at this moment, otherwise we will be wrongly attributing fill dialog - // event as concluded. - if (!wasPreviouslyFillDialog && !isSameViewAgain) { - // TODO(b/319872477): Re-consider this logic below - mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( - NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED); - mPresentationStatsEventLogger.logAndEndEvent(); - } - + // Trigger augmented autofill if applicable if ((flags & FLAG_MANUAL_REQUEST) == 0) { // Not a manual request if (mAugmentedAutofillableIds != null && mAugmentedAutofillableIds.contains( @@ -4658,26 +4654,25 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return; } } - // If previous request was FillDialog request, a logger event was already started - if (!wasPreviouslyFillDialog) { + + Optional<Integer> maybeNewRequestId = + requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags); + + // Previously, fill request will only start whenever a view is entered. + // With Fill Dialog, request starts prior to view getting entered. So, we can't end + // the event at this moment, otherwise we will be wrongly attributing fill dialog + // event as concluded. + if (!wasPreviouslyFillDialog + && (!isSameViewEntered || maybeNewRequestId.isPresent())) { startNewEventForPresentationStatsEventLogger(); - } - if (requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags)) { - // If a new request was issued even if previously it was fill dialog request, - // we should end the log event, and start a new one. However, it leaves us - // susceptible to race condition. But since mPresentationStatsEventLogger is - // lock guarded, we should be safe. - if (wasPreviouslyFillDialog) { - mPresentationStatsEventLogger.logAndEndEvent(); - startNewEventForPresentationStatsEventLogger(); + if (maybeNewRequestId.isPresent()) { + mPresentationStatsEventLogger.maybeSetRequestId(maybeNewRequestId.get()); } - return; } - FillResponse response = viewState.getResponse(); - if (response != null) { - logPresentationStatsOnViewEnteredLocked(response, isCredmanRequested); - } + logPresentationStatsOnViewEnteredLocked( + viewState.getResponse(), isCredmanRequested); + mPresentationStatsEventLogger.updateTextFieldLength(value); if (isSameViewEntered) { setFillDialogDisabledAndStartInput(); @@ -4719,13 +4714,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private void logPresentationStatsOnViewEnteredLocked(FillResponse response, boolean isCredmanRequested) { - mPresentationStatsEventLogger.maybeSetRequestId(response.getRequestId()); mPresentationStatsEventLogger.maybeSetIsCredentialRequest(isCredmanRequested); mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId( mFieldClassificationIdSnapshot); - mPresentationStatsEventLogger.maybeSetAvailableCount( - response.getDatasets(), mCurrentViewId); + mPresentationStatsEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid()); mPresentationStatsEventLogger.maybeSetFocusedId(mCurrentViewId); + + if (response != null) { + mPresentationStatsEventLogger.maybeSetRequestId(response.getRequestId()); + mPresentationStatsEventLogger.maybeSetAvailableCount( + response.getDatasets(), mCurrentViewId); + } } @GuardedBy("mLock") @@ -4796,8 +4795,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState viewState.setCurrentValue(value); final String filterText = textValue; - final AutofillValue filledValue = viewState.getAutofilledValue(); + + if (textValue != null) { + mPresentationStatsEventLogger.onFieldTextUpdated(viewState, value); + } + if (filledValue != null) { if (filledValue.equals(value)) { // When the update is caused by autofilling the view, just update the @@ -4821,9 +4824,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState currentView.maybeCallOnFillReady(flags); } } - if (textValue != null) { - mPresentationStatsEventLogger.onFieldTextUpdated(viewState, textValue.length()); - } if (viewState.id.equals(this.mCurrentViewId) && (viewState.getState() & ViewState.STATE_INLINE_SHOWN) != 0) { @@ -4888,7 +4888,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mSaveEventLogger.logAndEndEvent(); mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( NOT_SHOWN_REASON_SESSION_COMMITTED_PREMATURELY); - mPresentationStatsEventLogger.logAndEndEvent(); + mPresentationStatsEventLogger.logAndEndEvent("on fill ready"); return; } } @@ -4920,7 +4920,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState synchronized (mLock) { final ViewState currentView = mViewStates.get(mCurrentViewId); currentView.setState(ViewState.STATE_FILL_DIALOG_SHOWN); - mPresentationStatsEventLogger.maybeSetDisplayPresentationType(UI_TYPE_DIALOG); } // Just show fill dialog once, so disabled after shown. // Note: Cannot disable before requestShowFillDialog() because the method @@ -6086,6 +6085,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private void startNewEventForPresentationStatsEventLogger() { synchronized (mLock) { mPresentationStatsEventLogger.startNewEvent(); + // Set the default reason for now if the user doesn't trigger any focus event + // on the autofillable view. This can be changed downstream when more + // information is available or session is committed. + mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( + NOT_SHOWN_REASON_NO_FOCUS); mPresentationStatsEventLogger.maybeSetDetectionPreference( getDetectionPreferenceForLogging()); mPresentationStatsEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid()); @@ -6724,7 +6728,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState SystemClock.elapsedRealtime() - mStartTime); mFillRequestEventLogger.logAndEndEvent(); mFillResponseEventLogger.logAndEndEvent(); - mPresentationStatsEventLogger.logAndEndEvent(); + mPresentationStatsEventLogger.logAndEndEvent("log all events"); mSaveEventLogger.logAndEndEvent(); mSessionCommittedEventLogger.logAndEndEvent(); } diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java index a10039f9bf6c..2446a6dfee89 100644 --- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java +++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java @@ -461,9 +461,9 @@ public final class AutoFillUI { } @Override - public void onShown() { + public void onShown(int datasetsShown) { if (mCallback != null) { - mCallback.onShown(UI_TYPE_DIALOG, response.getDatasets().size()); + mCallback.onShown(UI_TYPE_DIALOG, datasetsShown); } } diff --git a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java index 5a71b895a57c..c7b6be60dd3e 100644 --- a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java @@ -85,7 +85,7 @@ final class DialogFillUi { void onDatasetPicked(@NonNull Dataset dataset); void onDismissed(); void onCanceled(); - void onShown(); + void onShown(int datasetsShown); void startIntentSender(IntentSender intentSender); } @@ -155,7 +155,8 @@ final class DialogFillUi { mDialog.setContentView(decor); setDialogParamsAsBottomSheet(); mDialog.setOnCancelListener((d) -> mCallback.onCanceled()); - mDialog.setOnShowListener((d) -> mCallback.onShown()); + int datasetsShown = (mAdapter != null) ? mAdapter.getCount() : 0; + mDialog.setOnShowListener((d) -> mCallback.onShown(datasetsShown)); show(); } diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS index a2bbff026b95..37dddc61a976 100644 --- a/services/core/java/com/android/server/OWNERS +++ b/services/core/java/com/android/server/OWNERS @@ -10,9 +10,6 @@ per-file DisplayThread.java = michaelwr@google.com, ogunwale@google.com # Zram writeback per-file ZramWriteback.java = minchan@google.com, rajekumar@google.com -# Userspace reboot -per-file UserspaceRebootLogger.java = ioffe@google.com, dvander@google.com - # ServiceWatcher per-file ServiceWatcher.java = sooniln@google.com diff --git a/services/core/java/com/android/server/UserspaceRebootLogger.java b/services/core/java/com/android/server/UserspaceRebootLogger.java deleted file mode 100644 index 89327b50883c..000000000000 --- a/services/core/java/com/android/server/UserspaceRebootLogger.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server; - -import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED; -import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT; -import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERSPACE_REBOOT_WATCHDOG_TRIGGERED; -import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__OUTCOME_UNKNOWN; -import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS; -import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__LOCKED; -import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__UNLOCKED; - -import android.os.PowerManager; -import android.os.SystemClock; -import android.os.SystemProperties; -import android.text.TextUtils; -import android.util.Slog; - -import com.android.internal.util.FrameworkStatsLog; - -import java.util.concurrent.Executor; - -/** - * Utility class to help abstract logging {@code UserspaceRebootReported} atom. - */ -public final class UserspaceRebootLogger { - - private static final String TAG = "UserspaceRebootLogger"; - - private static final String USERSPACE_REBOOT_SHOULD_LOG_PROPERTY = - "persist.sys.userspace_reboot.log.should_log"; - private static final String USERSPACE_REBOOT_LAST_STARTED_PROPERTY = - "sys.userspace_reboot.log.last_started"; - private static final String USERSPACE_REBOOT_LAST_FINISHED_PROPERTY = - "sys.userspace_reboot.log.last_finished"; - private static final String LAST_BOOT_REASON_PROPERTY = "sys.boot.reason.last"; - - private UserspaceRebootLogger() {} - - /** - * Modifies internal state to note that {@code UserspaceRebootReported} atom needs to be - * logged on the next successful boot. - * - * <p>This call should only be made on devices supporting userspace reboot. - */ - public static void noteUserspaceRebootWasRequested() { - if (!PowerManager.isRebootingUserspaceSupportedImpl()) { - Slog.wtf(TAG, "noteUserspaceRebootWasRequested: Userspace reboot is not supported."); - return; - } - - SystemProperties.set(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, "1"); - SystemProperties.set(USERSPACE_REBOOT_LAST_STARTED_PROPERTY, - String.valueOf(SystemClock.elapsedRealtime())); - } - - /** - * Updates internal state on boot after successful userspace reboot. - * - * <p>Should be called right before framework sets {@code sys.boot_completed} property. - * - * <p>This call should only be made on devices supporting userspace reboot. - */ - public static void noteUserspaceRebootSuccess() { - if (!PowerManager.isRebootingUserspaceSupportedImpl()) { - Slog.wtf(TAG, "noteUserspaceRebootSuccess: Userspace reboot is not supported."); - return; - } - - SystemProperties.set(USERSPACE_REBOOT_LAST_FINISHED_PROPERTY, - String.valueOf(SystemClock.elapsedRealtime())); - } - - /** - * Returns {@code true} if {@code UserspaceRebootReported} atom should be logged. - * - * <p>On devices that do not support userspace reboot this method will always return {@code - * false}. - */ - public static boolean shouldLogUserspaceRebootEvent() { - if (!PowerManager.isRebootingUserspaceSupportedImpl()) { - return false; - } - - return SystemProperties.getBoolean(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, false); - } - - /** - * Asynchronously logs {@code UserspaceRebootReported} on the given {@code executor}. - * - * <p>Should be called in the end of {@link - * com.android.server.am.ActivityManagerService#finishBooting()} method, after framework have - * tried to proactivelly unlock storage of the primary user. - * - * <p>This call should only be made on devices supporting userspace reboot. - */ - public static void logEventAsync(boolean userUnlocked, Executor executor) { - if (!PowerManager.isRebootingUserspaceSupportedImpl()) { - Slog.wtf(TAG, "logEventAsync: Userspace reboot is not supported."); - return; - } - - final int outcome = computeOutcome(); - final long durationMillis; - if (outcome == USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS) { - durationMillis = SystemProperties.getLong(USERSPACE_REBOOT_LAST_FINISHED_PROPERTY, 0) - - SystemProperties.getLong(USERSPACE_REBOOT_LAST_STARTED_PROPERTY, 0); - } else { - durationMillis = 0; - } - final int encryptionState = - userUnlocked - ? USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__UNLOCKED - : USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__LOCKED; - executor.execute( - () -> { - Slog.i(TAG, "Logging UserspaceRebootReported atom: { outcome: " + outcome - + " durationMillis: " + durationMillis + " encryptionState: " - + encryptionState + " }"); - FrameworkStatsLog.write(FrameworkStatsLog.USERSPACE_REBOOT_REPORTED, outcome, - durationMillis, encryptionState); - SystemProperties.set(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, ""); - }); - } - - private static int computeOutcome() { - if (SystemProperties.getLong(USERSPACE_REBOOT_LAST_STARTED_PROPERTY, -1) != -1) { - return USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS; - } - String reason = TextUtils.emptyIfNull(SystemProperties.get(LAST_BOOT_REASON_PROPERTY, "")); - if (reason.startsWith("reboot,")) { - reason = reason.substring("reboot".length()); - } - if (reason.startsWith("userspace_failed,watchdog_fork")) { - return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED; - } - if (reason.startsWith("userspace_failed,shutdown_aborted")) { - return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED; - } - if (reason.startsWith("mount_userdata_failed")) { - return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT; - } - if (reason.startsWith("userspace_failed,init_user0")) { - return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT; - } - if (reason.startsWith("userspace_failed,enablefilecrypto")) { - return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT; - } - if (reason.startsWith("userspace_failed,watchdog_triggered")) { - return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERSPACE_REBOOT_WATCHDOG_TRIGGERED; - } - return USERSPACE_REBOOT_REPORTED__OUTCOME__OUTCOME_UNKNOWN; - } -} diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 4a18cb1f5ed8..91b549c9a04b 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -372,7 +372,6 @@ import android.os.storage.StorageManager; import android.provider.DeviceConfig; import android.provider.Settings; import android.server.ServerProtoEnums; -import android.sysprop.InitProperties; import android.system.Os; import android.system.OsConstants; import android.telephony.TelephonyManager; @@ -451,7 +450,6 @@ import com.android.server.SystemConfig; import com.android.server.SystemService; import com.android.server.SystemServiceManager; import com.android.server.ThreadPriorityBooster; -import com.android.server.UserspaceRebootLogger; import com.android.server.Watchdog; import com.android.server.am.ComponentAliasResolver.Resolution; import com.android.server.am.LowMemDetector.MemFactor; @@ -2360,20 +2358,6 @@ public class ActivityManagerService extends IActivityManager.Stub } } - private void maybeLogUserspaceRebootEvent() { - if (!UserspaceRebootLogger.shouldLogUserspaceRebootEvent()) { - return; - } - final int userId = mUserController.getCurrentUserId(); - if (userId != UserHandle.USER_SYSTEM) { - // Only log for user0. - return; - } - // TODO(b/148767783): should we check all profiles under user0? - UserspaceRebootLogger.logEventAsync(StorageManager.isCeStorageUnlocked(userId), - BackgroundThread.getExecutor()); - } - /** * Encapsulates global settings related to hidden API enforcement behaviour, including tracking * the latest value via a content observer. @@ -5323,12 +5307,6 @@ public class ActivityManagerService extends IActivityManager.Stub // Start looking for apps that are abusing wake locks. Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_POWER_USE_MSG); mHandler.sendMessageDelayed(nmsg, mConstants.POWER_CHECK_INTERVAL); - // Check if we are performing userspace reboot before setting sys.boot_completed to - // avoid race with init reseting sys.init.userspace_reboot.in_progress once sys - // .boot_completed is 1. - if (InitProperties.userspace_reboot_in_progress().orElse(false)) { - UserspaceRebootLogger.noteUserspaceRebootSuccess(); - } // Tell anyone interested that we are done booting! SystemProperties.set("sys.boot_completed", "1"); SystemProperties.set("dev.bootcomplete", "1"); @@ -5352,7 +5330,6 @@ public class ActivityManagerService extends IActivityManager.Stub }, mConstants.FULL_PSS_MIN_INTERVAL); } }); - maybeLogUserspaceRebootEvent(); mUserController.scheduleStartProfiles(); } // UART is on if init's console service is running, send a warning notification. diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index f61bd6040658..154b52b86e73 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -1413,6 +1413,22 @@ public class AppOpsService extends IAppOpsService.Stub { } public void uidRemoved(int uid) { + if (Flags.dontRemoveExistingUidStates()) { + // b/358365471 If apps sharing UID are installed on multiple users and only one of + // them is installed for a single user while keeping the others we observe this + // subroutine get invoked incorrectly since the UID still exists. + final long token = Binder.clearCallingIdentity(); + try { + String uidName = getPackageManagerInternal().getNameForUid(uid); + if (uidName != null) { + Slog.e(TAG, "Tried to remove existing UID. uid: " + uid + " name: " + uidName); + return; + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + synchronized (this) { if (mUidStates.indexOfKey(uid) >= 0) { mUidStates.get(uid).clear(); diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java index 0bd22f3da67f..f0da67bd9f9a 100644 --- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java +++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java @@ -77,13 +77,15 @@ class PreAuthInfo { private final int mBiometricStrengthRequested; private final BiometricCameraManager mBiometricCameraManager; private final boolean mOnlyMandatoryBiometricsRequested; + private final boolean mIsMandatoryBiometricsAuthentication; private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested, boolean credentialRequested, List<BiometricSensor> eligibleSensors, List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable, PromptInfo promptInfo, int userId, Context context, BiometricCameraManager biometricCameraManager, - boolean isOnlyMandatoryBiometricsRequested) { + boolean isOnlyMandatoryBiometricsRequested, + boolean isMandatoryBiometricsAuthentication) { mBiometricRequested = biometricRequested; mBiometricStrengthRequested = biometricStrengthRequested; mBiometricCameraManager = biometricCameraManager; @@ -97,6 +99,7 @@ class PreAuthInfo { this.userId = userId; this.context = context; this.mOnlyMandatoryBiometricsRequested = isOnlyMandatoryBiometricsRequested; + this.mIsMandatoryBiometricsAuthentication = isMandatoryBiometricsAuthentication; } static PreAuthInfo create(ITrustManager trustManager, @@ -110,10 +113,12 @@ class PreAuthInfo { final boolean isOnlyMandatoryBiometricsRequested = promptInfo.getAuthenticators() == BiometricManager.Authenticators.MANDATORY_BIOMETRICS; + boolean isMandatoryBiometricsAuthentication = false; if (dropCredentialFallback(promptInfo.getAuthenticators(), settingObserver.getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser( userId), trustManager)) { + isMandatoryBiometricsAuthentication = true; promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG); promptInfo.setNegativeButtonText(context.getString(R.string.cancel)); } @@ -166,7 +171,8 @@ class PreAuthInfo { return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested, eligibleSensors, ineligibleSensors, credentialAvailable, promptInfo, userId, - context, biometricCameraManager, isOnlyMandatoryBiometricsRequested); + context, biometricCameraManager, isOnlyMandatoryBiometricsRequested, + isMandatoryBiometricsAuthentication); } private static boolean dropCredentialFallback(int authenticators, @@ -387,25 +393,6 @@ class PreAuthInfo { status = CREDENTIAL_NOT_ENROLLED; } } - } else if (Flags.mandatoryBiometrics() && mOnlyMandatoryBiometricsRequested) { - if (!eligibleSensors.isEmpty()) { - for (BiometricSensor sensor : eligibleSensors) { - modality |= sensor.modality; - } - - if (modality == TYPE_FACE && cameraPrivacyEnabled) { - // If the only modality requested is face, credential is unavailable, - // and the face sensor privacy is enabled then return - // BIOMETRIC_SENSOR_PRIVACY_ENABLED. - // - // Note: This sensor will not be eligible for calls to authenticate. - status = BIOMETRIC_SENSOR_PRIVACY_ENABLED; - } else { - status = AUTHENTICATOR_OK; - } - } else { - status = MANDATORY_BIOMETRIC_UNAVAILABLE_ERROR; - } } else if (mBiometricRequested) { if (!eligibleSensors.isEmpty()) { for (BiometricSensor sensor : eligibleSensors) { @@ -434,6 +421,9 @@ class PreAuthInfo { } else if (credentialRequested) { modality |= TYPE_CREDENTIAL; status = credentialAvailable ? AUTHENTICATOR_OK : CREDENTIAL_NOT_ENROLLED; + } else if (Flags.mandatoryBiometrics() && mOnlyMandatoryBiometricsRequested + && !mIsMandatoryBiometricsAuthentication) { + status = MANDATORY_BIOMETRIC_UNAVAILABLE_ERROR; } else { // This should not be possible via the public API surface and is here mainly for // "correctness". An exception should have been thrown before getting here. diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java index fb8a81be4b89..871121472938 100644 --- a/services/core/java/com/android/server/biometrics/Utils.java +++ b/services/core/java/com/android/server/biometrics/Utils.java @@ -35,6 +35,7 @@ import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NOT_ENROLLED; import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NO_HARDWARE; import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_SENSOR_PRIVACY_ENABLED; import static com.android.server.biometrics.PreAuthInfo.CREDENTIAL_NOT_ENROLLED; +import static com.android.server.biometrics.PreAuthInfo.MANDATORY_BIOMETRIC_UNAVAILABLE_ERROR; import android.annotation.NonNull; import android.annotation.Nullable; @@ -48,6 +49,7 @@ import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType; +import android.hardware.biometrics.Flags; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.PromptInfo; import android.hardware.biometrics.SensorProperties; @@ -309,11 +311,16 @@ public class Utils { break; case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT: case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT: - biometricManagerCode = BiometricManager.BIOMETRIC_SUCCESS; + biometricManagerCode = Flags.mandatoryBiometrics() + ? BiometricManager.BIOMETRIC_ERROR_LOCKOUT + : BiometricManager.BIOMETRIC_SUCCESS; break; case BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED: biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE; break; + case BiometricConstants.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE: + biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE; + break; default: Slog.e(BiometricService.TAG, "Unhandled result code: " + biometricConstantsCode); biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE; @@ -375,6 +382,8 @@ public class Utils { return BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT; case BIOMETRIC_SENSOR_PRIVACY_ENABLED: return BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED; + case MANDATORY_BIOMETRIC_UNAVAILABLE_ERROR: + return BiometricConstants.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE; case BIOMETRIC_DISABLED_BY_DEVICE_POLICY: case BIOMETRIC_HARDWARE_NOT_DETECTED: case BIOMETRIC_NOT_ENABLED_FOR_APPS: diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index 38e6d822477a..1094bee1e7e6 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -1002,9 +1002,9 @@ public final class DeviceStateManagerService extends SystemService { /** * Checks if the process can cancel a device state request. If the calling process ID is not - * both the top app and foregrounded nor does the process ID and userID match the IDs that made - * the device state request, then check if this process holds the CONTROL_DEVICE_STATE - * permission. + * both the top app and foregrounded, verify that the calling process is in the foreground and + * that it matches the process ID and user ID that made the device state request. If neither are + * true, then check if this process holds the CONTROL_DEVICE_STATE permission. * * @param callingPid Process ID that is requesting this state change * @param callingUid UID that is requesting this state change @@ -1018,8 +1018,8 @@ public final class DeviceStateManagerService extends SystemService { if (Flags.deviceStateRequesterCancelState()) { synchronized (mLock) { isAllowedToControlState = - isAllowedToControlState || doCallingIdsMatchOverrideRequestIdsLocked( - callingPid, callingUid); + isTopApp || (isForegroundApp && doCallingIdsMatchOverrideRequestIdsLocked( + callingPid, callingUid)); } } diff --git a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java index 1938642ef396..e2889fa9cbf6 100644 --- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java +++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java @@ -29,8 +29,8 @@ import android.content.Context; import android.media.AudioAttributes; import android.os.Binder; import android.os.RemoteException; -import android.os.ServiceManager; import android.util.Slog; + import com.android.internal.compat.IPlatformCompat; /** @@ -79,6 +79,11 @@ public class NotificationChannelExtractor implements NotificationSignalExtractor if (restrictAudioAttributesCall() || restrictAudioAttributesAlarm() || restrictAudioAttributesMedia()) { AudioAttributes attributes = record.getChannel().getAudioAttributes(); + if (attributes == null) { + if (DBG) Slog.d(TAG, "missing AudioAttributes"); + return null; + } + boolean updateAttributes = false; if (restrictAudioAttributesCall() && !record.getNotification().isStyle(Notification.CallStyle.class) diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index b22b72627f92..27024a7e7997 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -101,7 +101,6 @@ import android.provider.DeviceConfigInterface; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.service.dreams.DreamManagerInternal; -import android.sysprop.InitProperties; import android.sysprop.PowerProperties; import android.util.ArrayMap; import android.util.IntArray; @@ -132,7 +131,6 @@ import com.android.server.RescueParty; import com.android.server.ServiceThread; import com.android.server.SystemService; import com.android.server.UiThread; -import com.android.server.UserspaceRebootLogger; import com.android.server.Watchdog; import com.android.server.am.BatteryStatsService; import com.android.server.display.feature.DeviceConfigParameterProvider; @@ -1274,8 +1272,7 @@ public final class PowerManagerService extends SystemService mHalInteractiveModeEnabled = true; mWakefulnessRaw = WAKEFULNESS_AWAKE; - sQuiescent = mSystemProperties.get(SYSTEM_PROPERTY_QUIESCENT, "0").equals("1") - || InitProperties.userspace_reboot_in_progress().orElse(false); + sQuiescent = mSystemProperties.get(SYSTEM_PROPERTY_QUIESCENT, "0").equals("1"); mNativeWrapper.nativeInit(this); mNativeWrapper.nativeSetAutoSuspend(false); @@ -4032,7 +4029,6 @@ public final class PowerManagerService extends SystemService throw new UnsupportedOperationException( "Attempted userspace reboot on a device that doesn't support it"); } - UserspaceRebootLogger.noteUserspaceRebootWasRequested(); } if (mHandler == null || !mSystemReady) { if (RescueParty.isRecoveryTriggeredReboot()) { diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java index eccbffb53529..07610872b208 100644 --- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java +++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java @@ -364,10 +364,7 @@ public final class HapticFeedbackVibrationProvider { if ((privFlags & HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS) == 0) { return TOUCH_VIBRATION_ATTRIBUTES; } - return new VibrationAttributes.Builder(IME_FEEDBACK_VIBRATION_ATTRIBUTES) - // TODO(b/332661766): Remove CATEGORY_KEYBOARD logic - .setCategory(VibrationAttributes.CATEGORY_KEYBOARD) - .build(); + return IME_FEEDBACK_VIBRATION_ATTRIBUTES; } @Nullable diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java index 5c567da7844f..aa4b9f3e9d7d 100644 --- a/services/core/java/com/android/server/vibrator/Vibration.java +++ b/services/core/java/com/android/server/vibrator/Vibration.java @@ -282,12 +282,6 @@ abstract class Vibration { VibrationScaler.scaleLevelToString(mScaleLevel), mAdaptiveScale, Long.toBinaryString(mCallerInfo.attrs.getFlags()), mCallerInfo.attrs.usageToString()); - // Optional, most vibrations have category unknown so skip them to simplify the logs - String categoryStr = - mCallerInfo.attrs.getCategory() != VibrationAttributes.CATEGORY_UNKNOWN - ? " | category=" + VibrationAttributes.categoryToString( - mCallerInfo.attrs.getCategory()) - : ""; // Optional, most vibrations should not be defined via AudioAttributes // so skip them to simplify the logs String audioUsageStr = @@ -302,7 +296,7 @@ abstract class Vibration { " | played: %s | original: %s", mPlayedEffect == null ? null : mPlayedEffect.toDebugString(), mOriginalEffect == null ? null : mOriginalEffect.toDebugString()); - pw.println(timingsStr + paramStr + categoryStr + audioUsageStr + callerStr + effectStr); + pw.println(timingsStr + paramStr + audioUsageStr + callerStr + effectStr); } /** Write this info into given {@link PrintWriter}. */ @@ -335,7 +329,6 @@ abstract class Vibration { final VibrationAttributes attrs = mCallerInfo.attrs; proto.write(VibrationAttributesProto.USAGE, attrs.getUsage()); proto.write(VibrationAttributesProto.AUDIO_USAGE, attrs.getAudioUsage()); - proto.write(VibrationAttributesProto.CATEGORY, attrs.getCategory()); proto.write(VibrationAttributesProto.FLAGS, attrs.getFlags()); proto.end(attrsToken); diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index 2c734127b7ea..fbf09fe89164 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -1617,8 +1617,7 @@ final class AccessibilityController { // causing the notifying, or the recents/home window is removed, then we won't need the // delayed notification anymore. void onWMTransition(@TransitionType int type, @TransitionFlags int flags) { - if (Flags.delayNotificationToMagnificationWhenRecentsWindowToFrontTransition() - && type == WindowManager.TRANSIT_TO_FRONT + if (type == WindowManager.TRANSIT_TO_FRONT && (flags & TRANSIT_FLAG_IS_RECENTS) != 0) { // Delay the recents to front transition notification then send after if needed. mHasDelayedNotificationForRecentsToFrontTransition = true; diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index 02c8a4999f4d..20c5f02aaee2 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -1506,10 +1506,13 @@ public class BackgroundActivityStartController { PackageManager pm = mService.mContext.getPackageManager(); ApplicationInfo applicationInfo; + final int sourceUserId = UserHandle.getUserId(sourceUid); try { - applicationInfo = pm.getApplicationInfo(packageName, 0); + applicationInfo = pm.getApplicationInfoAsUser(packageName, /* flags= */ 0, + sourceUserId); } catch (PackageManager.NameNotFoundException e) { - Slog.wtf(TAG, "Package name: " + packageName + " not found."); + Slog.wtf(TAG, "Package name: " + packageName + " not found for user " + + sourceUserId); return bas.optedIn(ar); } diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index 3ebaf03c4a31..9f1966b974d3 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -255,8 +255,18 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { inOutConfig.windowConfiguration.setAppBounds( newParentConfiguration.windowConfiguration.getBounds()); outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); - outAppBounds.inset(displayContent.getDisplayPolicy() - .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets); + if (inOutConfig.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { + final DisplayPolicy.DecorInsets.Info decor = + displayContent.getDisplayPolicy().getDecorInsetsInfo(rotation, dw, dh); + if (outAppBounds.contains(decor.mOverrideNonDecorFrame)) { + outAppBounds.intersect(decor.mOverrideNonDecorFrame); + } + } else { + // TODO(b/358509380): Handle other windowing mode like split screen and freeform + // cases correctly. + outAppBounds.inset(displayContent.getDisplayPolicy() + .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets); + } } float density = inOutConfig.densityDpi; if (density == Configuration.DENSITY_DPI_UNDEFINED) { diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java index 2f0ee171b5ba..f40f26179f85 100644 --- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java +++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT; import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY; import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; import static android.view.WindowManager.REMOVE_CONTENT_MODE_DESTROY; @@ -183,7 +184,7 @@ class DisplayWindowSettings { final DisplayInfo displayInfo = dc.getDisplayInfo(); final SettingsProvider.SettingsEntry settings = mSettingsProvider.getSettings(displayInfo); if (settings.mRemoveContentMode == REMOVE_CONTENT_MODE_UNDEFINED) { - if (dc.isPrivate()) { + if (dc.isPrivate() || dc.getDisplay().getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT) { // For private displays by default content is destroyed on removal. return REMOVE_CONTENT_MODE_DESTROY; } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 329d11bfcb95..3a4094339b02 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -1780,11 +1780,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { if (resuming != null) { // We do not want to trigger auto-PiP upon launch of a translucent activity. final boolean resumingOccludesParent = resuming.occludesParent(); - // Resuming the new resume activity only if the previous activity can't go into Pip - // since we want to give Pip activities a chance to enter Pip before resuming the - // next activity. - final boolean lastResumedCanPip = prev.checkEnterPictureInPictureState( - "shouldAutoPipWhilePausing", userLeaving); if (ActivityTaskManagerService.isPip2ExperimentEnabled()) { // If a new task is being launched, then mark the existing top activity as @@ -1794,6 +1789,12 @@ class TaskFragment extends WindowContainer<WindowContainer> { Task.enableEnterPipOnTaskSwitch(prev, resuming.getTask(), resuming, resuming.getOptions()); } + + // Resuming the new resume activity only if the previous activity can't go into Pip + // since we want to give Pip activities a chance to enter Pip before resuming the + // next activity. + final boolean lastResumedCanPip = prev.checkEnterPictureInPictureState( + "shouldAutoPipWhilePausing", userLeaving); if (prev.supportsEnterPipOnTaskSwitch && userLeaving && resumingOccludesParent && lastResumedCanPip && prev.pictureInPictureArgs.isAutoEnterEnabled()) { diff --git a/services/proguard.flags b/services/proguard.flags index 35e27f8ee4af..cdd41abf6c7c 100644 --- a/services/proguard.flags +++ b/services/proguard.flags @@ -114,6 +114,13 @@ -keep public class android.os.** { *; } -keep public class com.android.internal.util.** { *; } -keep public class com.android.modules.utils.build.** { *; } +# Also suppress related duplicate type warnings for the above kept classes. +-dontwarn android.gsi.** +-dontwarn android.hidl.base.** +-dontwarn android.hidl.manager.** +-dontwarn android.os.** +-dontwarn com.android.internal.util.** +-dontwarn com.android.modules.utils.build.** # CoverageService guards optional jacoco class references with a runtime guard, so we can safely # suppress build-time warnings. diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java index 3931580db47e..d80a1f056e94 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java @@ -18,13 +18,20 @@ package com.android.server.accessibility.magnification; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; +import static android.view.MotionEvent.ACTION_HOVER_MOVE; import static android.view.MotionEvent.ACTION_UP; +import static junit.framework.Assert.assertFalse; + import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.testng.AssertJUnit.assertTrue; import android.annotation.NonNull; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; import android.view.InputDevice; import android.view.MotionEvent; @@ -32,8 +39,10 @@ import android.view.MotionEvent; import androidx.test.runner.AndroidJUnit4; import com.android.server.accessibility.AccessibilityTraceManager; +import com.android.server.accessibility.Flags; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -45,6 +54,9 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) public class MagnificationGestureHandlerTest { + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + private TestMagnificationGestureHandler mMgh; private static final int DISPLAY_0 = 0; private static final int FULLSCREEN_MODE = @@ -81,6 +93,66 @@ public class MagnificationGestureHandlerTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE) + public void onMotionEvent_isFromMouse_handleMouseOrStylusEvent() { + final MotionEvent mouseEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0); + mouseEvent.setSource(InputDevice.SOURCE_MOUSE); + + mMgh.onMotionEvent(mouseEvent, mouseEvent, /* policyFlags= */ 0); + + try { + assertTrue(mMgh.mIsHandleMouseOrStylusEventCalled); + } finally { + mouseEvent.recycle(); + } + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE) + public void onMotionEvent_isFromStylus_handleMouseOrStylusEvent() { + final MotionEvent stylusEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0); + stylusEvent.setSource(InputDevice.SOURCE_STYLUS); + + mMgh.onMotionEvent(stylusEvent, stylusEvent, /* policyFlags= */ 0); + + try { + assertTrue(mMgh.mIsHandleMouseOrStylusEventCalled); + } finally { + stylusEvent.recycle(); + } + } + + @Test + @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE) + public void onMotionEvent_isFromMouse_handleMouseOrStylusEventNotCalled() { + final MotionEvent mouseEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0); + mouseEvent.setSource(InputDevice.SOURCE_MOUSE); + + mMgh.onMotionEvent(mouseEvent, mouseEvent, /* policyFlags= */ 0); + + try { + assertFalse(mMgh.mIsHandleMouseOrStylusEventCalled); + } finally { + mouseEvent.recycle(); + } + } + + @Test + @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE) + public void onMotionEvent_isFromStylus_handleMouseOrStylusEventNotCalled() { + final MotionEvent stylusEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0); + stylusEvent.setSource(InputDevice.SOURCE_STYLUS); + + mMgh.onMotionEvent(stylusEvent, stylusEvent, /* policyFlags= */ 0); + + try { + assertFalse(mMgh.mIsHandleMouseOrStylusEventCalled); + } finally { + stylusEvent.recycle(); + } + } + + @Test public void onMotionEvent_downEvent_handleInteractionStart() { final MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); downEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN); @@ -125,6 +197,7 @@ public class MagnificationGestureHandlerTest { private static class TestMagnificationGestureHandler extends MagnificationGestureHandler { boolean mIsInternalMethodCalled = false; + boolean mIsHandleMouseOrStylusEventCalled = false; TestMagnificationGestureHandler(int displayId, boolean detectSingleFingerTripleTap, boolean detectTwoFingerTripleTap, @@ -135,6 +208,11 @@ public class MagnificationGestureHandlerTest { } @Override + void handleMouseOrStylusEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + mIsHandleMouseOrStylusEventCalled = true; + } + + @Override void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) { mIsInternalMethodCalled = true; } diff --git a/services/tests/servicestests/src/com/android/server/autofill/PresentationEventLoggerTest.java b/services/tests/servicestests/src/com/android/server/autofill/PresentationEventLoggerTest.java new file mode 100644 index 000000000000..75258f0aa7e0 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/autofill/PresentationEventLoggerTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.autofill; + + +import static com.google.common.truth.Truth.assertThat; + +import android.view.autofill.AutofillId; +import android.view.autofill.AutofillValue; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class PresentationEventLoggerTest { + + @Test + public void testViewEntered() { + PresentationStatsEventLogger pEventLogger = + PresentationStatsEventLogger.createPresentationLog(1, 1, 1); + + AutofillId id = new AutofillId(13); + AutofillValue initialValue = AutofillValue.forText("hello"); + AutofillValue lastValue = AutofillValue.forText("hello world"); + ViewState vState = new ViewState(id, null, 0, false); + + pEventLogger.startNewEvent(); + pEventLogger.maybeSetFocusedId(id); + pEventLogger.onFieldTextUpdated(vState, initialValue); + pEventLogger.onFieldTextUpdated(vState, lastValue); + + PresentationStatsEventLogger.PresentationStatsEventInternal event = + pEventLogger.getInternalEvent().get(); + assertThat(event).isNotNull(); + assertThat(event.mFieldFirstLength).isEqualTo(initialValue.getTextValue().length()); + assertThat(event.mFieldLastLength).isEqualTo(lastValue.getTextValue().length()); + assertThat(event.mFieldModifiedFirstTimestampMs).isNotEqualTo(-1); + assertThat(event.mFieldModifiedLastTimestampMs).isNotEqualTo(-1); + } + + @Test + public void testViewAutofilled() { + PresentationStatsEventLogger pEventLogger = + PresentationStatsEventLogger.createPresentationLog(1, 1, 1); + + String newTextValue = "hello"; + AutofillValue value = AutofillValue.forText(newTextValue); + AutofillId id = new AutofillId(13); + ViewState vState = new ViewState(id, null, ViewState.STATE_AUTOFILLED, false); + + pEventLogger.startNewEvent(); + pEventLogger.maybeSetFocusedId(id); + pEventLogger.onFieldTextUpdated(vState, value); + + PresentationStatsEventLogger.PresentationStatsEventInternal event = + pEventLogger.getInternalEvent().get(); + assertThat(event).isNotNull(); + assertThat(event.mFieldFirstLength).isEqualTo(newTextValue.length()); + assertThat(event.mFieldLastLength).isEqualTo(newTextValue.length()); + assertThat(event.mAutofilledTimestampMs).isNotEqualTo(-1); + assertThat(event.mFieldModifiedFirstTimestampMs).isEqualTo(-1); + assertThat(event.mFieldModifiedLastTimestampMs).isEqualTo(-1); + } + + @Test + public void testModifiedOnDifferentView() { + PresentationStatsEventLogger pEventLogger = + PresentationStatsEventLogger.createPresentationLog(1, 1, 1); + + String newTextValue = "hello"; + AutofillValue value = AutofillValue.forText(newTextValue); + AutofillId id = new AutofillId(13); + ViewState vState = new ViewState(id, null, ViewState.STATE_AUTOFILLED, false); + + pEventLogger.startNewEvent(); + pEventLogger.onFieldTextUpdated(vState, value); + + PresentationStatsEventLogger.PresentationStatsEventInternal event = + pEventLogger.getInternalEvent().get(); + assertThat(event).isNotNull(); + assertThat(event.mFieldFirstLength).isEqualTo(-1); + assertThat(event.mFieldLastLength).isEqualTo(-1); + assertThat(event.mFieldModifiedFirstTimestampMs).isEqualTo(-1); + assertThat(event.mFieldModifiedLastTimestampMs).isEqualTo(-1); + assertThat(event.mAutofilledTimestampMs).isEqualTo(-1); + } + + @Test + public void testSetCountShown() { + PresentationStatsEventLogger pEventLogger = + PresentationStatsEventLogger.createPresentationLog(1, 1, 1); + + pEventLogger.startNewEvent(); + pEventLogger.logWhenDatasetShown(7); + + PresentationStatsEventLogger.PresentationStatsEventInternal event = + pEventLogger.getInternalEvent().get(); + assertThat(event).isNotNull(); + assertThat(event.mCountShown).isEqualTo(7); + assertThat(event.mNoPresentationReason) + .isEqualTo(PresentationStatsEventLogger.NOT_SHOWN_REASON_ANY_SHOWN); + } + + @Test + public void testFillDialogShownThenInline() { + PresentationStatsEventLogger pEventLogger = + PresentationStatsEventLogger.createPresentationLog(1, 1, 1); + + pEventLogger.startNewEvent(); + pEventLogger.maybeSetDisplayPresentationType(3); + pEventLogger.maybeSetDisplayPresentationType(2); + + PresentationStatsEventLogger.PresentationStatsEventInternal event = + pEventLogger.getInternalEvent().get(); + assertThat(event).isNotNull(); + assertThat(event.mDisplayPresentationType).isEqualTo(3); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java index a4222ff5650b..d2961bc8a90f 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -79,6 +79,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.UserManager; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsDisabled; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; @@ -1488,15 +1489,30 @@ public class BiometricServiceTest { } @Test + @RequiresFlagsDisabled(Flags.FLAG_MANDATORY_BIOMETRICS) public void testCanAuthenticate_whenLockoutTimed() throws Exception { testCanAuthenticate_whenLockedOut(LockoutTracker.LOCKOUT_TIMED); } @Test + @RequiresFlagsDisabled(Flags.FLAG_MANDATORY_BIOMETRICS) public void testCanAuthenticate_whenLockoutPermanent() throws Exception { testCanAuthenticate_whenLockedOut(LockoutTracker.LOCKOUT_PERMANENT); } + @Test + @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS) + public void testCanAuthenticate_whenLockoutTimed_returnsLockoutError() throws Exception { + testCanAuthenticate_whenLockedOut_returnLockoutError(LockoutTracker.LOCKOUT_TIMED); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS) + public void testCanAuthenticate_whenLockoutPermanent_returnsLockoutError() throws Exception { + testCanAuthenticate_whenLockedOut_returnLockoutError(LockoutTracker.LOCKOUT_PERMANENT); + } + + @RequiresFlagsDisabled(Flags.FLAG_MANDATORY_BIOMETRICS) private void testCanAuthenticate_whenLockedOut(@LockoutTracker.LockoutMode int lockoutMode) throws Exception { // When only biometric is requested, and sensor is strong enough @@ -1510,6 +1526,21 @@ public class BiometricServiceTest { invokeCanAuthenticate(mBiometricService, Authenticators.BIOMETRIC_STRONG)); } + @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS) + private void testCanAuthenticate_whenLockedOut_returnLockoutError( + @LockoutTracker.LockoutMode int lockoutMode) + throws Exception { + // When only biometric is requested, and sensor is strong enough + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); + + when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt())) + .thenReturn(lockoutMode); + + // Lockout is not considered an error for BiometricManager#canAuthenticate + assertEquals(BiometricManager.BIOMETRIC_ERROR_LOCKOUT, + invokeCanAuthenticate(mBiometricService, Authenticators.BIOMETRIC_STRONG)); + } + @Test @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS) public void testCanAuthenticate_whenMandatoryBiometricsRequested() @@ -1529,7 +1560,7 @@ public class BiometricServiceTest { when(mTrustManager.isInSignificantPlace()).thenReturn(true); - assertEquals(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE, + assertEquals(BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE, invokeCanAuthenticate(mBiometricService, Authenticators.MANDATORY_BIOMETRICS)); } @@ -1572,7 +1603,7 @@ public class BiometricServiceTest { setupAuthForOnly(TYPE_CREDENTIAL, Authenticators.DEVICE_CREDENTIAL); - assertEquals(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE, + assertEquals(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE, invokeCanAuthenticate(mBiometricService, Authenticators.MANDATORY_BIOMETRICS)); when(mTrustManager.isInSignificantPlace()).thenReturn(true); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java index 240da9fe46bd..4c3a233fdd97 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java @@ -231,7 +231,7 @@ public class PreAuthInfoTest { @Test @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS) - public void testMandatoryBiometricsStatus_whenRequirementsNotSatisfiedAndSensorAvailable() + public void testMandatoryBiometricsAndStrongBiometricsStatus_whenRequirementsNotSatisfied() throws Exception { when(mTrustManager.isInSignificantPlace()).thenReturn(true); @@ -246,6 +246,24 @@ public class PreAuthInfoTest { assertThat(preAuthInfo.eligibleSensors).hasSize(1); } + @Test + @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS) + public void testMandatoryBiometricsStatus_whenRequirementsNotSatisfiedAndSensorAvailable() + throws Exception { + when(mTrustManager.isInSignificantPlace()).thenReturn(true); + + final BiometricSensor sensor = getFaceSensor(); + final PromptInfo promptInfo = new PromptInfo(); + promptInfo.setAuthenticators(BiometricManager.Authenticators.MANDATORY_BIOMETRICS); + final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager, + mSettingObserver, List.of(sensor), 0 /* userId */, promptInfo, TEST_PACKAGE_NAME, + false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager); + + assertThat(preAuthInfo.getCanAuthenticateResult()).isEqualTo( + BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE); + assertThat(preAuthInfo.eligibleSensors).hasSize(0); + } + private BiometricSensor getFingerprintSensor() { BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FINGERPRINT, TYPE_FINGERPRINT, BiometricManager.Authenticators.BIOMETRIC_STRONG, diff --git a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java index cb75e1ab6cce..14cb22d7698e 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java @@ -26,16 +26,25 @@ import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricPrompt; +import android.hardware.biometrics.Flags; import android.hardware.biometrics.PromptInfo; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import androidx.test.filters.SmallTest; +import org.junit.Rule; import org.junit.Test; @Presubmit @SmallTest public class UtilsTest { + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); @Test public void testCombineAuthenticatorBundles_withKeyDeviceCredential_andKeyAuthenticators() { @@ -215,7 +224,8 @@ public class UtilsTest { } @Test - public void testBiometricConstantsConversion() { + @RequiresFlagsDisabled(Flags.FLAG_MANDATORY_BIOMETRICS) + public void testBiometricConstantsConversionLegacy() { final int[][] testCases = { {BiometricConstants.BIOMETRIC_SUCCESS, BiometricManager.BIOMETRIC_SUCCESS}, @@ -240,6 +250,34 @@ public class UtilsTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS) + public void testBiometricConstantsConversion() { + final int[][] testCases = { + {BiometricConstants.BIOMETRIC_SUCCESS, + BiometricManager.BIOMETRIC_SUCCESS}, + {BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS, + BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED}, + {BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL, + BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED}, + {BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, + BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE}, + {BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT, + BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE}, + {BiometricConstants.BIOMETRIC_ERROR_LOCKOUT, + BiometricManager.BIOMETRIC_ERROR_LOCKOUT}, + {BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT, + BiometricManager.BIOMETRIC_ERROR_LOCKOUT}, + {BiometricConstants.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE, + BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE} + }; + + for (int i = 0; i < testCases.length; i++) { + assertEquals(testCases[i][1], + Utils.biometricConstantsToBiometricManager(testCases[i][0])); + } + } + + @Test public void testGetAuthenticationTypeForResult_getsCorrectType() { assertEquals(Utils.getAuthenticationTypeForResult( BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED), diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java index 240bd1ec56e3..8797e63dab27 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java @@ -16,8 +16,6 @@ package com.android.server.vibrator; -import static android.os.VibrationAttributes.CATEGORY_KEYBOARD; -import static android.os.VibrationAttributes.CATEGORY_UNKNOWN; import static android.os.VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY; import static android.os.VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF; import static android.os.VibrationAttributes.USAGE_IME_FEEDBACK; @@ -255,7 +253,7 @@ public class HapticFeedbackVibrationProviderTest { } @Test - public void testKeyboardHaptic_fixAmplitude_keyboardCategoryOn_keyboardVibrationReturned() { + public void testKeyboardHaptic_fixAmplitude_keyboardVibrationReturned() { mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK); mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE); @@ -330,7 +328,7 @@ public class HapticFeedbackVibrationProviderTest { } @Test - public void testVibrationAttribute_keyboardCategoryOn_notIme_useTouchUsage() { + public void testVibrationAttribute_notIme_useTouchUsage() { HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) { @@ -338,13 +336,11 @@ public class HapticFeedbackVibrationProviderTest { effectId, /* flags */ 0, /* privFlags */ 0); assertWithMessage("Expected USAGE_TOUCH for effect " + effectId) .that(attrs.getUsage()).isEqualTo(USAGE_TOUCH); - assertWithMessage("Expected CATEGORY_KEYBOARD for effect " + effectId) - .that(attrs.getCategory()).isEqualTo(CATEGORY_UNKNOWN); } } @Test - public void testVibrationAttribute_keyboardCategoryOn_isIme_useImeFeedbackUsage() { + public void testVibrationAttribute_isIme_useImeFeedbackUsage() { HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) { @@ -353,8 +349,6 @@ public class HapticFeedbackVibrationProviderTest { HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS); assertWithMessage("Expected USAGE_IME_FEEDBACK for effect " + effectId) .that(attrs.getUsage()).isEqualTo(USAGE_IME_FEEDBACK); - assertWithMessage("Expected CATEGORY_KEYBOARD for effect " + effectId) - .that(attrs.getCategory()).isEqualTo(CATEGORY_KEYBOARD); } } diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java index 6f06050f55ff..38cd49daf99d 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java @@ -610,7 +610,6 @@ public class VibrationSettingsTest { assertVibrationIgnoredForAttributes( new VibrationAttributes.Builder() .setUsage(USAGE_IME_FEEDBACK) - .setCategory(VibrationAttributes.CATEGORY_KEYBOARD) .build(), Vibration.Status.IGNORED_FOR_SETTINGS); @@ -619,7 +618,6 @@ public class VibrationSettingsTest { assertVibrationNotIgnoredForAttributes( new VibrationAttributes.Builder() .setUsage(USAGE_IME_FEEDBACK) - .setCategory(VibrationAttributes.CATEGORY_KEYBOARD) .setFlags(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF) .build()); } @@ -637,7 +635,6 @@ public class VibrationSettingsTest { assertVibrationNotIgnoredForAttributes( new VibrationAttributes.Builder() .setUsage(USAGE_IME_FEEDBACK) - .setCategory(VibrationAttributes.CATEGORY_KEYBOARD) .build()); } @@ -654,7 +651,6 @@ public class VibrationSettingsTest { assertVibrationIgnoredForAttributes( new VibrationAttributes.Builder() .setUsage(USAGE_IME_FEEDBACK) - .setCategory(VibrationAttributes.CATEGORY_KEYBOARD) .build(), Vibration.Status.IGNORED_FOR_SETTINGS); } diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt index 638d594b0a48..eb63e4985a9f 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt @@ -28,6 +28,7 @@ import com.android.server.wm.flicker.BaseTest import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd import com.android.server.wm.flicker.statusBarLayerIsVisibleAtStartAndEnd +import com.android.server.wm.flicker.taskBarLayerIsVisibleAtStartAndEnd import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Ignore @@ -114,28 +115,28 @@ class ShowImeWhileEnteringOverviewTest(flicker: LegacyFlickerTest) : BaseTest(fl /** * In the legacy transitions, the nav bar is not marked as invisible. In the new transitions - * this is fixed and the nav bar shows as invisible + * this is fixed and the status bar shows as invisible */ @Presubmit @Test fun statusBarLayerIsInvisibleInLandscapePhone() { Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart) Assume.assumeTrue(flicker.scenario.isGesturalNavigation) - Assume.assumeFalse(usesTaskbar) + Assume.assumeFalse(flicker.scenario.isTablet) flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) } flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.STATUS_BAR) } } /** * In the legacy transitions, the nav bar is not marked as invisible. In the new transitions - * this is fixed and the nav bar shows as invisible + * this is fixed and the status bar shows as invisible */ @Presubmit @Test fun statusBarLayerIsInvisibleInLandscapeTablet() { Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart) Assume.assumeTrue(flicker.scenario.isGesturalNavigation) - Assume.assumeTrue(usesTaskbar) + Assume.assumeTrue(flicker.scenario.isTablet) flicker.statusBarLayerIsVisibleAtStartAndEnd() } @@ -149,6 +150,10 @@ class ShowImeWhileEnteringOverviewTest(flicker: LegacyFlickerTest) : BaseTest(fl @Ignore("Visibility changes depending on orientation and navigation mode") override fun navBarLayerPositionAtStartAndEnd() {} + @Test + @Ignore("Visibility changes depending on orientation and navigation mode") + override fun taskBarLayerIsVisibleAtStartAndEnd() {} + /** {@inheritDoc} */ @Test @Ignore("Visibility changes depending on orientation and navigation mode") @@ -161,7 +166,10 @@ class ShowImeWhileEnteringOverviewTest(flicker: LegacyFlickerTest) : BaseTest(fl @Presubmit @Test - override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd() + fun taskBarLayerIsVisibleAtStartAndEndForTablets() { + Assume.assumeTrue(flicker.scenario.isTablet) + flicker.taskBarLayerIsVisibleAtStartAndEnd() + } @Presubmit @Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt index 70d762e02af5..851ce022bd81 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt @@ -137,8 +137,6 @@ constructor( /** * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible at the start and end of the * transition - * - * Note: Large screen only */ @Presubmit @Test @@ -149,8 +147,6 @@ constructor( /** * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible during the whole transition - * - * Note: Large screen only */ @Presubmit @Test diff --git a/tests/Internal/AndroidTest.xml b/tests/Internal/AndroidTest.xml index 7b67e9ebcced..2d6c650eb2dc 100644 --- a/tests/Internal/AndroidTest.xml +++ b/tests/Internal/AndroidTest.xml @@ -26,4 +26,12 @@ <option name="package" value="com.android.internal.tests" /> <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> </test> + + <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> + <option name="pull-pattern-keys" value="perfetto_file_path"/> + <option name="directory-keys" + value="/data/user/0/com.android.internal.tests/files"/> + <option name="collect-on-run-ended-only" value="true"/> + <option name="clean-up" value="true"/> + </metrics_collector> </configuration>
\ No newline at end of file diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java index 7d0c5966b5dc..4b745b289d33 100644 --- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java +++ b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java @@ -29,7 +29,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static java.io.File.createTempFile; -import static java.nio.file.Files.createTempDirectory; import android.content.Context; import android.os.SystemClock; @@ -45,6 +44,7 @@ import android.tracing.perfetto.DataSource; import android.util.proto.ProtoInputStream; import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; import com.android.internal.protolog.common.IProtoLogGroup; import com.android.internal.protolog.common.LogDataType; @@ -67,7 +67,6 @@ import java.io.File; import java.io.IOException; import java.util.List; import java.util.Random; -import java.util.TreeMap; import java.util.concurrent.atomic.AtomicInteger; /** @@ -78,7 +77,8 @@ import java.util.concurrent.atomic.AtomicInteger; @Presubmit @RunWith(JUnit4.class) public class PerfettoProtoLogImplTest { - private final File mTracingDirectory = createTempDirectory("temp").toFile(); + private final File mTracingDirectory = InstrumentationRegistry.getInstrumentation() + .getTargetContext().getFilesDir(); private final ResultWriter mWriter = new ResultWriter() .forScenario(new ScenarioBuilder() @@ -384,7 +384,7 @@ public class PerfettoProtoLogImplTest { new Object[]{5}); verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq( - LogLevel.INFO), eq("UNKNOWN MESSAGE#1234 (5)")); + LogLevel.INFO), eq("UNKNOWN MESSAGE args = (5)")); verify(mReader).getViewerString(eq(1234L)); } @@ -451,8 +451,8 @@ public class PerfettoProtoLogImplTest { before = SystemClock.elapsedRealtimeNanos(); mProtoLog.log( LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, - "My test message :: %s, %d, %o, %x, %f, %b", - "test", 1, 2, 3, 0.4, true); + "My test message :: %s, %d, %x, %f, %b", + "test", 1, 3, 0.4, true); after = SystemClock.elapsedRealtimeNanos(); } finally { traceMonitor.stop(mWriter); @@ -467,7 +467,7 @@ public class PerfettoProtoLogImplTest { Truth.assertThat(protolog.messages.getFirst().getTimestamp().getElapsedNanos()) .isAtMost(after); Truth.assertThat(protolog.messages.getFirst().getMessage()) - .isEqualTo("My test message :: test, 2, 4, 6, 0.400000, true"); + .isEqualTo("My test message :: test, 2, 6, 0.400000, true"); } @Test diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt index 36bfbefdb086..c510eeea63de 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt @@ -334,13 +334,14 @@ class HostStubGen(val options: HostStubGenOptions) { entry: ZipEntry, out: ZipOutputStream, ) { - BufferedInputStream(inZip.getInputStream(entry)).use { bis -> + // TODO: It seems like copying entries this way is _very_ slow, + // even with out.setLevel(0). Look for other ways to do it. + + inZip.getInputStream(entry).use { ins -> // Copy unknown entries as is to the impl out. (but not to the stub out.) val outEntry = ZipEntry(entry.name) out.putNextEntry(outEntry) - while (bis.available() > 0) { - out.write(bis.read()) - } + ins.transferTo(out) out.closeEntry() } } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt index ee4a06fb983d..fcdf8247e7d0 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt @@ -121,7 +121,7 @@ class HostStubGenLogger { return level.ordinal <= maxLogLevel.ordinal } - private fun println(level: LogLevel, message: String) { + fun println(level: LogLevel, message: String) { printers.forEach { if (it.logLevel.ordinal >= level.ordinal) { it.println(level, indent, message) @@ -129,7 +129,7 @@ class HostStubGenLogger { } } - private fun println(level: LogLevel, format: String, vararg args: Any?) { + fun println(level: LogLevel, format: String, vararg args: Any?) { if (isEnabled(level)) { println(level, String.format(format, *args)) } @@ -185,14 +185,29 @@ class HostStubGenLogger { println(LogLevel.Debug, format, *args) } - inline fun <T> iTime(message: String, block: () -> T): T { + inline fun <T> logTime(level: LogLevel, message: String, block: () -> T): T { val start = System.currentTimeMillis() - val ret = block() - val end = System.currentTimeMillis() + try { + return block() + } finally { + val end = System.currentTimeMillis() + if (isEnabled(level)) { + println(level, + String.format("%s: took %.1f second(s).", message, (end - start) / 1000.0)) + } + } + } - log.i("%s: took %.1f second(s).", message, (end - start) / 1000.0) + inline fun <T> iTime(message: String, block: () -> T): T { + return logTime(LogLevel.Info, message, block) + } + + inline fun <T> vTime(message: String, block: () -> T): T { + return logTime(LogLevel.Verbose, message, block) + } - return ret + inline fun <T> dTime(message: String, block: () -> T): T { + return logTime(LogLevel.Debug, message, block) } inline fun forVerbose(block: () -> Unit) { diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt index aa530050741b..22858803b597 100644 --- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt +++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt @@ -26,7 +26,6 @@ import com.github.javaparser.ast.CompilationUnit import com.github.javaparser.ast.Modifier import com.github.javaparser.ast.NodeList import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration -import com.github.javaparser.ast.body.InitializerDeclaration import com.github.javaparser.ast.expr.ArrayAccessExpr import com.github.javaparser.ast.expr.ArrayCreationExpr import com.github.javaparser.ast.expr.ArrayInitializerExpr @@ -42,7 +41,10 @@ import com.github.javaparser.ast.expr.NullLiteralExpr import com.github.javaparser.ast.expr.ObjectCreationExpr import com.github.javaparser.ast.expr.SimpleName import com.github.javaparser.ast.expr.StringLiteralExpr +import com.github.javaparser.ast.expr.VariableDeclarationExpr import com.github.javaparser.ast.stmt.BlockStmt +import com.github.javaparser.ast.stmt.ReturnStmt +import com.github.javaparser.ast.type.ClassOrInterfaceType import java.io.File import java.io.FileInputStream import java.io.FileNotFoundException @@ -195,6 +197,7 @@ object ProtoLogTool { groups: Map<String, LogGroup>, protoLogGroupsClassName: String ) { + var needsCreateLogGroupsMap = false classDeclaration.fields.forEach { field -> field.getAnnotationByClass(ProtoLogToolInjected::class.java) .ifPresent { annotationExpr -> @@ -222,33 +225,10 @@ object ProtoLogTool { } ?: NullLiteralExpr()) } ProtoLogToolInjected.Value.LOG_GROUPS.name -> { - val initializerBlockStmt = BlockStmt() - for (group in groups) { - initializerBlockStmt.addStatement( - MethodCallExpr() - .setName("put") - .setArguments( - NodeList(StringLiteralExpr(group.key), - FieldAccessExpr() - .setScope( - NameExpr( - protoLogGroupsClassName - )) - .setName(group.value.name))) - ) - group.key - } - - val treeMapCreation = ObjectCreationExpr() - .setType("TreeMap<String, IProtoLogGroup>") - .setAnonymousClassBody(NodeList( - InitializerDeclaration().setBody( - initializerBlockStmt - ) - )) - + needsCreateLogGroupsMap = true field.setFinal(true) - field.variables.first().setInitializer(treeMapCreation) + field.variables.first().setInitializer( + MethodCallExpr().setName("createLogGroupsMap")) } ProtoLogToolInjected.Value.CACHE_UPDATER.name -> { field.setFinal(true) @@ -261,6 +241,45 @@ object ProtoLogTool { } } } + + if (needsCreateLogGroupsMap) { + val body = BlockStmt() + body.addStatement(AssignExpr( + VariableDeclarationExpr( + ClassOrInterfaceType("TreeMap<String, IProtoLogGroup>"), + "result" + ), + ObjectCreationExpr().setType("TreeMap<String, IProtoLogGroup>"), + AssignExpr.Operator.ASSIGN + )) + for (group in groups) { + body.addStatement( + MethodCallExpr( + NameExpr("result"), + "put", + NodeList( + StringLiteralExpr(group.key), + FieldAccessExpr() + .setScope( + NameExpr( + protoLogGroupsClassName + )) + .setName(group.value.name) + ) + ) + ) + } + body.addStatement(ReturnStmt(NameExpr("result"))) + + val method = classDeclaration.addMethod( + "createLogGroupsMap", + Modifier.Keyword.PRIVATE, + Modifier.Keyword.STATIC, + Modifier.Keyword.FINAL + ) + method.setType("TreeMap<String, IProtoLogGroup>") + method.setBody(body) + } } private fun injectCacheClass( |