diff options
202 files changed, 6692 insertions, 3093 deletions
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index c6a1546fb931..65acd49d44fa 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -104,7 +104,9 @@ public class ActivityOptions extends ComponentOptions { MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED, MODE_BACKGROUND_ACTIVITY_START_ALLOWED, MODE_BACKGROUND_ACTIVITY_START_DENIED, - MODE_BACKGROUND_ACTIVITY_START_COMPAT}) + MODE_BACKGROUND_ACTIVITY_START_COMPAT, + MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS, + MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE}) public @interface BackgroundActivityStartMode {} /** * No explicit value chosen. The system will decide whether to grant privileges. @@ -119,6 +121,20 @@ public class ActivityOptions extends ComponentOptions { */ public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = 2; /** + * Allow the {@link PendingIntent} to use ALL background activity start privileges, including + * special permissions that will allow starts at any time. + * + * @hide + */ + public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS = 3; + /** + * Allow the {@link PendingIntent} to use background activity start privileges based on + * visibility of the app. + * + * @hide + */ + public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE = 4; + /** * Special behavior for compatibility. * Similar to {@link #MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED} * diff --git a/core/java/android/app/ComponentOptions.java b/core/java/android/app/ComponentOptions.java index 0e8e2e30c26f..b3fc0588022b 100644 --- a/core/java/android/app/ComponentOptions.java +++ b/core/java/android/app/ComponentOptions.java @@ -18,9 +18,11 @@ package android.app; import static android.app.ActivityOptions.BackgroundActivityStartMode; import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS; import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT; import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE; import android.annotation.NonNull; import android.annotation.Nullable; @@ -48,15 +50,7 @@ public class ComponentOptions { public static final String KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED = "android.pendingIntent.backgroundActivityAllowed"; - /** - * PendingIntent caller allows activity to be started if caller has BAL permission. - * @hide - */ - public static final String KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION = - "android.pendingIntent.backgroundActivityAllowedByPermission"; - private Integer mPendingIntentBalAllowed = MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; - private boolean mPendingIntentBalAllowedByPermission = false; ComponentOptions() { } @@ -69,9 +63,6 @@ public class ComponentOptions { mPendingIntentBalAllowed = opts.getInt(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED); - setPendingIntentBackgroundActivityLaunchAllowedByPermission( - opts.getBoolean( - KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, false)); } /** @@ -114,10 +105,19 @@ public class ComponentOptions { public @NonNull ComponentOptions setPendingIntentBackgroundActivityStartMode( @BackgroundActivityStartMode int state) { switch (state) { + case MODE_BACKGROUND_ACTIVITY_START_ALLOWED: + if (mPendingIntentBalAllowed != MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS) { + // do not overwrite ALWAYS with ALLOWED for backwards compatibility, + // if setPendingIntentBackgroundActivityLaunchAllowedByPermission is used + // before this method. + mPendingIntentBalAllowed = state; + } + break; case MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED: case MODE_BACKGROUND_ACTIVITY_START_DENIED: case MODE_BACKGROUND_ACTIVITY_START_COMPAT: - case MODE_BACKGROUND_ACTIVITY_START_ALLOWED: + case MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS: + case MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE: mPendingIntentBalAllowed = state; break; default: @@ -140,20 +140,32 @@ public class ComponentOptions { } /** - * Set PendingIntent activity can be launched from background if caller has BAL permission. + * Get PendingIntent activity is allowed to be started in the background if the caller + * has BAL permission. * @hide + * @deprecated check for #MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS */ - public void setPendingIntentBackgroundActivityLaunchAllowedByPermission(boolean allowed) { - mPendingIntentBalAllowedByPermission = allowed; + @Deprecated + public boolean isPendingIntentBackgroundActivityLaunchAllowedByPermission() { + return mPendingIntentBalAllowed == MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS; } /** - * Get PendingIntent activity is allowed to be started in the background if the caller - * has BAL permission. + * Set PendingIntent activity can be launched from background if caller has BAL permission. * @hide + * @deprecated use #MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS */ - public boolean isPendingIntentBackgroundActivityLaunchAllowedByPermission() { - return mPendingIntentBalAllowedByPermission; + @Deprecated + public void setPendingIntentBackgroundActivityLaunchAllowedByPermission(boolean allowed) { + if (allowed) { + setPendingIntentBackgroundActivityStartMode( + MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS); + } else { + if (getPendingIntentBackgroundActivityStartMode() + == MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS) { + setPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED); + } + } } /** @hide */ @@ -162,10 +174,6 @@ public class ComponentOptions { if (mPendingIntentBalAllowed != MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) { b.putInt(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, mPendingIntentBalAllowed); } - if (mPendingIntentBalAllowedByPermission) { - b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, - mPendingIntentBalAllowedByPermission); - } return b; } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 97404dcdea0c..111e6a8e93ef 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -5277,12 +5277,28 @@ public class Intent implements Parcelable, Cloneable { * through {@link #getData()}. User interaction is required to return the edited screenshot to * the calling activity. * + * <p>The response {@link Intent} may include additional data to "backlink" directly back to the + * application for which the screenshot was captured. If present, the application "backlink" can + * be retrieved via {@link #getClipData()}. The data is present only if the user accepted to + * include the link information with the screenshot. The data can contain one of the following: + * <ul> + * <li>A deeplinking {@link Uri} or an {@link Intent} if the captured app integrates with + * {@link android.app.assist.AssistContent}.</li> + * <li>Otherwise, a main launcher intent that launches the screenshotted application to + * its home screen.</li> + * </ul> + * The "backlink" to the screenshotted application will be set within {@link ClipData}, either + * as a {@link Uri} or an {@link Intent} if present. + * * <p>This intent action requires the permission * {@link android.Manifest.permission#LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE}. * * <p>Callers should query * {@link StatusBarManager#canLaunchCaptureContentActivityForNote(Activity)} before showing a UI * element that allows users to trigger this flow. + * + * <p>Callers should query for {@link #EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE} in the + * response {@link Intent} to check if the request was a success. */ @RequiresPermission(Manifest.permission.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE) @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java index a3beaf427226..209f323d7b34 100644 --- a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java +++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java @@ -216,7 +216,11 @@ public final class NavigationBarView extends FrameLayout { oldConfig.getLayoutDirection() != mConfiguration.getLayoutDirection(); if (densityChange || dirChange) { - mImeSwitcherIcon = getDrawable(com.android.internal.R.drawable.ic_ime_switcher); + final int switcherResId = Flags.imeSwitcherRevamp() + ? com.android.internal.R.drawable.ic_ime_switcher_new + : com.android.internal.R.drawable.ic_ime_switcher; + + mImeSwitcherIcon = getDrawable(switcherResId); } if (orientationChange || densityChange || dirChange) { mBackIcon = getBackDrawable(); diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index f0e673b3e3ac..7e247493e35c 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -41,6 +41,7 @@ import android.util.TimeUtils; import android.view.animation.AnimationUtils; import java.io.PrintWriter; +import java.util.Locale; /** * Coordinates the timing of animations, input and drawing. @@ -200,6 +201,7 @@ public final class Choreographer { private final DisplayEventReceiver.VsyncEventData mLastVsyncEventData = new DisplayEventReceiver.VsyncEventData(); private final FrameData mFrameData = new FrameData(); + private volatile boolean mInDoFrameCallback = false; /** * Contains information about the current frame for jank-tracking, @@ -818,6 +820,11 @@ public final class Choreographer { * @hide */ public long getVsyncId() { + if (!mInDoFrameCallback && Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + String message = String.format(Locale.getDefault(), "unsync-vsync-id=%d isSfChoreo=%s", + mLastVsyncEventData.preferredFrameTimeline().vsyncId, this == getSfInstance()); + Trace.instant(Trace.TRACE_TAG_VIEW, message); + } return mLastVsyncEventData.preferredFrameTimeline().vsyncId; } @@ -853,6 +860,7 @@ public final class Choreographer { if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { Trace.traceBegin( Trace.TRACE_TAG_VIEW, "Choreographer#doFrame " + timeline.mVsyncId); + mInDoFrameCallback = true; } synchronized (mLock) { if (!mFrameScheduled) { @@ -947,6 +955,7 @@ public final class Choreographer { doCallbacks(Choreographer.CALLBACK_COMMIT, frameIntervalNanos); } finally { AnimationUtils.unlockAnimationClock(); + mInDoFrameCallback = false; if (resynced) { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 82a7e162dc2d..a23e3839c348 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -30,6 +30,7 @@ import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE; import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED; +import static android.view.accessibility.Flags.removeChildHoverCheckForTouchExploration; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN; @@ -17486,9 +17487,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Dispatching hover events to {@link TouchDelegate} to improve accessibility. * <p> * This method is dispatching hover events to the delegate target to support explore by touch. - * Similar to {@link ViewGroup#dispatchTouchEvent}, this method send proper hover events to + * Similar to {@link ViewGroup#dispatchTouchEvent}, this method sends proper hover events to * the delegate target according to the pointer and the touch area of the delegate while touch - * exploration enabled. + * exploration is enabled. * </p> * * @param event The motion event dispatch to the delegate target. @@ -17520,17 +17521,33 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // hover events but receive accessibility focus, it should also not delegate to these // views when hovered. if (!oldHoveringTouchDelegate) { - if ((action == MotionEvent.ACTION_HOVER_ENTER - || action == MotionEvent.ACTION_HOVER_MOVE) - && !pointInHoveredChild(event) - && pointInDelegateRegion) { - mHoveringTouchDelegate = true; + if (removeChildHoverCheckForTouchExploration()) { + if ((action == MotionEvent.ACTION_HOVER_ENTER + || action == MotionEvent.ACTION_HOVER_MOVE) && pointInDelegateRegion) { + mHoveringTouchDelegate = true; + } + } else { + if ((action == MotionEvent.ACTION_HOVER_ENTER + || action == MotionEvent.ACTION_HOVER_MOVE) + && !pointInHoveredChild(event) + && pointInDelegateRegion) { + mHoveringTouchDelegate = true; + } } } else { - if (action == MotionEvent.ACTION_HOVER_EXIT - || (action == MotionEvent.ACTION_HOVER_MOVE + if (removeChildHoverCheckForTouchExploration()) { + if (action == MotionEvent.ACTION_HOVER_EXIT + || (action == MotionEvent.ACTION_HOVER_MOVE)) { + if (!pointInDelegateRegion) { + mHoveringTouchDelegate = false; + } + } + } else { + if (action == MotionEvent.ACTION_HOVER_EXIT + || (action == MotionEvent.ACTION_HOVER_MOVE && (pointInHoveredChild(event) || !pointInDelegateRegion))) { - mHoveringTouchDelegate = false; + mHoveringTouchDelegate = false; + } } } switch (action) { diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index d0bc57b9afbe..44c1acc34273 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -128,6 +128,13 @@ flag { } flag { + namespace: "accessibility" + name: "remove_child_hover_check_for_touch_exploration" + description: "Remove a check for a hovered child that prevents touch events from being delegated to non-direct descendants" + bug: "304770837" +} + +flag { name: "skip_accessibility_warning_dialog_for_trusted_services" namespace: "accessibility" description: "Skips showing the accessibility warning dialog for trusted services." diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java index 098f65575928..0e66f7ac47b7 100644 --- a/core/java/android/view/inputmethod/InputMethodInfo.java +++ b/core/java/android/view/inputmethod/InputMethodInfo.java @@ -891,12 +891,13 @@ public final class InputMethodInfo implements Parcelable { @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API) @Nullable public Intent createImeLanguageSettingsActivityIntent() { - if (TextUtils.isEmpty(mLanguageSettingsActivityName)) { + final var activityName = !TextUtils.isEmpty(mLanguageSettingsActivityName) + ? mLanguageSettingsActivityName : mSettingsActivityName; + if (TextUtils.isEmpty(activityName)) { return null; } return new Intent(ACTION_IME_LANGUAGE_SETTINGS).setComponent( - new ComponentName(getServiceInfo().packageName, - mLanguageSettingsActivityName) + new ComponentName(getServiceInfo().packageName, activityName) ); } diff --git a/core/java/com/android/internal/widget/MaxHeightFrameLayout.java b/core/java/com/android/internal/widget/MaxHeightFrameLayout.java new file mode 100644 index 000000000000..d65dddd9c5b1 --- /dev/null +++ b/core/java/com/android/internal/widget/MaxHeightFrameLayout.java @@ -0,0 +1,98 @@ +/* + * 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.internal.widget; + +import android.annotation.AttrRes; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.Px; +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.widget.FrameLayout; + +import com.android.internal.R; + +/** + * This custom subclass of FrameLayout enforces that its calculated height be no larger than the + * given maximum height (if any). + * + * @hide + */ +public class MaxHeightFrameLayout extends FrameLayout { + + private int mMaxHeight = Integer.MAX_VALUE; + + public MaxHeightFrameLayout(@NonNull Context context) { + this(context, null); + } + + public MaxHeightFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public MaxHeightFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, + @AttrRes int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public MaxHeightFrameLayout(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + final TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.MaxHeightFrameLayout, defStyleAttr, defStyleRes); + saveAttributeDataForStyleable(context, R.styleable.MaxHeightFrameLayout, + attrs, a, defStyleAttr, defStyleRes); + + setMaxHeight(a.getDimensionPixelSize(R.styleable.MaxHeightFrameLayout_maxHeight, + Integer.MAX_VALUE)); + } + + /** + * Gets the maximum height of this view, in pixels. + * + * @see #setMaxHeight(int) + * + * @attr ref android.R.styleable#MaxHeightFrameLayout_maxHeight + */ + @Px + public int getMaxHeight() { + return mMaxHeight; + } + + /** + * Sets the maximum height this view can have. + * + * @param maxHeight the maximum height, in pixels + * + * @see #getMaxHeight() + * + * @attr ref android.R.styleable#MaxHeightFrameLayout_maxHeight + */ + public void setMaxHeight(@Px int maxHeight) { + mMaxHeight = maxHeight; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (MeasureSpec.getSize(heightMeasureSpec) > mMaxHeight) { + final int mode = MeasureSpec.getMode(heightMeasureSpec); + heightMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxHeight, mode); + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } +} diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp index 30c926c57693..7e2a5ace7e64 100644 --- a/core/jni/platform/host/HostRuntime.cpp +++ b/core/jni/platform/host/HostRuntime.cpp @@ -17,6 +17,8 @@ #include <android-base/logging.h> #include <android-base/properties.h> #include <android/graphics/jni_runtime.h> +#include <android_runtime/AndroidRuntime.h> +#include <jni_wrappers.h> #include <nativehelper/JNIHelp.h> #include <nativehelper/jni_macros.h> #include <unicode/putil.h> @@ -27,9 +29,6 @@ #include <unordered_map> #include <vector> -#include "android_view_InputDevice.h" -#include "core_jni_helpers.h" -#include "jni.h" #ifdef _WIN32 #include <windows.h> #else @@ -38,8 +37,6 @@ #include <sys/stat.h> #endif -#include <iostream> - using namespace std; /* @@ -49,12 +46,6 @@ using namespace std; * (see AndroidRuntime.cpp). */ -static JavaVM* javaVM; -static jclass bridge; -static jclass layoutLog; -static jmethodID getLogId; -static jmethodID logMethodId; - extern int register_android_os_Binder(JNIEnv* env); extern int register_libcore_util_NativeAllocationRegistry_Delegate(JNIEnv* env); @@ -168,28 +159,9 @@ static int register_jni_procs(const std::unordered_map<std::string, RegJNIRec>& } } - if (register_android_graphics_classes(env) < 0) { - return -1; - } - return 0; } -int AndroidRuntime::registerNativeMethods(JNIEnv* env, const char* className, - const JNINativeMethod* gMethods, int numMethods) { - return jniRegisterNativeMethods(env, className, gMethods, numMethods); -} - -JNIEnv* AndroidRuntime::getJNIEnv() { - JNIEnv* env; - if (javaVM->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) return nullptr; - return env; -} - -JavaVM* AndroidRuntime::getJavaVM() { - return javaVM; -} - static vector<string> parseCsv(const string& csvString) { vector<string> result; istringstream stream(csvString); @@ -200,29 +172,6 @@ static vector<string> parseCsv(const string& csvString) { return result; } -void LayoutlibLogger(base::LogId, base::LogSeverity severity, const char* tag, const char* file, - unsigned int line, const char* message) { - JNIEnv* env = AndroidRuntime::getJNIEnv(); - jint logPrio = severity; - jstring tagString = env->NewStringUTF(tag); - jstring messageString = env->NewStringUTF(message); - - jobject bridgeLog = env->CallStaticObjectMethod(bridge, getLogId); - - env->CallVoidMethod(bridgeLog, logMethodId, logPrio, tagString, messageString); - - env->DeleteLocalRef(tagString); - env->DeleteLocalRef(messageString); - env->DeleteLocalRef(bridgeLog); -} - -void LayoutlibAborter(const char* abort_message) { - // Layoutlib should not call abort() as it would terminate Studio. - // Throw an exception back to Java instead. - JNIEnv* env = AndroidRuntime::getJNIEnv(); - jniThrowRuntimeException(env, "The Android framework has encountered a fatal error"); -} - // This method has been copied/adapted from system/core/init/property_service.cpp // If the ro.product.cpu.abilist* properties have not been explicitly // set, derive them from ro.system.product.cpu.abilist* properties. @@ -311,62 +260,49 @@ static void* mmapFile(const char* dataFilePath) { #endif } -static bool init_icu(const char* dataPath) { - void* addr = mmapFile(dataPath); - UErrorCode err = U_ZERO_ERROR; - udata_setCommonData(addr, &err); - if (err != U_ZERO_ERROR) { - return false; +// Loads the ICU data file from the location specified in the system property ro.icu.data.path +static void loadIcuData() { + string icuPath = base::GetProperty("ro.icu.data.path", ""); + if (!icuPath.empty()) { + // Set the location of ICU data + void* addr = mmapFile(icuPath.c_str()); + UErrorCode err = U_ZERO_ERROR; + udata_setCommonData(addr, &err); + if (err != U_ZERO_ERROR) { + ALOGE("Unable to load ICU data\n"); + } } - return true; } -// Creates an array of InputDevice from key character map files -static void init_keyboard(JNIEnv* env, const vector<string>& keyboardPaths) { - jclass inputDevice = FindClassOrDie(env, "android/view/InputDevice"); - jobjectArray inputDevicesArray = - env->NewObjectArray(keyboardPaths.size(), inputDevice, nullptr); - int keyboardId = 1; - - for (const string& path : keyboardPaths) { - base::Result<std::shared_ptr<KeyCharacterMap>> charMap = - KeyCharacterMap::load(path, KeyCharacterMap::Format::BASE); - - InputDeviceInfo info = InputDeviceInfo(); - info.initialize(keyboardId, 0, 0, InputDeviceIdentifier(), - "keyboard " + std::to_string(keyboardId), true, false, - ui::LogicalDisplayId::DEFAULT); - info.setKeyboardType(AINPUT_KEYBOARD_TYPE_ALPHABETIC); - info.setKeyCharacterMap(*charMap); - - jobject inputDeviceObj = android_view_InputDevice_create(env, info); - if (inputDeviceObj) { - env->SetObjectArrayElement(inputDevicesArray, keyboardId - 1, inputDeviceObj); - env->DeleteLocalRef(inputDeviceObj); - } - keyboardId++; - } +static int register_android_core_classes(JNIEnv* env) { + jclass system = FindClassOrDie(env, "java/lang/System"); + jmethodID getPropertyMethod = + GetStaticMethodIDOrDie(env, system, "getProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"); - if (bridge == nullptr) { - bridge = FindClassOrDie(env, "com/android/layoutlib/bridge/Bridge"); - bridge = MakeGlobalRefOrDie(env, bridge); - } - jmethodID setInputManager = GetStaticMethodIDOrDie(env, bridge, "setInputManager", - "([Landroid/view/InputDevice;)V"); - env->CallStaticVoidMethod(bridge, setInputManager, inputDevicesArray); - env->DeleteLocalRef(inputDevicesArray); -} + // Get the names of classes that need to register their native methods + auto nativesClassesJString = + (jstring)env->CallStaticObjectMethod(system, getPropertyMethod, + env->NewStringUTF("core_native_classes"), + env->NewStringUTF("")); + const char* nativesClassesArray = env->GetStringUTFChars(nativesClassesJString, nullptr); + string nativesClassesString(nativesClassesArray); + vector<string> classesToRegister = parseCsv(nativesClassesString); + env->ReleaseStringUTFChars(nativesClassesJString, nativesClassesArray); -} // namespace android + if (register_jni_procs(gRegJNIMap, classesToRegister, env) < 0) { + return JNI_ERR; + } -using namespace android; + return 0; +} // Called right before aborting by LOG_ALWAYS_FATAL. Print the pending exception. void abort_handler(const char* abort_message) { ALOGE("About to abort the process..."); - JNIEnv* env = NULL; - if (javaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + if (env == nullptr) { ALOGE("vm->GetEnv() failed"); return; } @@ -377,107 +313,98 @@ void abort_handler(const char* abort_message) { ALOGE("Aborting because: %s", abort_message); } -JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) { - javaVM = vm; - JNIEnv* env = nullptr; - if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { - return JNI_ERR; - } - - __android_log_set_aborter(abort_handler); +// ------------------ Host implementation of AndroidRuntime ------------------ - init_android_graphics(); +/*static*/ JavaVM* AndroidRuntime::mJavaVM; - // Configuration is stored as java System properties. - // Get a reference to System.getProperty - jclass system = FindClassOrDie(env, "java/lang/System"); - jmethodID getPropertyMethod = - GetStaticMethodIDOrDie(env, system, "getProperty", - "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"); +/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env, const char* className, + const JNINativeMethod* gMethods, + int numMethods) { + return jniRegisterNativeMethods(env, className, gMethods, numMethods); +} - // Java system properties that contain LayoutLib config. The initial values in the map - // are the default values if the property is not specified. - std::unordered_map<std::string, std::string> systemProperties = - {{"core_native_classes", ""}, - {"register_properties_during_load", ""}, - {"icu.data.path", ""}, - {"use_bridge_for_logging", ""}, - {"keyboard_paths", ""}}; - - for (auto& [name, defaultValue] : systemProperties) { - jstring propertyString = - (jstring)env->CallStaticObjectMethod(system, getPropertyMethod, - env->NewStringUTF(name.c_str()), - env->NewStringUTF(defaultValue.c_str())); - const char* propertyChars = env->GetStringUTFChars(propertyString, 0); - systemProperties[name] = string(propertyChars); - env->ReleaseStringUTFChars(propertyString, propertyChars); +/*static*/ JNIEnv* AndroidRuntime::getJNIEnv() { + JNIEnv* env; + JavaVM* vm = AndroidRuntime::getJavaVM(); + if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) { + return nullptr; } - // Get the names of classes that need to register their native methods - vector<string> classesToRegister = parseCsv(systemProperties["core_native_classes"]); + return env; +} - if (systemProperties["register_properties_during_load"] == "true") { - // Set the system properties first as they could be used in the static initialization of - // other classes - if (register_android_os_SystemProperties(env) < 0) { - return JNI_ERR; - } - classesToRegister.erase(find(classesToRegister.begin(), classesToRegister.end(), - "android.os.SystemProperties")); - bridge = FindClassOrDie(env, "com/android/layoutlib/bridge/Bridge"); - bridge = MakeGlobalRefOrDie(env, bridge); - jmethodID setSystemPropertiesMethod = - GetStaticMethodIDOrDie(env, bridge, "setSystemProperties", "()V"); - env->CallStaticVoidMethod(bridge, setSystemPropertiesMethod); - property_initialize_ro_cpu_abilist(); - } +/*static*/ JavaVM* AndroidRuntime::getJavaVM() { + return mJavaVM; +} - if (register_jni_procs(gRegJNIMap, classesToRegister, env) < 0) { +/*static*/ int AndroidRuntime::startReg(JNIEnv* env) { + if (register_android_core_classes(env) < 0) { return JNI_ERR; } - - if (!systemProperties["icu.data.path"].empty()) { - // Set the location of ICU data - bool icuInitialized = init_icu(systemProperties["icu.data.path"].c_str()); - if (!icuInitialized) { - return JNI_ERR; - } + if (register_android_graphics_classes(env) < 0) { + return JNI_ERR; } + return 0; +} - if (systemProperties["use_bridge_for_logging"] == "true") { - layoutLog = FindClassOrDie(env, "com/android/ide/common/rendering/api/ILayoutLog"); - layoutLog = MakeGlobalRefOrDie(env, layoutLog); - logMethodId = GetMethodIDOrDie(env, layoutLog, "logAndroidFramework", - "(ILjava/lang/String;Ljava/lang/String;)V"); - if (bridge == nullptr) { - bridge = FindClassOrDie(env, "com/android/layoutlib/bridge/Bridge"); - bridge = MakeGlobalRefOrDie(env, bridge); - } - getLogId = GetStaticMethodIDOrDie(env, bridge, "getLog", - "()Lcom/android/ide/common/rendering/api/ILayoutLog;"); - android::base::SetLogger(LayoutlibLogger); - android::base::SetAborter(LayoutlibAborter); - } else { - // initialize logging, so ANDROD_LOG_TAGS env variable is respected - android::base::InitLogging(nullptr, android::base::StderrLogger); - } +void AndroidRuntime::onVmCreated(JNIEnv* env) { + env->GetJavaVM(&mJavaVM); +} + +void AndroidRuntime::onStarted() { + property_initialize_ro_cpu_abilist(); + loadIcuData(); // Use English locale for number format to ensure correct parsing of floats when using strtof setlocale(LC_NUMERIC, "en_US.UTF-8"); +} - if (!systemProperties["keyboard_paths"].empty()) { - vector<string> keyboardPaths = parseCsv(systemProperties["keyboard_paths"]); - init_keyboard(env, keyboardPaths); - } else { - fprintf(stderr, "Skip initializing keyboard\n"); +void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + // Register native functions. + if (startReg(env) < 0) { + ALOGE("Unable to register all android native methods\n"); } + onStarted(); +} - return JNI_VERSION_1_6; +AndroidRuntime::AndroidRuntime(char* argBlockStart, const size_t argBlockLength) + : mExitWithoutCleanup(false), mArgBlockStart(argBlockStart), mArgBlockLength(argBlockLength) { + init_android_graphics(); } -JNIEXPORT void JNI_OnUnload(JavaVM* vm, void*) { +AndroidRuntime::~AndroidRuntime() {} + +// Version of AndroidRuntime to run on host +class HostRuntime : public AndroidRuntime { +public: + HostRuntime() : AndroidRuntime(nullptr, 0) {} + + void onVmCreated(JNIEnv* env) override { + AndroidRuntime::onVmCreated(env); + // initialize logging, so ANDROD_LOG_TAGS env variable is respected + android::base::InitLogging(nullptr, android::base::StderrLogger, abort_handler); + } + + void onStarted() override { + AndroidRuntime::onStarted(); + } +}; + +} // namespace android + +using namespace android; + +JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) { JNIEnv* env = nullptr; - vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6); - env->DeleteGlobalRef(bridge); - env->DeleteGlobalRef(layoutLog); + if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + return JNI_ERR; + } + + Vector<String8> args; + HostRuntime runtime; + + runtime.onVmCreated(env); + runtime.start("HostRuntime", args, false); + + return JNI_VERSION_1_6; } diff --git a/core/res/res/drawable/ic_ime_switcher_new.xml b/core/res/res/drawable/ic_ime_switcher_new.xml new file mode 100644 index 000000000000..04f4a250b3ec --- /dev/null +++ b/core/res/res/drawable/ic_ime_switcher_new.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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="20dp" + android:height="20dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM18.92,8h-2.95c-0.32,-1.25 -0.78,-2.45 -1.38,-3.56 1.84,0.63 3.37,1.91 4.33,3.56zM12,4.04c0.83,1.2 1.48,2.53 1.91,3.96h-3.82c0.43,-1.43 1.08,-2.76 1.91,-3.96zM4.26,14C4.1,13.36 4,12.69 4,12s0.1,-1.36 0.26,-2h3.38c-0.08,0.66 -0.14,1.32 -0.14,2 0,0.68 0.06,1.34 0.14,2L4.26,14zM5.08,16h2.95c0.32,1.25 0.78,2.45 1.38,3.56 -1.84,-0.63 -3.37,-1.9 -4.33,-3.56zM8.03,8L5.08,8c0.96,-1.66 2.49,-2.93 4.33,-3.56C8.81,5.55 8.35,6.75 8.03,8zM12,19.96c-0.83,-1.2 -1.48,-2.53 -1.91,-3.96h3.82c-0.43,1.43 -1.08,2.76 -1.91,3.96zM14.34,14L9.66,14c-0.09,-0.66 -0.16,-1.32 -0.16,-2 0,-0.68 0.07,-1.35 0.16,-2h4.68c0.09,0.65 0.16,1.32 0.16,2 0,0.68 -0.07,1.34 -0.16,2zM14.59,19.56c0.6,-1.11 1.06,-2.31 1.38,-3.56h2.95c-0.96,1.65 -2.49,2.93 -4.33,3.56zM16.36,14c0.08,-0.66 0.14,-1.32 0.14,-2 0,-0.68 -0.06,-1.34 -0.14,-2h3.38c0.16,0.64 0.26,1.31 0.26,2s-0.1,1.36 -0.26,2h-3.38z" + android:fillColor="#FFFFFFFF"/> +</vector> diff --git a/core/res/res/drawable/input_method_switch_button.xml b/core/res/res/drawable/input_method_switch_button.xml new file mode 100644 index 000000000000..396d81ed87f6 --- /dev/null +++ b/core/res/res/drawable/input_method_switch_button.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<inset xmlns:android="http://schemas.android.com/apk/res/android" + android:insetTop="6dp" + android:insetBottom="6dp"> + <ripple android:color="?android:attr/colorControlHighlight"> + <item android:id="@android:id/mask"> + <shape android:shape="rectangle"> + <corners android:radius="28dp"/> + <solid android:color="@color/white"/> + </shape> + </item> + + <item> + <shape android:shape="rectangle"> + <corners android:radius="28dp"/> + <solid android:color="@color/transparent"/> + <stroke android:color="?attr/materialColorPrimary" + android:width="1dp"/> + <padding android:left="16dp" + android:top="8dp" + android:right="16dp" + android:bottom="8dp"/> + </shape> + </item> + </ripple> +</inset> diff --git a/core/res/res/drawable/input_method_switch_item_background.xml b/core/res/res/drawable/input_method_switch_item_background.xml new file mode 100644 index 000000000000..eb7a24691f37 --- /dev/null +++ b/core/res/res/drawable/input_method_switch_item_background.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="@color/list_highlight_material"> + <item android:id="@id/mask"> + <shape android:shape="rectangle"> + <corners android:radius="28dp"/> + <solid android:color="@color/white"/> + </shape> + </item> + + <item> + <selector> + <item android:state_activated="true"> + <shape android:shape="rectangle"> + <corners android:radius="28dp"/> + <solid android:color="?attr/materialColorSecondaryContainer"/> + </shape> + </item> + </selector> + </item> +</ripple> diff --git a/core/res/res/layout/input_method_switch_dialog_new.xml b/core/res/res/layout/input_method_switch_dialog_new.xml new file mode 100644 index 000000000000..5a4d6b14a52b --- /dev/null +++ b/core/res/res/layout/input_method_switch_dialog_new.xml @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <com.android.internal.widget.MaxHeightFrameLayout + android:layout_width="320dp" + android:layout_height="0dp" + android:layout_weight="1" + android:maxHeight="373dp"> + + <com.android.internal.widget.RecyclerView + android:id="@+id/list" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingVertical="8dp" + android:clipToPadding="false" + android:layoutManager="com.android.internal.widget.LinearLayoutManager"/> + + </com.android.internal.widget.MaxHeightFrameLayout> + + <LinearLayout + style="?android:attr/buttonBarStyle" + android:id="@+id/button_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:paddingHorizontal="16dp" + android:paddingTop="8dp" + android:paddingBottom="16dp" + android:visibility="gone"> + + <Space + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1"/> + + <Button + style="?attr/buttonBarButtonStyle" + android:id="@+id/button1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@drawable/input_method_switch_button" + android:layout_gravity="end" + android:text="@string/input_method_language_settings" + android:fontFamily="google-sans-text" + android:textAppearance="?attr/textAppearance" + android:visibility="gone"/> + + </LinearLayout> + +</LinearLayout> diff --git a/core/res/res/layout/input_method_switch_item_new.xml b/core/res/res/layout/input_method_switch_item_new.xml new file mode 100644 index 000000000000..16a97c4b39ee --- /dev/null +++ b/core/res/res/layout/input_method_switch_item_new.xml @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingHorizontal="16dp" + android:paddingBottom="8dp"> + + <View + android:id="@+id/divider" + android:layout_width="match_parent" + android:layout_height="1dp" + android:background="?attr/materialColorSurfaceVariant" + android:layout_marginStart="20dp" + android:layout_marginTop="8dp" + android:layout_marginEnd="24dp" + android:layout_marginBottom="12dp" + android:visibility="gone"/> + + <TextView + android:id="@+id/header_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:ellipsize="end" + android:singleLine="true" + android:fontFamily="google-sans-text" + android:textAppearance="?attr/textAppearance" + android:textColor="?attr/materialColorPrimary" + android:visibility="gone"/> + + <LinearLayout + android:id="@+id/list_item" + android:layout_width="match_parent" + android:layout_height="72dp" + android:background="@drawable/input_method_switch_item_background" + android:gravity="center_vertical" + android:orientation="horizontal" + android:paddingStart="20dp" + android:paddingEnd="24dp"> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:gravity="start|center_vertical" + android:orientation="vertical"> + + <TextView + android:id="@+id/text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ellipsize="marquee" + android:singleLine="true" + android:fontFamily="google-sans-text" + android:textAppearance="?attr/textAppearanceListItem"/> + + </LinearLayout> + + <ImageView + android:id="@+id/image" + android:layout_width="24dp" + android:layout_height="24dp" + android:gravity="center_vertical" + android:layout_marginStart="12dp" + android:src="@drawable/ic_check_24dp" + android:tint="?attr/materialColorOnSurface" + android:visibility="gone"/> + + </LinearLayout> + +</LinearLayout> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 0975eda3f9ff..7cc9e13db5cf 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -5243,6 +5243,11 @@ the VISIBLE or INVISIBLE state when measuring. Defaults to false. --> <attr name="measureAllChildren" format="boolean" /> </declare-styleable> + <!-- @hide --> + <declare-styleable name="MaxHeightFrameLayout"> + <!-- An optional argument to supply a maximum height for this view. --> + <attr name="maxHeight" format="dimension" /> + </declare-styleable> <declare-styleable name="ExpandableListView"> <!-- Indicator shown beside the group View. This can be a stateful Drawable. --> <attr name="groupIndicator" format="reference" /> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 46b154163224..ec865f6c376f 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -3880,6 +3880,8 @@ <!-- Title of the pop-up dialog in which the user switches keyboard, also known as input method. --> <string name="select_input_method">Choose input method</string> + <!-- Button to access the language settings of the current input method. [CHAR LIMIT=50]--> + <string name="input_method_language_settings">Language Settings</string> <!-- Summary text of a toggle switch to enable/disable use of the IME while a physical keyboard is connected --> <string name="show_ime">Keep it on screen while physical keyboard is active</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index c50b961f74cd..fcafdaed8d1a 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1577,6 +1577,8 @@ <java-symbol type="layout" name="input_method" /> <java-symbol type="layout" name="input_method_extract_view" /> <java-symbol type="layout" name="input_method_switch_item" /> + <java-symbol type="layout" name="input_method_switch_item_new" /> + <java-symbol type="layout" name="input_method_switch_dialog_new" /> <java-symbol type="layout" name="input_method_switch_dialog_title" /> <java-symbol type="layout" name="js_prompt" /> <java-symbol type="layout" name="list_content_simple" /> @@ -2552,6 +2554,7 @@ <java-symbol type="dimen" name="input_method_nav_key_button_ripple_max_width" /> <java-symbol type="drawable" name="ic_ime_nav_back" /> <java-symbol type="drawable" name="ic_ime_switcher" /> + <java-symbol type="drawable" name="ic_ime_switcher_new" /> <java-symbol type="id" name="input_method_nav_back" /> <java-symbol type="id" name="input_method_nav_buttons" /> <java-symbol type="id" name="input_method_nav_center_group" /> @@ -5400,6 +5403,7 @@ <java-symbol type="style" name="Theme.DeviceDefault.DialogWhenLarge" /> <java-symbol type="style" name="Theme.DeviceDefault.DocumentsUI" /> <java-symbol type="style" name="Theme.DeviceDefault.InputMethod" /> + <java-symbol type="style" name="Theme.DeviceDefault.InputMethodSwitcherDialog" /> <java-symbol type="style" name="Theme.DeviceDefault.Light.DarkActionBar" /> <java-symbol type="style" name="Theme.DeviceDefault.Light.Dialog.FixedSize" /> <java-symbol type="style" name="Theme.DeviceDefault.Light.Dialog.MinWidth" /> diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml index 382ff0441fd2..f5c67387cb92 100644 --- a/core/res/res/values/themes_device_defaults.xml +++ b/core/res/res/values/themes_device_defaults.xml @@ -6179,4 +6179,10 @@ easier. <item name="colorListDivider">@color/list_divider_opacity_device_default_light</item> <item name="opacityListDivider">@color/list_divider_opacity_device_default_light</item> </style> + + <!-- Device default theme for the Input Method Switcher dialog. --> + <style name="Theme.DeviceDefault.InputMethodSwitcherDialog" parent="Theme.DeviceDefault.Dialog.Alert.DayNight"> + <item name="windowMinWidthMajor">@null</item> + <item name="windowMinWidthMinor">@null</item> + </style> </resources> diff --git a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/Android.bp b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/Android.bp deleted file mode 100644 index 1fb5f2c0789b..000000000000 --- a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/Android.bp +++ /dev/null @@ -1,30 +0,0 @@ -package { - // 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" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -android_test { - name: "BatteryUsageStatsProtoTests", - srcs: ["src/**/*.java"], - - static_libs: [ - "androidx.test.rules", - "junit", - "mockito-target-minus-junit4", - "platform-test-annotations", - "platformprotosnano", - "statsdprotolite", - "truth", - ], - - libs: ["android.test.runner"], - - platform_apis: true, - certificate: "platform", - - test_suites: ["device-tests"], -} diff --git a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/AndroidManifest.xml b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/AndroidManifest.xml deleted file mode 100644 index 9128dca2080b..000000000000 --- a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/AndroidManifest.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2021 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.frameworks.core.batteryusagestatsprototests"> - - <uses-permission android:name="android.permission.BATTERY_STATS"/> - - <instrumentation - android:name="androidx.test.runner.AndroidJUnitRunner" - android:targetPackage="com.android.frameworks.core.batteryusagestatsprototests" - android:label="BatteryUsageStats Proto Tests" /> - -</manifest> diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt index 235b9bf7b9fd..fc3dc1465dff 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt @@ -168,6 +168,16 @@ object PhysicsAnimatorTestUtils { } } + /** Whether any animation is currently running. */ + @JvmStatic + fun isAnyAnimationRunning(): Boolean { + for (target in allAnimatedObjects) { + val animator = PhysicsAnimator.getInstance(target) + if (animator.isRunning()) return true + } + return false + } + /** * Blocks the calling thread until the first animation frame in which predicate returns true. If * the given object isn't animating, returns without blocking. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index bb239adfe73f..a9fdea34515e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -114,6 +114,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @Nullable private BackNavigationInfo mBackNavigationInfo; + private boolean mReceivedNullNavigationInfo = false; private final IActivityTaskManager mActivityTaskManager; private final Context mContext; private final ContentResolver mContentResolver; @@ -430,7 +431,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mThresholdCrossed = true; // There was no focus window when calling startBackNavigation, still pilfer pointers so // the next focus window won't receive motion events. - if (mBackNavigationInfo == null) { + if (mBackNavigationInfo == null && mReceivedNullNavigationInfo) { tryPilferPointers(); return; } @@ -553,6 +554,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Received backNavigationInfo:%s", backNavigationInfo); if (backNavigationInfo == null) { ProtoLog.e(WM_SHELL_BACK_PREVIEW, "Received BackNavigationInfo is null."); + mReceivedNullNavigationInfo = true; cancelLatencyTracking(); tryPilferPointers(); return; @@ -909,6 +911,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mPointersPilfered = false; mShellBackAnimationRegistry.resetDefaultCrossActivity(); cancelLatencyTracking(); + mReceivedNullNavigationInfo = false; if (mBackNavigationInfo != null) { mPreviousNavigationType = mBackNavigationInfo.getType(); mBackNavigationInfo.onBackNavigationFinished(triggerBack); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 9a1a8a20ae0e..9e6099f2e4cc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -983,6 +983,7 @@ class DesktopTasksController( ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: skip keyguard is locked") return null } + val wct = WindowContainerTransaction() if (!isDesktopModeShowing(task.displayId)) { ProtoLog.d( WM_SHELL_DESKTOP_MODE, @@ -990,12 +991,17 @@ class DesktopTasksController( " taskId=%d", task.taskId ) - return WindowContainerTransaction().also { wct -> - bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId) - wct.reorder(task.token, true) + // We are outside of desktop mode and already existing desktop task is being launched. + // We should make this task go to fullscreen instead of freeform. Note that this means + // any re-launch of a freeform window outside of desktop will be in fullscreen. + if (desktopModeTaskRepository.isActiveTask(task.taskId)) { + addMoveToFullscreenChanges(wct, task) + return wct } + bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId) + wct.reorder(task.token, true) + return wct } - val wct = WindowContainerTransaction() if (useDesktopOverrideDensity()) { wct.setDensityDpi(task.token, DESKTOP_DENSITY_OVERRIDE) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index 9539a456502f..d001b2c09f85 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -466,7 +466,7 @@ public class RecentTasksController implements TaskStackListenerCallback, @Nullable WindowContainerToken ignoreTaskToken) { List<ActivityManager.RunningTaskInfo> tasks = mActivityTaskManager.getTasks(2, false /* filterOnlyVisibleRecents */); - for (int i = tasks.size() - 1; i >= 0; i--) { + for (int i = 0; i < tasks.size(); i++) { final ActivityManager.RunningTaskInfo task = tasks.get(i); if (task.token.equals(ignoreTaskToken)) { continue; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index da886863cc1f..6002c21ccb24 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -1326,6 +1326,22 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun handleRequest_freeformTask_relaunchActiveTask_taskBecomesUndefined() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + + val freeformTask = setUpFreeformTask() + markTaskHidden(freeformTask) + + val wct = + controller.handleRequest(Binder(), createTransition(freeformTask)) + + // Should become undefined as the TDA is set to fullscreen. It will inherit from the TDA. + assertNotNull(wct, "should handle request") + assertThat(wct.changes[freeformTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_UNDEFINED) + } + + @Test @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) fun handleRequest_freeformTask_desktopWallpaperDisabled_freeformNotVisible_reorderedToTop() { assumeTrue(ENABLE_SHELL_TRANSITIONS) @@ -1574,7 +1590,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION, ) - fun handleRequest_backTransition_singleActiveTaskNoToken_wallpaperDisabled_backNavDisabled_doesNotHandle() { + fun handleRequest_backTransition_singleTaskNoToken_noWallpaper_noBackNav_doesNotHandle() { val task = setUpFreeformTask() val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) @@ -1587,7 +1603,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_backTransition_singleActiveTaskNoToken_wallpaperEnabled_backNavEnabled_removesTask() { + fun handleRequest_backTransition_singleTaskNoToken_withWallpaper_withBackNav_removesTask() { val task = setUpFreeformTask() val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) @@ -1598,7 +1614,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun handleRequest_backTransition_singleActiveTaskNoToken_backNavigationDisabled_doesNotHandle() { + fun handleRequest_backTransition_singleTaskNoToken_noBackNav_doesNotHandle() { val task = setUpFreeformTask() val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) @@ -1611,7 +1627,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_backTransition_singleActiveTaskWithToken_wallpaperDisabled_backNavDisabled_doesNotHandle() { + fun handleRequest_backTransition_singleTaskWithToken_noWallpaper_noBackNav_doesNotHandle() { val task = setUpFreeformTask() desktopModeTaskRepository.wallpaperActivityToken = MockToken().token() @@ -1625,7 +1641,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_backTransition_singleActiveTaskWithToken_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() { + fun handleRequest_backTransition_singleTask_withWallpaper_withBackNav_removesWallpaperAndTask() { val task = setUpFreeformTask() val wallpaperToken = MockToken().token() @@ -1640,7 +1656,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun handleRequest_backTransition_singleActiveTaskWithToken_backNavigationDisabled_removesWallpaper() { + fun handleRequest_backTransition_singleTaskWithToken_noBackNav_removesWallpaper() { val task = setUpFreeformTask() val wallpaperToken = MockToken().token() @@ -1656,7 +1672,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_backTransition_multipleTasks_wallpaperDisabled_backNavDisabled_doesNotHandle() { + fun handleRequest_backTransition_multipleTasks_noWallpaper_noBackNav_doesNotHandle() { val task1 = setUpFreeformTask() setUpFreeformTask() @@ -1671,7 +1687,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_backTransition_multipleTasks_wallpaperEnabled_backNavEnabled_removesTask() { + fun handleRequest_backTransition_multipleTasks_withWallpaper_withBackNav_removesTask() { val task1 = setUpFreeformTask() setUpFreeformTask() @@ -1684,7 +1700,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun handleRequest_backTransition_multipleTasks_backNavigationDisabled_doesNotHandle() { + fun handleRequest_backTransition_multipleTasks_noBackNav_doesNotHandle() { val task1 = setUpFreeformTask() setUpFreeformTask() @@ -1699,7 +1715,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_backTransition_multipleTasksSingleNonClosing_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() { + fun handleRequest_backTransition_multipleTasksSingleNonClosing_removesWallpaperAndTask() { val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val wallpaperToken = MockToken().token() @@ -1716,7 +1732,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun handleRequest_backTransition_multipleTasksSingleNonClosing_backNavigationDisabled_removesWallpaper() { + fun handleRequest_backTransition_multipleTasksSingleNonClosing_noBackNav_removesWallpaper() { val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val wallpaperToken = MockToken().token() @@ -1734,7 +1750,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_backTransition_multipleTasksSingleNonMinimized_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() { + fun handleRequest_backTransition_multipleTasksSingleNonMinimized_removesWallpaperAndTask() { val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val wallpaperToken = MockToken().token() @@ -1751,7 +1767,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun handleRequest_backTransition_multipleTasksSingleNonMinimized_backNavigationDisabled_removesWallpaper() { + fun handleRequest_backTransition_multipleTasksSingleNonMinimized_noBackNav_removesWallpaper() { val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val wallpaperToken = MockToken().token() @@ -1769,7 +1785,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_backTransition_nonMinimizadTask_wallpaperEnabled_backNavEnabled_removesWallpaper() { + fun handleRequest_backTransition_nonMinimizadTask_withWallpaper_withBackNav_removesWallpaper() { val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val wallpaperToken = MockToken().token() @@ -1789,7 +1805,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_closeTransition_singleActiveTaskNoToken_wallpaperDisabled_backNavDisabled_doesNotHandle() { + fun handleRequest_closeTransition_singleTaskNoToken_noWallpaper_noBackNav_doesNotHandle() { val task = setUpFreeformTask() val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) @@ -1802,7 +1818,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_closeTransition_singleActiveTaskNoToken_wallpaperEnabled_backNavEnabled_removesTask() { + fun handleRequest_closeTransition_singleTaskNoToken_withWallpaper_withBackNav_removesTask() { val task = setUpFreeformTask() val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) @@ -1813,7 +1829,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun handleRequest_closeTransition_singleActiveTaskNoToken_backNavigationDisabled_doesNotHandle() { + fun handleRequest_closeTransition_singleTaskNoToken_noBackNav_doesNotHandle() { val task = setUpFreeformTask() val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) @@ -1826,7 +1842,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_closeTransition_singleActiveTaskWithToken_wallpaperDisabled_backNavDisabled_doesNotHandle() { + fun handleRequest_closeTransition_singleTaskWithToken_noWallpaper_noBackNav_doesNotHandle() { val task = setUpFreeformTask() desktopModeTaskRepository.wallpaperActivityToken = MockToken().token() @@ -1840,7 +1856,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_closeTransition_singleActiveTaskWithToken_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() { + fun handleRequest_closeTransition_singleTaskWithToken_removesWallpaperAndTask() { val task = setUpFreeformTask() val wallpaperToken = MockToken().token() @@ -1855,7 +1871,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun handleRequest_closeTransition_singleActiveTaskWithToken_backNavigationDisabled_removesWallpaper() { + fun handleRequest_closeTransition_singleTaskWithToken_withWallpaper_noBackNav_removesWallpaper() { val task = setUpFreeformTask() val wallpaperToken = MockToken().token() @@ -1871,7 +1887,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_closeTransition_multipleTasks_wallpaperDisabled_backNavDisabled_doesNotHandle() { + fun handleRequest_closeTransition_multipleTasks_noWallpaper_noBackNav_doesNotHandle() { val task1 = setUpFreeformTask() setUpFreeformTask() @@ -1886,7 +1902,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_closeTransition_multipleTasks_wallpaperEnabled_backNavEnabled_removesTask() { + fun handleRequest_closeTransition_multipleTasks_withWallpaper_withBackNav_removesTask() { val task1 = setUpFreeformTask() setUpFreeformTask() @@ -1900,7 +1916,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun handleRequest_closeTransition_multipleTasksFlagEnabled_backNavigationDisabled_doesNotHandle() { + fun handleRequest_closeTransition_multipleTasksFlagEnabled_noBackNav_doesNotHandle() { val task1 = setUpFreeformTask() setUpFreeformTask() @@ -1915,7 +1931,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_closeTransition_multipleTasksSingleNonClosing_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() { + fun handleRequest_closeTransition_multipleTasksSingleNonClosing_removesWallpaperAndTask() { val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val wallpaperToken = MockToken().token() @@ -1932,7 +1948,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun handleRequest_closeTransition_multipleTasksSingleNonClosing_backNavigationDisabled_removesWallpaper() { + fun handleRequest_closeTransition_multipleTasksSingleNonClosing_noBackNav_removesWallpaper() { val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val wallpaperToken = MockToken().token() @@ -1950,7 +1966,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_closeTransition_multipleTasksOneNonMinimized_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() { + fun handleRequest_closeTransition_multipleTasksOneNonMinimized_removesWallpaperAndTask() { val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val wallpaperToken = MockToken().token() @@ -1967,7 +1983,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun handleRequest_closeTransition_multipleTasksSingleNonMinimized_backNavigationDisabled_removesWallpaper() { + fun handleRequest_closeTransition_multipleTasksSingleNonMinimized_noBackNav_removesWallpaper() { val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val wallpaperToken = MockToken().token() @@ -1985,7 +2001,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_closeTransition_minimizadTask_wallpaperEnabled_backNavEnabled_removesWallpaper() { + fun handleRequest_closeTransition_minimizadTask_withWallpaper_withBackNav_removesWallpaper() { val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val wallpaperToken = MockToken().token() diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntrySliceData.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntrySliceData.kt deleted file mode 100644 index fc551a88f6c5..000000000000 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntrySliceData.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settingslib.spa.framework.common - -import androidx.lifecycle.LiveData -import androidx.slice.Slice -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch - -open class EntrySliceData : LiveData<Slice?>() { - private val asyncRunnerScope = CoroutineScope(Dispatchers.IO) - private var asyncRunnerJob: Job? = null - private var asyncActionJob: Job? = null - private var isActive = false - - open suspend fun asyncRunner() {} - - open suspend fun asyncAction() {} - - override fun onActive() { - asyncRunnerJob?.cancel() - asyncRunnerJob = asyncRunnerScope.launch { asyncRunner() } - isActive = true - } - - override fun onInactive() { - asyncRunnerJob?.cancel() - asyncRunnerJob = null - asyncActionJob?.cancel() - asyncActionJob = null - isActive = false - } - - fun isActive(): Boolean { - return isActive - } - - fun doAction() { - asyncActionJob?.cancel() - asyncActionJob = asyncRunnerScope.launch { asyncAction() } - } -} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt index 90581b99160d..3f53091c8a63 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt @@ -16,7 +16,6 @@ package com.android.settingslib.spa.framework.common -import android.net.Uri import android.os.Bundle import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -42,7 +41,6 @@ val LocalEntryDataProvider = typealias UiLayerRenderer = @Composable (arguments: Bundle?) -> Unit typealias StatusDataGetter = (arguments: Bundle?) -> EntryStatusData? typealias SearchDataGetter = (arguments: Bundle?) -> EntrySearchData? -typealias SliceDataGetter = (sliceUri: Uri, arguments: Bundle?) -> EntrySliceData? /** * Defines data of a Settings entry. @@ -80,9 +78,6 @@ data class SettingsEntry( // If so, for instance, we'll reindex its status for search. val hasMutableStatus: Boolean = false, - // Indicate whether the entry has SliceProvider support. - val hasSliceSupport: Boolean = false, - /** * ======================================== * Defines entry APIs to get data here. @@ -102,12 +97,6 @@ data class SettingsEntry( private val searchDataImpl: SearchDataGetter = { null }, /** - * API to get Slice data of this entry. The Slice data is implemented as a LiveData, - * and is associated with the Slice's lifecycle (pin / unpin) by the framework. - */ - private val sliceDataImpl: SliceDataGetter = { _: Uri, _: Bundle? -> null }, - - /** * API to Render UI of this entry directly. For now, we use it in the internal injection, to * support the case that the injection page owner wants to maintain both data and UI of the * injected entry. In the long term, we may deprecate the @Composable Page() API in SPP, and @@ -137,10 +126,6 @@ data class SettingsEntry( return searchDataImpl(fullArgument(runtimeArguments)) } - fun getSliceData(sliceUri: Uri, runtimeArguments: Bundle? = null): EntrySliceData? { - return sliceDataImpl(sliceUri, fullArgument(runtimeArguments)) - } - @Composable fun UiLayout(runtimeArguments: Bundle? = null) { val arguments = remember { fullArgument(runtimeArguments) } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryBuilder.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryBuilder.kt index 0d489e895017..085c3c6e026b 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryBuilder.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryBuilder.kt @@ -16,7 +16,6 @@ package com.android.settingslib.spa.framework.common -import android.net.Uri import android.os.Bundle import androidx.compose.runtime.remember import com.android.settingslib.spa.framework.util.genEntryId @@ -36,13 +35,11 @@ class SettingsEntryBuilder(private val name: String, private val owner: Settings private var isAllowSearch: Boolean = false private var isSearchDataDynamic: Boolean = false private var hasMutableStatus: Boolean = false - private var hasSliceSupport: Boolean = false // Functions private var uiLayoutFn: UiLayerRenderer = { } private var statusDataFn: StatusDataGetter = { null } private var searchDataFn: SearchDataGetter = { null } - private var sliceDataFn: SliceDataGetter = { _: Uri, _: Bundle? -> null } fun build(): SettingsEntry { val page = fromPage ?: owner @@ -62,12 +59,10 @@ class SettingsEntryBuilder(private val name: String, private val owner: Settings isAllowSearch = isEnabled && isAllowSearch, isSearchDataDynamic = isSearchDataDynamic, hasMutableStatus = hasMutableStatus, - hasSliceSupport = isEnabled && hasSliceSupport, // functions statusDataImpl = statusDataFn, searchDataImpl = searchDataFn, - sliceDataImpl = sliceDataFn, uiLayoutImpl = uiLayoutFn, ) } @@ -123,12 +118,6 @@ class SettingsEntryBuilder(private val name: String, private val owner: Settings return this } - fun setSliceDataFn(fn: SliceDataGetter): SettingsEntryBuilder { - this.sliceDataFn = fn - this.hasSliceSupport = true - return this - } - fun setUiLayoutFn(fn: UiLayerRenderer): SettingsEntryBuilder { this.uiLayoutFn = fn return this diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt index 6e5132bbb53e..11ae9e92b5ff 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt @@ -74,7 +74,7 @@ abstract class SpaEnvironment(context: Context) { // Set your SpaLogger implementation, for any SPA events logging. open val logger: SpaLogger = object : SpaLogger {} - // Specify class name of browse activity and slice broadcast receiver, which is used to + // Specify class name of browse activity, which is used to // generate the necessary intents. open val browseActivityClass: Class<out Activity>? = null diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt deleted file mode 100644 index ec89c7cd3a6c..000000000000 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settingslib.spa.slice - -import android.net.Uri -import android.os.Bundle -import com.android.settingslib.spa.framework.util.KEY_DESTINATION -import com.android.settingslib.spa.framework.util.KEY_HIGHLIGHT_ENTRY - -// Defines SliceUri, which contains special query parameters: -// -- KEY_DESTINATION: The route that this slice is navigated to. -// -- KEY_HIGHLIGHT_ENTRY: The entry id of this slice -// Other parameters can considered as runtime parameters. -// Use {entryId, runtimeParams} as the unique Id of this Slice. -typealias SliceUri = Uri - -fun SliceUri.getEntryId(): String? { - return getQueryParameter(KEY_HIGHLIGHT_ENTRY) -} - -fun Uri.Builder.appendSpaParams( - destination: String? = null, - entryId: String? = null, - runtimeArguments: Bundle? = null -): Uri.Builder { - if (destination != null) appendQueryParameter(KEY_DESTINATION, destination) - if (entryId != null) appendQueryParameter(KEY_HIGHLIGHT_ENTRY, entryId) - if (runtimeArguments != null) { - for (key in runtimeArguments.keySet()) { - appendQueryParameter(key, runtimeArguments.getString(key, "")) - } - } - return this -} - diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt index ce349795c8c6..d0a040c003b5 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt @@ -17,7 +17,6 @@ package com.android.settingslib.spa.framework.common import android.content.Context -import android.net.Uri import androidx.compose.runtime.Composable import androidx.compose.ui.test.junit4.createComposeRule import androidx.core.os.bundleOf @@ -25,8 +24,6 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settingslib.spa.framework.util.genEntryId import com.android.settingslib.spa.framework.util.genPageId -import com.android.settingslib.spa.slice.appendSpaParams -import com.android.settingslib.spa.slice.getEntryId import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest import com.android.settingslib.spa.tests.testutils.createSettingsPage import com.google.common.truth.Truth.assertThat @@ -76,7 +73,6 @@ class SettingsEntryTest { assertThat(entry.isAllowSearch).isFalse() assertThat(entry.isSearchDataDynamic).isFalse() assertThat(entry.hasMutableStatus).isFalse() - assertThat(entry.hasSliceSupport).isFalse() } @Test @@ -137,7 +133,6 @@ class SettingsEntryTest { .setIsSearchDataDynamic(false) .setHasMutableStatus(true) .setSearchDataFn { null } - .setSliceDataFn { _, _ -> null } val entry = entryBuilder.build() assertThat(entry.id).isEqualTo(genEntryId("myEntry", owner)) assertThat(entry.label).isEqualTo("myEntryDisplay") @@ -146,7 +141,6 @@ class SettingsEntryTest { assertThat(entry.isAllowSearch).isTrue() assertThat(entry.isSearchDataDynamic).isFalse() assertThat(entry.hasMutableStatus).isTrue() - assertThat(entry.hasSliceSupport).isTrue() // Test disabled Spp val ownerDisabled = createSettingsPage("SppDisabled") @@ -156,7 +150,6 @@ class SettingsEntryTest { .setIsSearchDataDynamic(false) .setHasMutableStatus(true) .setSearchDataFn { null } - .setSliceDataFn { _, _ -> null } val entryDisabled = entryBuilderDisabled.build() assertThat(entryDisabled.id).isEqualTo(genEntryId("myEntry", ownerDisabled)) assertThat(entryDisabled.label).isEqualTo("myEntryDisplay") @@ -165,7 +158,6 @@ class SettingsEntryTest { assertThat(entryDisabled.isAllowSearch).isFalse() assertThat(entryDisabled.isSearchDataDynamic).isFalse() assertThat(entryDisabled.hasMutableStatus).isTrue() - assertThat(entryDisabled.hasSliceSupport).isFalse() // Clear search data fn val entry2 = entryBuilder.clearSearchDataFn().build() @@ -181,7 +173,6 @@ class SettingsEntryTest { assertThat(entry3.isAllowSearch).isFalse() assertThat(entry3.isSearchDataDynamic).isFalse() assertThat(entry3.hasMutableStatus).isTrue() - assertThat(entry3.hasSliceSupport).isFalse() } @Test @@ -200,7 +191,6 @@ class SettingsEntryTest { assertThat(entry.isAllowSearch).isTrue() assertThat(entry.isSearchDataDynamic).isFalse() assertThat(entry.hasMutableStatus).isFalse() - assertThat(entry.hasSliceSupport).isFalse() val searchData = entry.getSearchData(rtArguments) val statusData = entry.getStatusData(rtArguments) assertThat(searchData?.title).isEqualTo("myTitle") @@ -208,25 +198,4 @@ class SettingsEntryTest { assertThat(statusData?.isDisabled).isTrue() assertThat(statusData?.isSwitchOff).isTrue() } - - @Test - fun testSetSliceDataFn() { - SpaEnvironmentFactory.reset(spaEnvironment) - val owner = createSettingsPage("SppHome") - val entryId = genEntryId("myEntry", owner) - val emptySliceData = EntrySliceData() - - val entryBuilder = SettingsEntryBuilder.create(owner, "myEntry").setSliceDataFn { uri, _ -> - return@setSliceDataFn if (uri.getEntryId() == entryId) emptySliceData else null - } - val entry = entryBuilder.build() - assertThat(entry.id).isEqualTo(entryId) - assertThat(entry.hasSliceSupport).isTrue() - assertThat(entry.getSliceData(Uri.EMPTY)).isNull() - assertThat( - entry.getSliceData( - Uri.Builder().scheme("content").appendSpaParams(entryId = entryId).build() - ) - ).isEqualTo(emptySliceData) - } } diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt deleted file mode 100644 index b489afdae157..000000000000 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settingslib.spa.slice - -import android.net.Uri -import androidx.core.os.bundleOf -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.google.common.truth.Truth.assertThat -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class SliceUtilTest { - @Test - fun sliceUriTest() { - assertThat(Uri.EMPTY.getEntryId()).isNull() - - // valid slice uri - val dest = "myRoute" - val entryId = "myEntry" - val sliceUriWithoutParams = Uri.Builder().appendSpaParams(dest, entryId).build() - assertThat(sliceUriWithoutParams.getEntryId()).isEqualTo(entryId) - - val sliceUriWithParams = - Uri.Builder().appendSpaParams(dest, entryId, bundleOf("p1" to "v1")).build() - assertThat(sliceUriWithParams.getEntryId()).isEqualTo(entryId) - } -}
\ No newline at end of file diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt index 4f8fd794b248..9fcf886e52ca 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt @@ -17,9 +17,7 @@ package com.android.settingslib.spa.tests.testutils import android.app.Activity -import android.content.BroadcastReceiver import android.content.Context -import android.content.Intent import android.os.Bundle import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -27,7 +25,6 @@ import androidx.navigation.NavType import androidx.navigation.navArgument import com.android.settingslib.spa.framework.BrowseActivity import com.android.settingslib.spa.framework.common.EntrySearchData -import com.android.settingslib.spa.framework.common.EntrySliceData import com.android.settingslib.spa.framework.common.EntryStatusData import com.android.settingslib.spa.framework.common.LogCategory import com.android.settingslib.spa.framework.common.LogEvent @@ -75,9 +72,6 @@ class SpaLoggerForTest : SpaLogger { } class BlankActivity : BrowseActivity() -class BlankSliceBroadcastReceiver : BroadcastReceiver() { - override fun onReceive(p0: Context?, p1: Intent?) {} -} object SppHome : SettingsPageProvider { override val name = "SppHome" @@ -149,15 +143,7 @@ object SppLayer2 : SettingsPageProvider { override fun buildEntry(arguments: Bundle?): List<SettingsEntry> { val owner = this.createSettingsPage() return listOf( - SettingsEntryBuilder.create(owner, "Layer2Entry1") - .setSliceDataFn { _, _ -> - return@setSliceDataFn object : EntrySliceData() { - init { - postValue(null) - } - } - } - .build(), + SettingsEntryBuilder.create(owner, "Layer2Entry1").build(), SettingsEntryBuilder.create(owner, "Layer2Entry2").build(), ) } diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java index ca3af53681fe..85713607f917 100644 --- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java @@ -83,6 +83,12 @@ public final class BatteryUtils { return userManager.isManagedProfile() && !userManager.isSystemUser(); } + /** Returns true if current user is a private profile user. */ + public static boolean isPrivateProfile(Context context) { + final UserManager userManager = context.getSystemService(UserManager.class); + return userManager.isPrivateProfile(); + } + private static Boolean sChargingStringV2Enabled = null; /** Returns {@code true} if the charging string v2 is enabled. */ diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatteryUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatteryUtilsTest.java index 6424352d2c1b..465798ccb6c2 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatteryUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatteryUtilsTest.java @@ -23,11 +23,11 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; import android.accessibilityservice.AccessibilityServiceInfo; import android.content.Context; -import android.content.Intent; import android.content.IntentFilter; import android.os.UserManager; import android.provider.Settings; @@ -41,7 +41,6 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; -import org.robolectric.Shadows; import org.robolectric.shadows.ShadowAccessibilityManager; import java.util.Arrays; @@ -59,12 +58,15 @@ public class BatteryUtilsTest { private AccessibilityServiceInfo mAccessibilityServiceInfo1; @Mock private AccessibilityServiceInfo mAccessibilityServiceInfo2; + @Mock + private UserManager mUserManager; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = spy(ApplicationProvider.getApplicationContext()); doReturn(mContext).when(mContext).getApplicationContext(); + when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); mAccessibilityManager = spy(mContext.getSystemService(AccessibilityManager.class)); mShadowAccessibilityManager = shadowOf(mAccessibilityManager); doReturn(mAccessibilityManager).when(mContext) @@ -108,13 +110,23 @@ public class BatteryUtilsTest { @Test public void isWorkProfile_workProfileMode_returnTrue() { - final UserManager userManager = mContext.getSystemService(UserManager.class); - Shadows.shadowOf(userManager).setManagedProfile(true); - Shadows.shadowOf(userManager).setIsSystemUser(false); + doReturn(true).when(mUserManager).isManagedProfile(); assertThat(BatteryUtils.isWorkProfile(mContext)).isTrue(); } + @Test + public void isPrivateProfile_defaultValue_returnFalse() { + assertThat(BatteryUtils.isPrivateProfile(mContext)).isFalse(); + } + + @Test + public void isPrivateProfile_privateProfileMode_returnTrue() { + doReturn(true).when(mUserManager).isPrivateProfile(); + + assertThat(BatteryUtils.isPrivateProfile(mContext)).isTrue(); + } + private void setTtsPackageName(String defaultTtsPackageName) { Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_SYNTH, defaultTtsPackageName); diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 71f5511af4f5..97a45fb7b16a 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -1186,4 +1186,24 @@ flag { namespace: "systemui" description: "Enables MSDL feedback in SysUI surfaces." bug: "352600066" +} + +flag { + name: "sim_pin_race_condition_on_restart" + namespace: "systemui" + description: "The SIM PIN screen may be shown incorrectly on reboot" + bug: "351426938" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "sim_pin_talkback_fix_for_double_submit" + namespace: "systemui" + description: "The SIM PIN entry screens show the wrong message due" + bug: "346932439" + metadata { + purpose: PURPOSE_BUGFIX + } }
\ No newline at end of file diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt index 92f03d792554..35db9e0c2bb8 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -209,7 +209,6 @@ fun CommunalContainer( backgroundType = backgroundType, colors = colors, content = content, - modifier = Modifier.horizontalNestedScrollToScene(), ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt index 776e166f937c..c4970c5b43f9 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt @@ -145,6 +145,7 @@ fun SceneScope.HeadsUpNotificationSpace( } // Note: boundsInWindow doesn't scroll off the screen stackScrollView.setHeadsUpTop(boundsInWindow.top) + stackScrollView.setHeadsUpBottom(boundsInWindow.bottom) } ) } @@ -471,7 +472,11 @@ fun SceneScope.NotificationScrollingStack( ) } if (shouldIncludeHeadsUpSpace) { - HeadsUpNotificationSpace(stackScrollView = stackScrollView, viewModel = viewModel) + HeadsUpNotificationSpace( + stackScrollView = stackScrollView, + viewModel = viewModel, + modifier = Modifier.padding(top = topPadding) + ) } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt index 2d5d25913074..3cf8e70d458f 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt @@ -305,8 +305,7 @@ private fun SceneScope.QuickSettingsScene( if (isCustomizerShowing) { Modifier.fillMaxHeight().align(Alignment.TopCenter) } else { - Modifier.verticalNestedScrollToScene() - .verticalScroll( + Modifier.verticalScroll( scrollState, enabled = isScrollable, ) @@ -408,7 +407,7 @@ private fun SceneScope.QuickSettingsScene( HeadsUpNotificationSpace( stackScrollView = notificationStackScrollView, viewModel = notificationsPlaceholderViewModel, - modifier = Modifier.align(Alignment.BottomCenter), + modifier = Modifier.align(Alignment.BottomCenter).navigationBarsPadding(), isPeekFromBottom = true, ) NotificationScrollingStack( 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 805351ea8bbe..ece8b40ad332 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 @@ -514,8 +514,7 @@ private fun SceneScope.SplitShade( .sysuiResTag("expanded_qs_scroll_view") .weight(1f) .thenIf(!isCustomizerShowing) { - Modifier.verticalNestedScrollToScene() - .verticalScroll( + Modifier.verticalScroll( quickSettingsScrollState, enabled = isScrollable ) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt index 20b1303ae6bd..78ba7defe77c 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt @@ -64,6 +64,7 @@ internal class DraggableHandlerImpl( internal val orientation: Orientation, internal val coroutineScope: CoroutineScope, ) : DraggableHandler { + internal val nestedScrollKey = Any() /** The [DraggableHandler] can only have one active [DragController] at a time. */ private var dragController: DragControllerImpl? = null @@ -912,9 +913,9 @@ private class Swipes( internal class NestedScrollHandlerImpl( private val layoutImpl: SceneTransitionLayoutImpl, private val orientation: Orientation, - private val topOrLeftBehavior: NestedScrollBehavior, - private val bottomOrRightBehavior: NestedScrollBehavior, - private val isExternalOverscrollGesture: () -> Boolean, + internal var topOrLeftBehavior: NestedScrollBehavior, + internal var bottomOrRightBehavior: NestedScrollBehavior, + internal var isExternalOverscrollGesture: () -> Boolean, private val pointersInfoOwner: PointersInfoOwner, ) { private val layoutState = layoutImpl.state 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 615d393f8bee..2b78b5adaa62 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 @@ -41,7 +41,6 @@ import androidx.compose.ui.node.DelegatingNode import androidx.compose.ui.node.ModifierNodeElement import androidx.compose.ui.node.ObserverModifierNode import androidx.compose.ui.node.PointerInputModifierNode -import androidx.compose.ui.node.TraversableNode import androidx.compose.ui.node.currentValueOf import androidx.compose.ui.node.findNearestAncestor import androidx.compose.ui.node.observeReads @@ -139,16 +138,12 @@ internal class MultiPointerDraggableNode( DelegatingNode(), PointerInputModifierNode, CompositionLocalConsumerModifierNode, - TraversableNode, - PointersInfoOwner, ObserverModifierNode { private val pointerInputHandler: suspend PointerInputScope.() -> Unit = { pointerInput() } private val delegate = delegate(SuspendingPointerInputModifierNode(pointerInputHandler)) private val velocityTracker = VelocityTracker() private var previousEnabled: Boolean = false - override val traverseKey: Any = TRAVERSE_KEY - var enabled: () -> Boolean = enabled set(value) { // Reset the pointer input whenever enabled changed. @@ -208,7 +203,7 @@ internal class MultiPointerDraggableNode( private var startedPosition: Offset? = null private var pointersDown: Int = 0 - override fun pointersInfo(): PointersInfo { + internal fun pointersInfo(): PointersInfo { return PointersInfo( startedPosition = startedPosition, // Note: We could have 0 pointers during fling or for other reasons. diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt index ddff2f709082..945043d8fe95 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt @@ -18,12 +18,13 @@ package com.android.compose.animation.scene import androidx.compose.foundation.gestures.Orientation import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.nestedScrollModifierNode -import androidx.compose.ui.node.DelegatableNode import androidx.compose.ui.node.DelegatingNode import androidx.compose.ui.node.ModifierNodeElement import androidx.compose.ui.platform.InspectorInfo -import com.android.compose.nestedscroll.PriorityNestedScrollConnection /** * Defines the behavior of the [SceneTransitionLayout] when a scrollable component is scrolled. @@ -67,7 +68,11 @@ enum class NestedScrollBehavior(val canStartOnPostFling: Boolean) { * In addition, during scene transitions, scroll events are consumed by the * [SceneTransitionLayout] instead of the scrollable component. */ - EdgeAlways(canStartOnPostFling = true), + EdgeAlways(canStartOnPostFling = true); + + companion object { + val Default = EdgeNoPreview + } } internal fun Modifier.nestedScrollToScene( @@ -122,37 +127,60 @@ private data class NestedScrollToSceneElement( } private class NestedScrollToSceneNode( - layoutImpl: SceneTransitionLayoutImpl, - orientation: Orientation, - topOrLeftBehavior: NestedScrollBehavior, - bottomOrRightBehavior: NestedScrollBehavior, - isExternalOverscrollGesture: () -> Boolean, + private var layoutImpl: SceneTransitionLayoutImpl, + private var orientation: Orientation, + private var topOrLeftBehavior: NestedScrollBehavior, + private var bottomOrRightBehavior: NestedScrollBehavior, + private var isExternalOverscrollGesture: () -> Boolean, ) : DelegatingNode() { - lateinit var pointersInfoOwner: PointersInfoOwner - private var priorityNestedScrollConnection: PriorityNestedScrollConnection = - scenePriorityNestedScrollConnection( - layoutImpl = layoutImpl, - orientation = orientation, - topOrLeftBehavior = topOrLeftBehavior, - bottomOrRightBehavior = bottomOrRightBehavior, - isExternalOverscrollGesture = isExternalOverscrollGesture, - pointersInfoOwner = { pointersInfoOwner.pointersInfo() } - ) - - private var nestedScrollNode: DelegatableNode = - nestedScrollModifierNode( - connection = priorityNestedScrollConnection, - dispatcher = null, - ) + private var scrollBehaviorOwner: ScrollBehaviorOwner? = null + + private fun requireScrollBehaviorOwner(): ScrollBehaviorOwner { + var behaviorOwner = scrollBehaviorOwner + if (behaviorOwner == null) { + behaviorOwner = requireScrollBehaviorOwner(layoutImpl.draggableHandler(orientation)) + scrollBehaviorOwner = behaviorOwner + } + return behaviorOwner + } - override fun onAttach() { - pointersInfoOwner = requireAncestorPointersInfoOwner() - delegate(nestedScrollNode) + private val updateScrollBehaviorsConnection = + object : NestedScrollConnection { + /** + * When using [NestedScrollConnection.onPostScroll], we can specify the desired behavior + * before our parent components. This gives them the option to override our behavior if + * they choose. + * + * The behavior can be communicated at every scroll gesture to ensure that the hierarchy + * is respected, even if one of our descendant nodes changes behavior after we set it. + */ + override fun onPostScroll( + consumed: Offset, + available: Offset, + source: NestedScrollSource, + ): Offset { + // If we have some remaining scroll, that scroll can be used to initiate a + // transition between scenes. We can assume that the behavior is only needed if + // there is some remaining amount. + if (available != Offset.Zero) { + requireScrollBehaviorOwner() + .updateScrollBehaviors( + topOrLeftBehavior = topOrLeftBehavior, + bottomOrRightBehavior = bottomOrRightBehavior, + isExternalOverscrollGesture = isExternalOverscrollGesture, + ) + } + + return Offset.Zero + } + } + + init { + delegate(nestedScrollModifierNode(updateScrollBehaviorsConnection, dispatcher = null)) } override fun onDetach() { - // Make sure we reset the scroll connection when this modifier is removed from composition - priorityNestedScrollConnection.reset() + scrollBehaviorOwner = null } fun update( @@ -162,43 +190,10 @@ private class NestedScrollToSceneNode( bottomOrRightBehavior: NestedScrollBehavior, isExternalOverscrollGesture: () -> Boolean, ) { - // Clean up the old nested scroll connection - priorityNestedScrollConnection.reset() - undelegate(nestedScrollNode) - - // Create a new nested scroll connection - priorityNestedScrollConnection = - scenePriorityNestedScrollConnection( - layoutImpl = layoutImpl, - orientation = orientation, - topOrLeftBehavior = topOrLeftBehavior, - bottomOrRightBehavior = bottomOrRightBehavior, - isExternalOverscrollGesture = isExternalOverscrollGesture, - pointersInfoOwner = pointersInfoOwner, - ) - nestedScrollNode = - nestedScrollModifierNode( - connection = priorityNestedScrollConnection, - dispatcher = null, - ) - delegate(nestedScrollNode) + this.layoutImpl = layoutImpl + this.orientation = orientation + this.topOrLeftBehavior = topOrLeftBehavior + this.bottomOrRightBehavior = bottomOrRightBehavior + this.isExternalOverscrollGesture = isExternalOverscrollGesture } } - -private fun scenePriorityNestedScrollConnection( - layoutImpl: SceneTransitionLayoutImpl, - orientation: Orientation, - topOrLeftBehavior: NestedScrollBehavior, - bottomOrRightBehavior: NestedScrollBehavior, - isExternalOverscrollGesture: () -> Boolean, - pointersInfoOwner: PointersInfoOwner, -) = - NestedScrollHandlerImpl( - layoutImpl = layoutImpl, - orientation = orientation, - topOrLeftBehavior = topOrLeftBehavior, - bottomOrRightBehavior = bottomOrRightBehavior, - isExternalOverscrollGesture = isExternalOverscrollGesture, - pointersInfoOwner = pointersInfoOwner, - ) - .connection diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index 0c467b181cd8..82275a9ac0a6 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -207,8 +207,8 @@ interface BaseSceneScope : ElementStateScope { * @param rightBehavior when we should perform the overscroll animation at the right. */ fun Modifier.horizontalNestedScrollToScene( - leftBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview, - rightBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview, + leftBehavior: NestedScrollBehavior = NestedScrollBehavior.Default, + rightBehavior: NestedScrollBehavior = NestedScrollBehavior.Default, isExternalOverscrollGesture: () -> Boolean = { false }, ): Modifier @@ -220,8 +220,8 @@ interface BaseSceneScope : ElementStateScope { * @param bottomBehavior when we should perform the overscroll animation at the bottom. */ fun Modifier.verticalNestedScrollToScene( - topBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview, - bottomBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview, + topBehavior: NestedScrollBehavior = NestedScrollBehavior.Default, + bottomBehavior: NestedScrollBehavior = NestedScrollBehavior.Default, isExternalOverscrollGesture: () -> Boolean = { false }, ): Modifier 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 aeb62628a8f4..b8010f25f9a4 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 @@ -20,11 +20,15 @@ import androidx.compose.foundation.gestures.Orientation import androidx.compose.runtime.Stable import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.nestedscroll.nestedScrollModifierNode import androidx.compose.ui.input.pointer.PointerEvent import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.node.DelegatableNode import androidx.compose.ui.node.DelegatingNode import androidx.compose.ui.node.ModifierNodeElement import androidx.compose.ui.node.PointerInputModifierNode +import androidx.compose.ui.node.TraversableNode +import androidx.compose.ui.node.findNearestAncestor import androidx.compose.ui.unit.IntSize /** @@ -53,7 +57,7 @@ private class SwipeToSceneNode( draggableHandler: DraggableHandlerImpl, swipeDetector: SwipeDetector, ) : DelegatingNode(), PointerInputModifierNode { - private val delegate = + private val multiPointerDraggableNode = delegate( MultiPointerDraggableNode( orientation = draggableHandler.orientation, @@ -74,21 +78,41 @@ private class SwipeToSceneNode( // Make sure to update the delegate orientation. Note that this will automatically // reset the underlying pointer input handler, so previous gestures will be // cancelled. - delegate.orientation = value.orientation + multiPointerDraggableNode.orientation = value.orientation } } + private val nestedScrollHandlerImpl = + NestedScrollHandlerImpl( + layoutImpl = draggableHandler.layoutImpl, + orientation = draggableHandler.orientation, + topOrLeftBehavior = NestedScrollBehavior.Default, + bottomOrRightBehavior = NestedScrollBehavior.Default, + isExternalOverscrollGesture = { false }, + pointersInfoOwner = { multiPointerDraggableNode.pointersInfo() }, + ) + + init { + delegate(nestedScrollModifierNode(nestedScrollHandlerImpl.connection, dispatcher = null)) + delegate(ScrollBehaviorOwnerNode(draggableHandler.nestedScrollKey, nestedScrollHandlerImpl)) + } + + override fun onDetach() { + // Make sure we reset the scroll connection when this modifier is removed from composition + nestedScrollHandlerImpl.connection.reset() + } + override fun onPointerEvent( pointerEvent: PointerEvent, pass: PointerEventPass, bounds: IntSize, - ) = delegate.onPointerEvent(pointerEvent, pass, bounds) + ) = multiPointerDraggableNode.onPointerEvent(pointerEvent, pass, bounds) - override fun onCancelPointerInput() = delegate.onCancelPointerInput() + override fun onCancelPointerInput() = multiPointerDraggableNode.onCancelPointerInput() private fun enabled(): Boolean { return draggableHandler.isDrivingTransition || - currentScene().shouldEnableSwipes(delegate.orientation) + currentScene().shouldEnableSwipes(multiPointerDraggableNode.orientation) } private fun currentScene(): Scene { @@ -118,3 +142,43 @@ private class SwipeToSceneNode( return currentScene().shouldEnableSwipes(oppositeOrientation) } } + +/** Find the [ScrollBehaviorOwner] for the current orientation. */ +internal fun DelegatableNode.requireScrollBehaviorOwner( + draggableHandler: DraggableHandlerImpl +): ScrollBehaviorOwner { + val ancestorNode = + checkNotNull(findNearestAncestor(draggableHandler.nestedScrollKey)) { + "This should never happen! Couldn't find a ScrollBehaviorOwner. " + + "Are we inside an SceneTransitionLayout?" + } + return ancestorNode as ScrollBehaviorOwner +} + +internal fun interface ScrollBehaviorOwner { + fun updateScrollBehaviors( + topOrLeftBehavior: NestedScrollBehavior, + bottomOrRightBehavior: NestedScrollBehavior, + isExternalOverscrollGesture: () -> Boolean, + ) +} + +/** + * We need a node that receives the desired behavior. + * + * TODO(b/353234530) move this logic into [SwipeToSceneNode] + */ +private class ScrollBehaviorOwnerNode( + override val traverseKey: Any, + val nestedScrollHandlerImpl: NestedScrollHandlerImpl +) : Modifier.Node(), TraversableNode, ScrollBehaviorOwner { + override fun updateScrollBehaviors( + topOrLeftBehavior: NestedScrollBehavior, + bottomOrRightBehavior: NestedScrollBehavior, + isExternalOverscrollGesture: () -> Boolean + ) { + nestedScrollHandlerImpl.topOrLeftBehavior = topOrLeftBehavior + nestedScrollHandlerImpl.bottomOrRightBehavior = bottomOrRightBehavior + nestedScrollHandlerImpl.isExternalOverscrollGesture = isExternalOverscrollGesture + } +} diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt index 7988e0e4e416..c91151e41605 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt @@ -797,8 +797,6 @@ class ElementTest { scene(SceneB, userActions = mapOf(Swipe.Up to SceneA)) { Box( Modifier - // Unconsumed scroll gesture will be intercepted by STL - .verticalNestedScrollToScene() // A scrollable that does not consume the scroll gesture .scrollable( rememberScrollableState(consumeScrollDelta = { 0f }), @@ -875,8 +873,6 @@ class ElementTest { ) { Box( Modifier - // Unconsumed scroll gesture will be intercepted by STL - .verticalNestedScrollToScene() // A scrollable that does not consume the scroll gesture .scrollable( rememberScrollableState(consumeScrollDelta = { 0f }), 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 new file mode 100644 index 000000000000..311a58018840 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.compose.animation.scene + +import androidx.compose.foundation.gestures.Orientation.Vertical +import androidx.compose.foundation.gestures.rememberScrollableState +import androidx.compose.foundation.gestures.scrollable +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.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.platform.LocalViewConfiguration +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onRoot +import androidx.compose.ui.test.performTouchInput +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.animation.scene.TestScenes.SceneA +import com.android.compose.animation.scene.TestScenes.SceneB +import com.android.compose.animation.scene.subjects.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class NestedScrollToSceneTest { + @get:Rule val rule = createComposeRule() + + private var touchSlop = 0f + private val layoutWidth: Dp = 200.dp + private val layoutHeight = 400.dp + + private fun setup2ScenesAndScrollTouchSlop( + modifierSceneA: @Composable SceneScope.() -> Modifier = { Modifier }, + ): MutableSceneTransitionLayoutState { + val state = + rule.runOnUiThread { + MutableSceneTransitionLayoutState(SceneA, transitions = EmptyTestTransitions) + } + + rule.setContent { + touchSlop = LocalViewConfiguration.current.touchSlop + SceneTransitionLayout( + state = state, + modifier = Modifier.size(layoutWidth, layoutHeight) + ) { + scene(SceneA, userActions = mapOf(Swipe.Up to SceneB)) { + Spacer(modifierSceneA().fillMaxSize()) + } + scene(SceneB, userActions = mapOf(Swipe.Down to SceneA)) { + Spacer(Modifier.fillMaxSize()) + } + } + } + + pointerDownAndScrollTouchSlop() + + assertThat(state.transitionState).isIdle() + + return state + } + + private fun pointerDownAndScrollTouchSlop() { + rule.onRoot().performTouchInput { + val middleTop = Offset((layoutWidth / 2).toPx(), 0f) + down(middleTop) + // Scroll touchSlop + moveBy(Offset(0f, touchSlop), delayMillis = 1_000) + } + } + + private fun scrollDown(percent: Float = 1f) { + rule.onRoot().performTouchInput { + moveBy(Offset(0f, layoutHeight.toPx() * percent), delayMillis = 1_000) + } + } + + private fun scrollUp(percent: Float = 1f) = scrollDown(-percent) + + private fun pointerUp() { + rule.onRoot().performTouchInput { up() } + } + + @Test + fun scrollableElementsInSTL_shouldHavePriority() { + val state = setup2ScenesAndScrollTouchSlop { + Modifier + // A scrollable that consumes the scroll gesture + .scrollable(rememberScrollableState { it }, Vertical) + } + + scrollUp(percent = 0.5f) + + // Consumed by the scrollable element + assertThat(state.transitionState).isIdle() + } + + @Test + fun unconsumedScrollEvents_canBeConsumedBySTLByDefault() { + val state = setup2ScenesAndScrollTouchSlop { + Modifier + // A scrollable that does not consume the scroll gesture + .scrollable(rememberScrollableState { 0f }, Vertical) + } + + scrollUp(percent = 0.5f) + // STL will start a transition with the remaining scroll + val transition = assertThat(state.transitionState).isTransition() + assertThat(transition).hasProgress(0.5f) + + scrollUp(percent = 1f) + assertThat(transition).hasProgress(1.5f) + } + + @Test + fun customizeStlNestedScrollBehavior_DuringTransitionBetweenScenes() { + var canScroll = true + val state = setup2ScenesAndScrollTouchSlop { + Modifier.verticalNestedScrollToScene( + bottomBehavior = NestedScrollBehavior.DuringTransitionBetweenScenes + ) + .scrollable(rememberScrollableState { if (canScroll) it else 0f }, Vertical) + } + + scrollUp(percent = 0.5f) + assertThat(state.transitionState).isIdle() + + // Reach the end of the scrollable element + canScroll = false + scrollUp(percent = 0.5f) + assertThat(state.transitionState).isIdle() + + pointerUp() + assertThat(state.transitionState).isIdle() + + // Start a new gesture + pointerDownAndScrollTouchSlop() + scrollUp(percent = 0.5f) + assertThat(state.transitionState).isIdle() + } + + @Test + fun customizeStlNestedScrollBehavior_EdgeNoPreview() { + var canScroll = true + val state = setup2ScenesAndScrollTouchSlop { + Modifier.verticalNestedScrollToScene( + bottomBehavior = NestedScrollBehavior.EdgeNoPreview + ) + .scrollable(rememberScrollableState { if (canScroll) it else 0f }, Vertical) + } + + scrollUp(percent = 0.5f) + assertThat(state.transitionState).isIdle() + + // Reach the end of the scrollable element + canScroll = false + scrollUp(percent = 0.5f) + assertThat(state.transitionState).isIdle() + + pointerUp() + assertThat(state.transitionState).isIdle() + + // Start a new gesture + pointerDownAndScrollTouchSlop() + scrollUp(percent = 0.5f) + val transition = assertThat(state.transitionState).isTransition() + assertThat(transition).hasProgress(0.5f) + + pointerUp() + rule.waitForIdle() + assertThat(state.transitionState).isIdle() + assertThat(state.transitionState).hasCurrentScene(SceneB) + } + + @Test + fun customizeStlNestedScrollBehavior_EdgeWithPreview() { + var canScroll = true + val state = setup2ScenesAndScrollTouchSlop { + Modifier.verticalNestedScrollToScene( + bottomBehavior = NestedScrollBehavior.EdgeWithPreview + ) + .scrollable(rememberScrollableState { if (canScroll) it else 0f }, Vertical) + } + + scrollUp(percent = 0.5f) + assertThat(state.transitionState).isIdle() + + // Reach the end of the scrollable element + canScroll = false + scrollUp(percent = 0.5f) + val transition1 = assertThat(state.transitionState).isTransition() + assertThat(transition1).hasProgress(0.5f) + + pointerUp() + rule.waitForIdle() + assertThat(state.transitionState).isIdle() + assertThat(state.transitionState).hasCurrentScene(SceneA) + + // Start a new gesture + pointerDownAndScrollTouchSlop() + scrollUp(percent = 0.5f) + val transition2 = assertThat(state.transitionState).isTransition() + assertThat(transition2).hasProgress(0.5f) + + pointerUp() + rule.waitForIdle() + assertThat(state.transitionState).isIdle() + assertThat(state.transitionState).hasCurrentScene(SceneB) + } + + @Test + fun customizeStlNestedScrollBehavior_EdgeAlways() { + var canScroll = true + val state = setup2ScenesAndScrollTouchSlop { + Modifier.verticalNestedScrollToScene(bottomBehavior = NestedScrollBehavior.EdgeAlways) + .scrollable(rememberScrollableState { if (canScroll) it else 0f }, Vertical) + } + + scrollUp(percent = 0.5f) + assertThat(state.transitionState).isIdle() + + // Reach the end of the scrollable element + canScroll = false + scrollUp(percent = 0.5f) + val transition = assertThat(state.transitionState).isTransition() + assertThat(transition).hasProgress(0.5f) + + pointerUp() + rule.waitForIdle() + assertThat(state.transitionState).isIdle() + assertThat(state.transitionState).hasCurrentScene(SceneB) + } + + @Test + fun customizeStlNestedScrollBehavior_multipleRequests() { + val state = setup2ScenesAndScrollTouchSlop { + Modifier + // This verticalNestedScrollToScene is closer the STL (an ancestor node) + .verticalNestedScrollToScene(bottomBehavior = NestedScrollBehavior.EdgeAlways) + // Another verticalNestedScrollToScene modifier + .verticalNestedScrollToScene( + bottomBehavior = NestedScrollBehavior.DuringTransitionBetweenScenes + ) + .scrollable(rememberScrollableState { 0f }, Vertical) + } + + scrollUp(percent = 0.5f) + // EdgeAlways always consume the remaining scroll, DuringTransitionBetweenScenes does not. + val transition = assertThat(state.transitionState).isTransition() + assertThat(transition).hasProgress(0.5f) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt index 546a6b7a43a2..d5e1fae215c7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt @@ -160,6 +160,12 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { } @Test + fun testShowReturnsFalseWhenDelegateIsNotSet() { + whenever(bouncerView.delegate).thenReturn(null) + assertThat(underTest.show(true)).isEqualTo(false) + } + + @Test fun testShow_isResumed() { whenever(repository.primaryBouncerShow.value).thenReturn(true) whenever(keyguardSecurityModel.getSecurityMode(anyInt())) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt index be0d899fd946..9e696011e285 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt @@ -18,8 +18,11 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.Flags import com.android.systemui.Flags as AConfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -74,9 +77,9 @@ class AodAlphaViewModelTest : SysuiTestCase() { @Test @DisableSceneContainer + @DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun alpha_WhenNotGone_clockMigrationFlagIsOff_emitsKeyguardAlpha() = testScope.runTest { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) val alpha by collectLastValue(underTest.alpha) keyguardTransitionRepository.sendTransitionSteps( @@ -186,9 +189,9 @@ class AodAlphaViewModelTest : SysuiTestCase() { @Test @DisableSceneContainer + @EnableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun alpha_whenGone_equalsZero() = testScope.runTest { - mSetFlagsRule.enableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) val alpha by collectLastValue(underTest.alpha) keyguardTransitionRepository.sendTransitionStep( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt index 63d06a409522..41c5b7332a4f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt @@ -18,6 +18,8 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags as AConfigFlags @@ -69,10 +71,11 @@ class AodBurnInViewModelTest : SysuiTestCase() { private val burnInFlow = MutableStateFlow(BurnInModel()) @Before + @DisableFlags( + AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, + AConfigFlags.FLAG_COMPOSE_LOCKSCREEN + ) fun setUp() { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN) - MockitoAnnotations.initMocks(this) whenever(burnInInteractor.burnIn(anyInt(), anyInt())).thenReturn(burnInFlow) kosmos.burnInInteractor = burnInInteractor @@ -174,10 +177,9 @@ class AodBurnInViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun translationAndScale_whenFullyDozing_MigrationFlagOff_staysOutOfTopInset() = testScope.runTest { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - burnInParameters = burnInParameters.copy( minViewY = 100, @@ -226,10 +228,9 @@ class AodBurnInViewModelTest : SysuiTestCase() { } @Test + @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun translationAndScale_whenFullyDozing_MigrationFlagOn_staysOutOfTopInset() = testScope.runTest { - mSetFlagsRule.enableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - burnInParameters = burnInParameters.copy( minViewY = 100, @@ -310,104 +311,99 @@ class AodBurnInViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN) + @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun translationAndScale_composeFlagOff_weatherLargeClock() = testBurnInViewModelForClocks( isSmallClock = false, isWeatherClock = true, expectedScaleOnly = false, - enableMigrateClocksToBlueprintFlag = true, - enableComposeLockscreenFlag = false ) @Test + @DisableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN) + @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun translationAndScale_composeFlagOff_weatherSmallClock() = testBurnInViewModelForClocks( isSmallClock = true, isWeatherClock = true, expectedScaleOnly = false, - enableMigrateClocksToBlueprintFlag = true, - enableComposeLockscreenFlag = false ) @Test + @DisableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN) + @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun translationAndScale_composeFlagOff_nonWeatherLargeClock() = testBurnInViewModelForClocks( isSmallClock = false, isWeatherClock = false, expectedScaleOnly = true, - enableMigrateClocksToBlueprintFlag = true, - enableComposeLockscreenFlag = false ) @Test + @DisableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN) + @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun translationAndScale_composeFlagOff_nonWeatherSmallClock() = testBurnInViewModelForClocks( isSmallClock = true, isWeatherClock = false, expectedScaleOnly = false, - enableMigrateClocksToBlueprintFlag = true, - enableComposeLockscreenFlag = false ) @Test + @EnableFlags( + AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, + AConfigFlags.FLAG_COMPOSE_LOCKSCREEN + ) fun translationAndScale_composeFlagOn_weatherLargeClock() = testBurnInViewModelForClocks( isSmallClock = false, isWeatherClock = true, expectedScaleOnly = false, - enableMigrateClocksToBlueprintFlag = true, - enableComposeLockscreenFlag = true ) @Test + @EnableFlags( + AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, + AConfigFlags.FLAG_COMPOSE_LOCKSCREEN + ) fun translationAndScale_composeFlagOn_weatherSmallClock() = testBurnInViewModelForClocks( isSmallClock = true, isWeatherClock = true, expectedScaleOnly = false, - enableMigrateClocksToBlueprintFlag = true, - enableComposeLockscreenFlag = true ) @Test + @EnableFlags( + AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, + AConfigFlags.FLAG_COMPOSE_LOCKSCREEN + ) fun translationAndScale_composeFlagOn_nonWeatherLargeClock() = testBurnInViewModelForClocks( isSmallClock = false, isWeatherClock = false, expectedScaleOnly = true, - enableMigrateClocksToBlueprintFlag = true, - enableComposeLockscreenFlag = true ) @Test + @EnableFlags( + AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, + AConfigFlags.FLAG_COMPOSE_LOCKSCREEN + ) fun translationAndScale_composeFlagOn_nonWeatherSmallClock() = testBurnInViewModelForClocks( isSmallClock = true, isWeatherClock = false, expectedScaleOnly = false, - enableMigrateClocksToBlueprintFlag = true, - enableComposeLockscreenFlag = true ) private fun testBurnInViewModelForClocks( isSmallClock: Boolean, isWeatherClock: Boolean, expectedScaleOnly: Boolean, - enableMigrateClocksToBlueprintFlag: Boolean, - enableComposeLockscreenFlag: Boolean ) = testScope.runTest { - if (enableMigrateClocksToBlueprintFlag) { - mSetFlagsRule.enableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - } else { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - } - - if (enableComposeLockscreenFlag) { - mSetFlagsRule.enableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN) - } else { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN) - } if (isSmallClock) { keyguardClockRepository.setClockSize(ClockSize.SMALL) // we need the following step to update stateFlow value diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt index 9b9e584a936e..d5c910248942 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt @@ -21,14 +21,23 @@ import android.provider.Settings import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel +import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate import com.google.common.truth.Truth +import kotlin.coroutines.EmptyCoroutineContext import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) @@ -36,7 +45,33 @@ import org.junit.runner.RunWith class ModesTileUserActionInteractorTest : SysuiTestCase() { private val inputHandler = FakeQSTileIntentUserInputHandler() - val underTest = ModesTileUserActionInteractor(inputHandler) + @Mock private lateinit var dialogTransitionAnimator: DialogTransitionAnimator + @Mock private lateinit var dialogDelegate: ModesDialogDelegate + @Mock private lateinit var mockDialog: SystemUIDialog + + private lateinit var underTest: ModesTileUserActionInteractor + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + + whenever(dialogDelegate.createDialog()).thenReturn(mockDialog) + + underTest = + ModesTileUserActionInteractor( + EmptyCoroutineContext, + inputHandler, + dialogTransitionAnimator, + dialogDelegate, + ) + } + + @Test + fun handleClick() = runTest { + underTest.handleInput(QSTileInputTestKtx.click(ModesTileModel(false))) + + verify(mockDialog).show() + } @Test fun handleLongClick() = runTest { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt new file mode 100644 index 000000000000..3baf2f40f175 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt @@ -0,0 +1,74 @@ +/* + * 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.tiles.impl.modes.ui + +import android.graphics.drawable.TestStubDrawable +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileConfigTestBuilder +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig +import com.android.systemui.res.R +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class ModesTileMapperTest : SysuiTestCase() { + val config = + QSTileConfigTestBuilder.build { + uiConfig = + QSTileUIConfig.Resource( + iconRes = R.drawable.qs_dnd_icon_off, + labelRes = R.string.quick_settings_modes_label, + ) + } + + val underTest = + ModesTileMapper( + context.orCreateTestableResources + .apply { + addOverride(R.drawable.qs_dnd_icon_on, TestStubDrawable()) + addOverride(R.drawable.qs_dnd_icon_off, TestStubDrawable()) + } + .resources, + context.theme, + ) + + @Test + fun inactiveState() { + val model = ModesTileModel(isActivated = false) + + val state = underTest.map(config, model) + + assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.INACTIVE) + assertThat(state.iconRes).isEqualTo(R.drawable.qs_dnd_icon_off) + } + + @Test + fun activeState() { + val model = ModesTileModel(isActivated = true) + + val state = underTest.map(config, model) + + assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.ACTIVE) + assertThat(state.iconRes).isEqualTo(R.drawable.qs_dnd_icon_on) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt new file mode 100644 index 000000000000..6ddc07432a16 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt @@ -0,0 +1,683 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.statusbar.notification.collection.coordinator + +import android.app.Notification +import android.os.UserHandle +import android.platform.test.flag.junit.FlagsParameterization +import android.provider.Settings +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.andSceneContainer +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.log.logcatLogBuffer +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.scene.data.repository.Idle +import com.android.systemui.scene.data.repository.setTransition +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository +import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow +import com.android.systemui.statusbar.policy.HeadsUpManager +import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener +import com.android.systemui.util.settings.FakeSettings +import com.google.common.truth.Truth.assertThat +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestCoroutineScheduler +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.same +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters + +@SmallTest +@RunWith(ParameterizedAndroidJunit4::class) +class OriginalUnseenKeyguardCoordinatorTest(flags: FlagsParameterization) : SysuiTestCase() { + + private val kosmos = Kosmos() + + private val headsUpManager: HeadsUpManager = mock() + private val keyguardRepository = FakeKeyguardRepository() + private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + private val notifPipeline: NotifPipeline = mock() + private val statusBarStateController: StatusBarStateController = mock() + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } + + @Test + fun unseenFilterSuppressesSeenNotifWhileKeyguardShowing() { + // GIVEN: Keyguard is not showing, shade is expanded, and a notification is present + keyguardRepository.setKeyguardShowing(false) + whenever(statusBarStateController.isExpanded).thenReturn(true) + runKeyguardCoordinatorTest { + val fakeEntry = NotificationEntryBuilder().build() + collectionListener.onEntryAdded(fakeEntry) + + // WHEN: The keyguard is now showing + keyguardRepository.setKeyguardShowing(true) + testScheduler.runCurrent() + + // THEN: The notification is recognized as "seen" and is filtered out. + assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue() + + // WHEN: The keyguard goes away + keyguardRepository.setKeyguardShowing(false) + testScheduler.runCurrent() + + // THEN: The notification is shown regardless + assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() + } + } + + @Test + fun unseenFilterStopsMarkingSeenNotifWhenTransitionToAod() { + // GIVEN: Keyguard is not showing, shade is not expanded, and a notification is present + keyguardRepository.setKeyguardShowing(false) + whenever(statusBarStateController.isExpanded).thenReturn(false) + runKeyguardCoordinatorTest { + val fakeEntry = NotificationEntryBuilder().build() + collectionListener.onEntryAdded(fakeEntry) + + // WHEN: The device transitions to AOD + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + this.testScheduler, + ) + testScheduler.runCurrent() + + // THEN: We are no longer listening for shade expansions + verify(statusBarStateController, never()).addCallback(any()) + } + } + + @Test + fun unseenFilter_headsUpMarkedAsSeen() { + // GIVEN: Keyguard is not showing, shade is not expanded + keyguardRepository.setKeyguardShowing(false) + whenever(statusBarStateController.isExpanded).thenReturn(false) + runKeyguardCoordinatorTest { + kosmos.setTransition( + sceneTransition = Idle(Scenes.Gone), + stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE) + ) + + // WHEN: A notification is posted + val fakeEntry = NotificationEntryBuilder().build() + collectionListener.onEntryAdded(fakeEntry) + + // WHEN: That notification is heads up + onHeadsUpChangedListener.onHeadsUpStateChanged(fakeEntry, /* isHeadsUp= */ true) + testScheduler.runCurrent() + + // WHEN: The keyguard is now showing + keyguardRepository.setKeyguardShowing(true) + kosmos.setTransition( + sceneTransition = Idle(Scenes.Lockscreen), + stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD) + ) + + // THEN: The notification is recognized as "seen" and is filtered out. + assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue() + + // WHEN: The keyguard goes away + keyguardRepository.setKeyguardShowing(false) + kosmos.setTransition( + sceneTransition = Idle(Scenes.Gone), + stateTransition = TransitionStep(KeyguardState.AOD, KeyguardState.GONE) + ) + + // THEN: The notification is shown regardless + assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() + } + } + + @Test + fun unseenFilterDoesNotSuppressSeenOngoingNotifWhileKeyguardShowing() { + // GIVEN: Keyguard is not showing, shade is expanded, and an ongoing notification is present + keyguardRepository.setKeyguardShowing(false) + whenever(statusBarStateController.isExpanded).thenReturn(true) + runKeyguardCoordinatorTest { + val fakeEntry = + NotificationEntryBuilder() + .setNotification(Notification.Builder(mContext, "id").setOngoing(true).build()) + .build() + collectionListener.onEntryAdded(fakeEntry) + + // WHEN: The keyguard is now showing + keyguardRepository.setKeyguardShowing(true) + testScheduler.runCurrent() + + // THEN: The notification is recognized as "ongoing" and is not filtered out. + assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() + } + } + + @Test + fun unseenFilterDoesNotSuppressSeenMediaNotifWhileKeyguardShowing() { + // GIVEN: Keyguard is not showing, shade is expanded, and a media notification is present + keyguardRepository.setKeyguardShowing(false) + whenever(statusBarStateController.isExpanded).thenReturn(true) + runKeyguardCoordinatorTest { + val fakeEntry = + NotificationEntryBuilder().build().apply { + row = + mock<ExpandableNotificationRow>().apply { + whenever(isMediaRow).thenReturn(true) + } + } + collectionListener.onEntryAdded(fakeEntry) + + // WHEN: The keyguard is now showing + keyguardRepository.setKeyguardShowing(true) + testScheduler.runCurrent() + + // THEN: The notification is recognized as "media" and is not filtered out. + assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() + } + } + + @Test + fun unseenFilterUpdatesSeenProviderWhenSuppressing() { + // GIVEN: Keyguard is not showing, shade is expanded, and a notification is present + keyguardRepository.setKeyguardShowing(false) + whenever(statusBarStateController.isExpanded).thenReturn(true) + runKeyguardCoordinatorTest { + val fakeEntry = NotificationEntryBuilder().build() + collectionListener.onEntryAdded(fakeEntry) + + // WHEN: The keyguard is now showing + keyguardRepository.setKeyguardShowing(true) + testScheduler.runCurrent() + + // THEN: The notification is recognized as "seen" and is filtered out. + assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue() + + // WHEN: The filter is cleaned up + unseenFilter.onCleanup() + + // THEN: The SeenNotificationProvider has been updated to reflect the suppression + assertThat(seenNotificationsInteractor.hasFilteredOutSeenNotifications.value).isTrue() + } + } + + @Test + fun unseenFilterInvalidatesWhenSettingChanges() { + // GIVEN: Keyguard is not showing, and shade is expanded + keyguardRepository.setKeyguardShowing(false) + whenever(statusBarStateController.isExpanded).thenReturn(true) + runKeyguardCoordinatorTest { + // GIVEN: A notification is present + val fakeEntry = NotificationEntryBuilder().build() + collectionListener.onEntryAdded(fakeEntry) + + // GIVEN: The setting for filtering unseen notifications is disabled + showOnlyUnseenNotifsOnKeyguardSetting = false + + // GIVEN: The pipeline has registered the unseen filter for invalidation + val invalidationListener: Pluggable.PluggableListener<NotifFilter> = mock() + unseenFilter.setInvalidationListener(invalidationListener) + + // WHEN: The keyguard is now showing + keyguardRepository.setKeyguardShowing(true) + testScheduler.runCurrent() + + // THEN: The notification is not filtered out + assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() + + // WHEN: The secure setting is changed + showOnlyUnseenNotifsOnKeyguardSetting = true + + // THEN: The pipeline is invalidated + verify(invalidationListener).onPluggableInvalidated(same(unseenFilter), any()) + + // THEN: The notification is recognized as "seen" and is filtered out. + assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue() + } + } + + @Test + fun unseenFilterAllowsNewNotif() { + // GIVEN: Keyguard is showing, no notifications present + keyguardRepository.setKeyguardShowing(true) + runKeyguardCoordinatorTest { + // WHEN: A new notification is posted + val fakeEntry = NotificationEntryBuilder().build() + collectionListener.onEntryAdded(fakeEntry) + + // THEN: The notification is recognized as "unseen" and is not filtered out. + assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() + } + } + + @Test + fun unseenFilterSeenGroupSummaryWithUnseenChild() { + // GIVEN: Keyguard is not showing, shade is expanded, and a notification is present + keyguardRepository.setKeyguardShowing(false) + whenever(statusBarStateController.isExpanded).thenReturn(true) + runKeyguardCoordinatorTest { + // WHEN: A new notification is posted + val fakeSummary = NotificationEntryBuilder().build() + val fakeChild = + NotificationEntryBuilder() + .setGroup(context, "group") + .setGroupSummary(context, false) + .build() + GroupEntryBuilder().setSummary(fakeSummary).addChild(fakeChild).build() + + collectionListener.onEntryAdded(fakeSummary) + collectionListener.onEntryAdded(fakeChild) + + // WHEN: Keyguard is now showing, both notifications are marked as seen + keyguardRepository.setKeyguardShowing(true) + testScheduler.runCurrent() + + // WHEN: The child notification is now unseen + collectionListener.onEntryUpdated(fakeChild) + + // THEN: The summary is not filtered out, because the child is unseen + assertThat(unseenFilter.shouldFilterOut(fakeSummary, 0L)).isFalse() + } + } + + @Test + fun unseenNotificationIsMarkedAsSeenWhenKeyguardGoesAway() { + // GIVEN: Keyguard is showing, not dozing, unseen notification is present + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setIsDozing(false) + runKeyguardCoordinatorTest { + val fakeEntry = NotificationEntryBuilder().build() + collectionListener.onEntryAdded(fakeEntry) + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + this.testScheduler, + ) + testScheduler.runCurrent() + + // WHEN: five seconds have passed + testScheduler.advanceTimeBy(5.seconds) + testScheduler.runCurrent() + + // WHEN: Keyguard is no longer showing + keyguardRepository.setKeyguardShowing(false) + kosmos.setTransition( + sceneTransition = Idle(Scenes.Gone), + stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE) + ) + + // WHEN: Keyguard is shown again + keyguardRepository.setKeyguardShowing(true) + kosmos.setTransition( + sceneTransition = Idle(Scenes.Lockscreen), + stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD) + ) + + // THEN: The notification is now recognized as "seen" and is filtered out. + assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue() + } + } + + @Test + fun unseenNotificationIsNotMarkedAsSeenIfShadeNotExpanded() { + // GIVEN: Keyguard is showing, unseen notification is present + keyguardRepository.setKeyguardShowing(true) + runKeyguardCoordinatorTest { + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.LOCKSCREEN, + this.testScheduler, + ) + val fakeEntry = NotificationEntryBuilder().build() + collectionListener.onEntryAdded(fakeEntry) + + // WHEN: Keyguard is no longer showing + keyguardRepository.setKeyguardShowing(false) + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + this.testScheduler, + ) + + // WHEN: Keyguard is shown again + keyguardRepository.setKeyguardShowing(true) + testScheduler.runCurrent() + + // THEN: The notification is not recognized as "seen" and is not filtered out. + assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() + } + } + + @Test + fun unseenNotificationIsNotMarkedAsSeenIfNotOnKeyguardLongEnough() { + // GIVEN: Keyguard is showing, not dozing, unseen notification is present + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setIsDozing(false) + runKeyguardCoordinatorTest { + kosmos.setTransition( + sceneTransition = Idle(Scenes.Lockscreen), + stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.LOCKSCREEN) + ) + val firstEntry = NotificationEntryBuilder().setId(1).build() + collectionListener.onEntryAdded(firstEntry) + testScheduler.runCurrent() + + // WHEN: one second has passed + testScheduler.advanceTimeBy(1.seconds) + testScheduler.runCurrent() + + // WHEN: another unseen notification is posted + val secondEntry = NotificationEntryBuilder().setId(2).build() + collectionListener.onEntryAdded(secondEntry) + testScheduler.runCurrent() + + // WHEN: four more seconds have passed + testScheduler.advanceTimeBy(4.seconds) + testScheduler.runCurrent() + + // WHEN: the keyguard is no longer showing + keyguardRepository.setKeyguardShowing(false) + kosmos.setTransition( + sceneTransition = Idle(Scenes.Gone), + stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE) + ) + + // WHEN: Keyguard is shown again + keyguardRepository.setKeyguardShowing(true) + kosmos.setTransition( + sceneTransition = Idle(Scenes.Lockscreen), + stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.LOCKSCREEN) + ) + + // THEN: The first notification is considered seen and is filtered out. + assertThat(unseenFilter.shouldFilterOut(firstEntry, 0L)).isTrue() + + // THEN: The second notification is still considered unseen and is not filtered out + assertThat(unseenFilter.shouldFilterOut(secondEntry, 0L)).isFalse() + } + } + + @Test + fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedAfterThreshold() { + // GIVEN: Keyguard is showing, not dozing + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setIsDozing(false) + runKeyguardCoordinatorTest { + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.LOCKSCREEN, + this.testScheduler, + ) + testScheduler.runCurrent() + + // WHEN: a new notification is posted + val entry = NotificationEntryBuilder().setId(1).build() + collectionListener.onEntryAdded(entry) + testScheduler.runCurrent() + + // WHEN: five more seconds have passed + testScheduler.advanceTimeBy(5.seconds) + testScheduler.runCurrent() + + // WHEN: the notification is removed + collectionListener.onEntryRemoved(entry, 0) + testScheduler.runCurrent() + + // WHEN: the notification is re-posted + collectionListener.onEntryAdded(entry) + testScheduler.runCurrent() + + // WHEN: one more second has passed + testScheduler.advanceTimeBy(1.seconds) + testScheduler.runCurrent() + + // WHEN: the keyguard is no longer showing + keyguardRepository.setKeyguardShowing(false) + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + this.testScheduler, + ) + testScheduler.runCurrent() + + // WHEN: Keyguard is shown again + keyguardRepository.setKeyguardShowing(true) + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.LOCKSCREEN, + this.testScheduler, + ) + testScheduler.runCurrent() + + // THEN: The notification is considered unseen and is not filtered out. + assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse() + } + } + + @Test + fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedBeforeThreshold() { + // GIVEN: Keyguard is showing, not dozing + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setIsDozing(false) + runKeyguardCoordinatorTest { + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.LOCKSCREEN, + this.testScheduler, + ) + testScheduler.runCurrent() + + // WHEN: a new notification is posted + val entry = NotificationEntryBuilder().setId(1).build() + collectionListener.onEntryAdded(entry) + testScheduler.runCurrent() + + // WHEN: one second has passed + testScheduler.advanceTimeBy(1.seconds) + testScheduler.runCurrent() + + // WHEN: the notification is removed + collectionListener.onEntryRemoved(entry, 0) + testScheduler.runCurrent() + + // WHEN: the notification is re-posted + collectionListener.onEntryAdded(entry) + testScheduler.runCurrent() + + // WHEN: one more second has passed + testScheduler.advanceTimeBy(1.seconds) + testScheduler.runCurrent() + + // WHEN: the keyguard is no longer showing + keyguardRepository.setKeyguardShowing(false) + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + this.testScheduler, + ) + testScheduler.runCurrent() + + // WHEN: Keyguard is shown again + keyguardRepository.setKeyguardShowing(true) + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.LOCKSCREEN, + this.testScheduler, + ) + testScheduler.runCurrent() + + // THEN: The notification is considered unseen and is not filtered out. + assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse() + } + } + + @Test + fun unseenNotificationOnKeyguardNotMarkedAsSeenIfUpdatedBeforeThreshold() { + // GIVEN: Keyguard is showing, not dozing + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setIsDozing(false) + runKeyguardCoordinatorTest { + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.LOCKSCREEN, + this.testScheduler, + ) + testScheduler.runCurrent() + + // WHEN: a new notification is posted + val entry = NotificationEntryBuilder().setId(1).build() + collectionListener.onEntryAdded(entry) + testScheduler.runCurrent() + + // WHEN: one second has passed + testScheduler.advanceTimeBy(1.seconds) + testScheduler.runCurrent() + + // WHEN: the notification is updated + collectionListener.onEntryUpdated(entry) + testScheduler.runCurrent() + + // WHEN: four more seconds have passed + testScheduler.advanceTimeBy(4.seconds) + testScheduler.runCurrent() + + // WHEN: the keyguard is no longer showing + keyguardRepository.setKeyguardShowing(false) + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + this.testScheduler, + ) + testScheduler.runCurrent() + + // WHEN: Keyguard is shown again + keyguardRepository.setKeyguardShowing(true) + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.LOCKSCREEN, + this.testScheduler, + ) + testScheduler.runCurrent() + + // THEN: The notification is considered unseen and is not filtered out. + assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse() + } + } + + private fun runKeyguardCoordinatorTest( + testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit + ) { + val testDispatcher = UnconfinedTestDispatcher() + val testScope = TestScope(testDispatcher) + val fakeSettings = + FakeSettings().apply { + putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1) + } + val seenNotificationsInteractor = + SeenNotificationsInteractor(ActiveNotificationListRepository()) + val keyguardCoordinator = + OriginalUnseenKeyguardCoordinator( + testDispatcher, + mock<DumpManager>(), + headsUpManager, + keyguardRepository, + kosmos.keyguardTransitionInteractor, + KeyguardCoordinatorLogger(logcatLogBuffer()), + testScope.backgroundScope, + fakeSettings, + seenNotificationsInteractor, + statusBarStateController, + ) + keyguardCoordinator.attach(notifPipeline) + testScope.runTest { + KeyguardCoordinatorTestScope( + keyguardCoordinator, + testScope, + seenNotificationsInteractor, + fakeSettings, + ) + .testBlock() + } + } + + private inner class KeyguardCoordinatorTestScope( + private val keyguardCoordinator: OriginalUnseenKeyguardCoordinator, + private val scope: TestScope, + val seenNotificationsInteractor: SeenNotificationsInteractor, + private val fakeSettings: FakeSettings, + ) : CoroutineScope by scope { + val testScheduler: TestCoroutineScheduler + get() = scope.testScheduler + + val unseenFilter: NotifFilter + get() = keyguardCoordinator.unseenNotifFilter + + val collectionListener: NotifCollectionListener = + argumentCaptor { verify(notifPipeline).addCollectionListener(capture()) }.lastValue + + val onHeadsUpChangedListener: OnHeadsUpChangedListener + get() = argumentCaptor { verify(headsUpManager).addListener(capture()) }.lastValue + + var showOnlyUnseenNotifsOnKeyguardSetting: Boolean + get() = + fakeSettings.getIntForUser( + Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, + UserHandle.USER_CURRENT, + ) == 1 + set(value) { + fakeSettings.putIntForUser( + Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, + if (value) 1 else 2, + UserHandle.USER_CURRENT, + ) + } + } + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf().andSceneContainer() + } + } +} diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 2bd97d9a2f91..7caa2c65c64a 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1084,6 +1084,15 @@ <!-- QuickStep: Accessibility to toggle overview [CHAR LIMIT=40] --> <string name="quick_step_accessibility_toggle_overview">Toggle Overview</string> + <!-- Priority modes dialog title [CHAR LIMIT=35] --> + <string name="zen_modes_dialog_title">Priority modes</string> + + <!-- Priority modes dialog confirmation button [CHAR LIMIT=15] --> + <string name="zen_modes_dialog_done">Done</string> + + <!-- Priority modes dialog settings shortcut button [CHAR LIMIT=15] --> + <string name="zen_modes_dialog_settings">Settings</string> + <!-- Zen mode: Priority only introduction message on first use --> <string name="zen_priority_introduction">You won\'t be disturbed by sounds and vibrations, except from alarms, reminders, events, and callers you specify. You\'ll still hear anything you choose to play including music, videos, and games.</string> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java index 10d1891c8405..0f61233ac64f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java @@ -34,6 +34,7 @@ import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor; +import com.android.systemui.Flags; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.res.R; @@ -130,7 +131,10 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB verifyPasswordAndUnlock(); } }); - okButton.setOnHoverListener(mLiftToActivateListener); + + if (!Flags.simPinTalkbackFixForDoubleSubmit()) { + okButton.setOnHoverListener(mLiftToActivateListener); + } } if (pinInputFieldStyledFocusState()) { collectFlow(mPasswordEntry, mKeyguardKeyboardInteractor.isAnyKeyboardConnected(), diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index c95a94e5e388..b10d37e5c27a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -34,7 +34,9 @@ import com.android.settingslib.Utils import com.android.systemui.CoreStartable import com.android.systemui.Flags.lightRevealMigration import com.android.systemui.biometrics.data.repository.FacePropertyRepository +import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams +import com.android.systemui.biometrics.shared.model.toSensorType import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.AuthRippleInteractor import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor @@ -102,6 +104,7 @@ constructor( private var udfpsController: UdfpsController? = null private var udfpsRadius: Float = -1f + private var udfpsType: FingerprintSensorType = FingerprintSensorType.UNKNOWN override fun start() { init() @@ -370,8 +373,11 @@ constructor( private val udfpsControllerCallback = object : UdfpsController.Callback { override fun onFingerDown() { - // only show dwell ripple for device entry - if (keyguardUpdateMonitor.isFingerprintDetectionRunning) { + // only show dwell ripple for device entry non-ultrasonic udfps + if ( + keyguardUpdateMonitor.isFingerprintDetectionRunning && + udfpsType != FingerprintSensorType.UDFPS_ULTRASONIC + ) { showDwellRipple() } } @@ -397,6 +403,7 @@ constructor( if (it.size > 0) { udfpsController = udfpsControllerProvider.get() udfpsRadius = authController.udfpsRadius + udfpsType = it[0].sensorType.toSensorType() if (mView.isAttachedToWindow) { udfpsController?.addCallback(udfpsControllerCallback) diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt index 3c5e571f5181..c28bce2e0de1 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt @@ -134,7 +134,7 @@ constructor( // TODO(b/243695312): Encapsulate all of the show logic for the bouncer. /** Show the bouncer if necessary and set the relevant states. */ @JvmOverloads - fun show(isScrimmed: Boolean) { + fun show(isScrimmed: Boolean): Boolean { // When the scene container framework is enabled, instead of calling this, call // SceneInteractor#changeScene(Scenes.Bouncer, ...). SceneContainerFlag.assertInLegacyMode() @@ -146,44 +146,48 @@ constructor( "primaryBouncerDelegate is set. Let's exit early so we don't " + "set the wrong primaryBouncer state." ) - return + return false } - // Reset some states as we show the bouncer. - repository.setKeyguardAuthenticatedBiometrics(null) - repository.setPrimaryStartingToHide(false) + try { + Trace.beginSection("KeyguardBouncer#show") + // Reset some states as we show the bouncer. + repository.setKeyguardAuthenticatedBiometrics(null) + repository.setPrimaryStartingToHide(false) - val resumeBouncer = - (isBouncerShowing() || repository.primaryBouncerShowingSoon.value) && - needsFullscreenBouncer() + val resumeBouncer = + (isBouncerShowing() || repository.primaryBouncerShowingSoon.value) && + needsFullscreenBouncer() - Trace.beginSection("KeyguardBouncer#show") - repository.setPrimaryScrimmed(isScrimmed) - if (isScrimmed) { - setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE) - } + repository.setPrimaryScrimmed(isScrimmed) + if (isScrimmed) { + setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE) + } - // In this special case, we want to hide the bouncer and show it again. We want to emit - // show(true) again so that we can reinflate the new view. - if (resumeBouncer) { - repository.setPrimaryShow(false) - } + // In this special case, we want to hide the bouncer and show it again. We want to emit + // show(true) again so that we can reinflate the new view. + if (resumeBouncer) { + repository.setPrimaryShow(false) + } - if (primaryBouncerView.delegate?.showNextSecurityScreenOrFinish() == true) { - // Keyguard is done. - return - } + if (primaryBouncerView.delegate?.showNextSecurityScreenOrFinish() == true) { + // Keyguard is done. + return false + } - repository.setPrimaryShowingSoon(true) - if (usePrimaryBouncerPassiveAuthDelay()) { - Log.d(TAG, "delay bouncer, passive auth may succeed") - mainHandler.postDelayed(showRunnable, passiveAuthBouncerDelay) - } else { - DejankUtils.postAfterTraversal(showRunnable) + repository.setPrimaryShowingSoon(true) + if (usePrimaryBouncerPassiveAuthDelay()) { + Log.d(TAG, "delay bouncer, passive auth may succeed") + mainHandler.postDelayed(showRunnable, passiveAuthBouncerDelay) + } else { + DejankUtils.postAfterTraversal(showRunnable) + } + keyguardStateController.notifyPrimaryBouncerShowing(true) + primaryBouncerCallbackInteractor.dispatchStartingToShow() + return true + } finally { + Trace.endSection() } - keyguardStateController.notifyPrimaryBouncerShowing(true) - primaryBouncerCallbackInteractor.dispatchStartingToShow() - Trace.endSection() } /** Sets the correct bouncer states to hide the bouncer. */ 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 46c5c188344e..c5d7b25827ea 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 @@ -888,6 +888,8 @@ constructor( heightInSceneContainerPx = height mediaCarouselScrollHandler.playerWidthPlusPadding = width + context.resources.getDimensionPixelSize(R.dimen.qs_media_padding) + mediaContent.minimumWidth = widthInSceneContainerPx + mediaContent.minimumHeight = heightInSceneContainerPx updatePlayers(recreateMedia = true) } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java index 1dbd500c15f1..c4abcd2afc4f 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java @@ -54,6 +54,7 @@ import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; +import android.view.inputmethod.Flags; import android.widget.FrameLayout; import androidx.annotation.Nullable; @@ -285,8 +286,11 @@ public class NavigationBarView extends FrameLayout { // Set up the context group of buttons mContextualButtonGroup = new ContextualButtonGroup(R.id.menu_container); + final int switcherResId = Flags.imeSwitcherRevamp() + ? com.android.internal.R.drawable.ic_ime_switcher_new + : R.drawable.ic_ime_switcher_default; final ContextualButton imeSwitcherButton = new ContextualButton(R.id.ime_switcher, - mLightContext, R.drawable.ic_ime_switcher_default); + mLightContext, switcherResId); final ContextualButton accessibilityButton = new ContextualButton(R.id.accessibility_button, mLightContext, R.drawable.ic_sysbar_accessibility_button); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt index b91891cf7be0..a3000316057f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt @@ -44,6 +44,7 @@ import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.res.R import javax.inject.Inject import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking class ModesTile @Inject @@ -91,8 +92,8 @@ constructor( override fun newTileState() = BooleanState() - override fun handleClick(expandable: Expandable?) { - // TODO(b/346519570) open dialog + override fun handleClick(expandable: Expandable?) = runBlocking { + userActionInteractor.handleClick(expandable) } override fun getLongClickIntent(): Intent = userActionInteractor.longClickIntent @@ -107,6 +108,7 @@ constructor( label = tileLabel secondaryLabel = tileState.secondaryLabel contentDescription = tileState.contentDescription + forceExpandIcon = true } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt index fd1f3d8fb23a..4c6563d6c143 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt @@ -16,19 +16,31 @@ package com.android.systemui.qs.tiles.impl.modes.domain.interactor +//noinspection CleanArchitectureDependencyViolation: dialog needs to be opened on click import android.content.Intent import android.provider.Settings +import com.android.internal.jank.InteractionJankMonitor +import com.android.systemui.animation.DialogCuj +import com.android.systemui.animation.DialogTransitionAnimator +import com.android.systemui.animation.Expandable +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler import com.android.systemui.qs.tiles.base.interactor.QSTileInput import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction +import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.withContext class ModesTileUserActionInteractor @Inject constructor( + @Main private val coroutineContext: CoroutineContext, private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler, + private val dialogTransitionAnimator: DialogTransitionAnimator, + private val dialogDelegate: ModesDialogDelegate, ) : QSTileUserActionInteractor<ModesTileModel> { val longClickIntent = Intent(Settings.ACTION_ZEN_MODE_SETTINGS) @@ -36,7 +48,7 @@ constructor( with(input) { when (action) { is QSTileUserAction.Click -> { - // TODO(b/346519570) open dialog + handleClick(action.expandable) } is QSTileUserAction.LongClick -> { qsTileIntentUserActionHandler.handle(action.expandable, longClickIntent) @@ -44,4 +56,24 @@ constructor( } } } + + suspend fun handleClick(expandable: Expandable?) { + // Show a dialog with the list of modes to configure. Dialogs shown by the + // DialogTransitionAnimator must be created and shown on the main thread, so we post it to + // the UI handler. + withContext(coroutineContext) { + val dialog = dialogDelegate.createDialog() + + expandable + ?.dialogTransitionController( + DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG) + ) + ?.let { controller -> dialogTransitionAnimator.show(dialog, controller) } + ?: dialog.show() + } + } + + companion object { + private const val INTERACTION_JANK_TAG = "configure_priority_modes" + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt index 26b9a4c7f416..7048adab329d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt @@ -59,5 +59,6 @@ constructor( QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK, ) + sideViewIcon = QSTileState.SideViewIcon.Chevron } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt index 11ccdff687a1..59fd0ca4513e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt @@ -57,7 +57,7 @@ constructor( interactor.ongoingCallState .map { state -> when (state) { - is OngoingCallModel.NoCall -> OngoingActivityChipModel.Hidden + is OngoingCallModel.NoCall -> OngoingActivityChipModel.Hidden() is OngoingCallModel.InCall -> { // This block mimics OngoingCallController#updateChip. if (state.startTimeMs <= 0L) { @@ -82,7 +82,7 @@ constructor( } } } - .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden) + .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden()) private fun getOnClickListener(state: OngoingCallModel.InCall): View.OnClickListener? { if (state.intent == null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt index bafec38efe9d..6ea72b97cb3a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt @@ -44,9 +44,10 @@ class EndCastScreenToOtherDeviceDialogDelegate( // No custom on-click, because the dialog will automatically be dismissed when the // button is clicked anyway. setNegativeButton(R.string.close_dialog_button, /* onClick= */ null) - setPositiveButton(R.string.cast_to_other_device_stop_dialog_button) { _, _ -> - stopAction.invoke() - } + setPositiveButton( + R.string.cast_to_other_device_stop_dialog_button, + endMediaProjectionDialogHelper.wrapStopAction(stopAction), + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegate.kt index 7dc9b255badc..b0c832172776 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegate.kt @@ -55,9 +55,10 @@ class EndGenericCastToOtherDeviceDialogDelegate( // No custom on-click, because the dialog will automatically be dismissed when the // button is clicked anyway. setNegativeButton(R.string.close_dialog_button, /* onClick= */ null) - setPositiveButton(R.string.cast_to_other_device_stop_dialog_button) { _, _ -> - stopAction.invoke() - } + setPositiveButton( + R.string.cast_to_other_device_stop_dialog_button, + endMediaProjectionDialogHelper.wrapStopAction(stopAction), + ) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt index afa9ccefab86..d9b0504308f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt @@ -18,6 +18,9 @@ package com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel import android.content.Context import androidx.annotation.DrawableRes +import com.android.internal.jank.Cuj +import com.android.systemui.animation.DialogCuj +import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton @@ -35,6 +38,7 @@ import com.android.systemui.statusbar.chips.mediaprojection.domain.model.Project import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel +import com.android.systemui.statusbar.chips.ui.viewmodel.ChipTransitionHelper import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener import com.android.systemui.util.time.SystemClock @@ -60,6 +64,7 @@ constructor( private val mediaProjectionChipInteractor: MediaProjectionChipInteractor, private val mediaRouterChipInteractor: MediaRouterChipInteractor, private val systemClock: SystemClock, + private val dialogTransitionAnimator: DialogTransitionAnimator, private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper, @StatusBarChipsLog private val logger: LogBuffer, ) : OngoingActivityChipViewModel { @@ -74,18 +79,18 @@ constructor( mediaProjectionChipInteractor.projection .map { projectionModel -> when (projectionModel) { - is ProjectionChipModel.NotProjecting -> OngoingActivityChipModel.Hidden + is ProjectionChipModel.NotProjecting -> OngoingActivityChipModel.Hidden() is ProjectionChipModel.Projecting -> { if (projectionModel.type != ProjectionChipModel.Type.CAST_TO_OTHER_DEVICE) { - OngoingActivityChipModel.Hidden + OngoingActivityChipModel.Hidden() } else { createCastScreenToOtherDeviceChip(projectionModel) } } } } - // See b/347726238. - .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden) + // See b/347726238 for [SharingStarted.Lazily] reasoning. + .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden()) /** * The cast chip to show, based only on MediaRouter API events. @@ -109,7 +114,7 @@ constructor( mediaRouterChipInteractor.mediaRouterCastingState .map { routerModel -> when (routerModel) { - is MediaRouterCastModel.DoingNothing -> OngoingActivityChipModel.Hidden + is MediaRouterCastModel.DoingNothing -> OngoingActivityChipModel.Hidden() is MediaRouterCastModel.Casting -> { // A consequence of b/269975671 is that MediaRouter will mark a device as // casting before casting has actually started. To alleviate this bug a bit, @@ -123,9 +128,9 @@ constructor( } } } - .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden) + .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden()) - override val chip: StateFlow<OngoingActivityChipModel> = + private val internalChip: StateFlow<OngoingActivityChipModel> = combine(projectionChip, routerChip) { projection, router -> logger.log( TAG, @@ -159,17 +164,24 @@ constructor( router } } - .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden) + .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden()) + + private val hideChipDuringDialogTransitionHelper = ChipTransitionHelper(scope) + + override val chip: StateFlow<OngoingActivityChipModel> = + hideChipDuringDialogTransitionHelper.createChipFlow(internalChip) /** Stops the currently active projection. */ - private fun stopProjecting() { - logger.log(TAG, LogLevel.INFO, {}, { "Stop casting requested (projection)" }) + private fun stopProjectingFromDialog() { + logger.log(TAG, LogLevel.INFO, {}, { "Stop casting requested from dialog (projection)" }) + hideChipDuringDialogTransitionHelper.onActivityStoppedFromDialog() mediaProjectionChipInteractor.stopProjecting() } /** Stops the currently active media route. */ - private fun stopMediaRouterCasting() { - logger.log(TAG, LogLevel.INFO, {}, { "Stop casting requested (router)" }) + private fun stopMediaRouterCastingFromDialog() { + logger.log(TAG, LogLevel.INFO, {}, { "Stop casting requested from dialog (router)" }) + hideChipDuringDialogTransitionHelper.onActivityStoppedFromDialog() mediaRouterChipInteractor.stopCasting() } @@ -190,6 +202,8 @@ constructor( startTimeMs = systemClock.elapsedRealtime(), createDialogLaunchOnClickListener( createCastScreenToOtherDeviceDialogDelegate(state), + dialogTransitionAnimator, + DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Cast to other device"), logger, TAG, ), @@ -207,6 +221,11 @@ constructor( colors = ColorsModel.Red, createDialogLaunchOnClickListener( createGenericCastToOtherDeviceDialogDelegate(deviceName), + dialogTransitionAnimator, + DialogCuj( + Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, + tag = "Cast to other device audio only", + ), logger, TAG, ), @@ -219,7 +238,7 @@ constructor( EndCastScreenToOtherDeviceDialogDelegate( endMediaProjectionDialogHelper, context, - stopAction = this::stopProjecting, + stopAction = this::stopProjectingFromDialog, state, ) @@ -228,7 +247,7 @@ constructor( endMediaProjectionDialogHelper, context, deviceName, - stopAction = this::stopMediaRouterCasting, + stopAction = this::stopMediaRouterCastingFromDialog, ) companion object { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt index 600436557efb..2d9ccb7b09b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt @@ -17,7 +17,9 @@ package com.android.systemui.statusbar.chips.mediaprojection.ui.view import android.app.ActivityManager +import android.content.DialogInterface import android.content.pm.PackageManager +import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.dagger.SysUISingleton import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.statusbar.phone.SystemUIDialog @@ -29,6 +31,7 @@ class EndMediaProjectionDialogHelper @Inject constructor( private val dialogFactory: SystemUIDialog.Factory, + private val dialogTransitionAnimator: DialogTransitionAnimator, private val packageManager: PackageManager, ) { /** Creates a new [SystemUIDialog] using the given delegate. */ @@ -36,6 +39,28 @@ constructor( return dialogFactory.create(delegate) } + /** + * Returns the click listener that should be invoked if a user clicks "Stop" on the end media + * projection dialog. + * + * The click listener will invoke [stopAction] and also do some UI manipulation. + * + * @param stopAction an action that, when invoked, should notify system API(s) that the media + * projection should be stopped. + */ + fun wrapStopAction(stopAction: () -> Unit): DialogInterface.OnClickListener { + return DialogInterface.OnClickListener { _, _ -> + // If the projection is stopped, then the chip will disappear, so we don't want the + // dialog to animate back into the chip just for the chip to disappear in a few frames. + dialogTransitionAnimator.disableAllCurrentDialogsExitAnimations() + stopAction.invoke() + // TODO(b/332662551): If the projection is stopped, there's a brief moment where the + // dialog closes and the chip re-shows because the system APIs haven't come back and + // told SysUI that the projection has officially stopped. It would be great for the chip + // to not re-show at all. + } + } + fun getAppName(state: MediaProjectionState.Projecting): CharSequence? { val specificTaskInfo = if (state is MediaProjectionState.Projecting.SingleTask) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegate.kt index 1eca827d55c4..72656ca1934c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegate.kt @@ -52,9 +52,10 @@ class EndScreenRecordingDialogDelegate( // No custom on-click, because the dialog will automatically be dismissed when the // button is clicked anyway. setNegativeButton(R.string.close_dialog_button, /* onClick= */ null) - setPositiveButton(R.string.screenrecord_stop_dialog_button) { _, _ -> - stopAction.invoke() - } + setPositiveButton( + R.string.screenrecord_stop_dialog_button, + endMediaProjectionDialogHelper.wrapStopAction(stopAction), + ) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt index 0c349810257a..fcf3de42eb32 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt @@ -19,6 +19,9 @@ package com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel import android.app.ActivityManager import android.content.Context import androidx.annotation.DrawableRes +import com.android.internal.jank.Cuj +import com.android.systemui.animation.DialogCuj +import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton @@ -32,8 +35,10 @@ import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProj import com.android.systemui.statusbar.chips.screenrecord.domain.interactor.ScreenRecordChipInteractor import com.android.systemui.statusbar.chips.screenrecord.domain.model.ScreenRecordChipModel import com.android.systemui.statusbar.chips.screenrecord.ui.view.EndScreenRecordingDialogDelegate +import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.ShareToAppChipViewModel import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel +import com.android.systemui.statusbar.chips.ui.viewmodel.ChipTransitionHelper import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener import com.android.systemui.util.time.SystemClock @@ -52,15 +57,18 @@ constructor( @Application private val scope: CoroutineScope, private val context: Context, private val interactor: ScreenRecordChipInteractor, + private val shareToAppChipViewModel: ShareToAppChipViewModel, private val systemClock: SystemClock, private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper, + private val dialogTransitionAnimator: DialogTransitionAnimator, @StatusBarChipsLog private val logger: LogBuffer, ) : OngoingActivityChipViewModel { - override val chip: StateFlow<OngoingActivityChipModel> = + + private val internalChip = interactor.screenRecordState .map { state -> when (state) { - is ScreenRecordChipModel.DoingNothing -> OngoingActivityChipModel.Hidden + is ScreenRecordChipModel.DoingNothing -> OngoingActivityChipModel.Hidden() is ScreenRecordChipModel.Starting -> { OngoingActivityChipModel.Shown.Countdown( colors = ColorsModel.Red, @@ -80,6 +88,11 @@ constructor( startTimeMs = systemClock.elapsedRealtime(), createDialogLaunchOnClickListener( createDelegate(state.recordedTask), + dialogTransitionAnimator, + DialogCuj( + Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, + tag = "Screen record", + ), logger, TAG, ), @@ -87,8 +100,13 @@ constructor( } } } - // See b/347726238. - .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden) + // See b/347726238 for [SharingStarted.Lazily] reasoning. + .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden()) + + private val chipTransitionHelper = ChipTransitionHelper(scope) + + override val chip: StateFlow<OngoingActivityChipModel> = + chipTransitionHelper.createChipFlow(internalChip) private fun createDelegate( recordedTask: ActivityManager.RunningTaskInfo? @@ -96,13 +114,15 @@ constructor( return EndScreenRecordingDialogDelegate( endMediaProjectionDialogHelper, context, - stopAction = this::stopRecording, + stopAction = this::stopRecordingFromDialog, recordedTask, ) } - private fun stopRecording() { - logger.log(TAG, LogLevel.INFO, {}, { "Stop recording requested" }) + private fun stopRecordingFromDialog() { + logger.log(TAG, LogLevel.INFO, {}, { "Stop recording requested from dialog" }) + chipTransitionHelper.onActivityStoppedFromDialog() + shareToAppChipViewModel.onRecordingStoppedFromDialog() interactor.stopRecording() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegate.kt index 564f20e4b596..d10bd7705ce9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegate.kt @@ -44,9 +44,10 @@ class EndShareToAppDialogDelegate( // No custom on-click, because the dialog will automatically be dismissed when the // button is clicked anyway. setNegativeButton(R.string.close_dialog_button, /* onClick= */ null) - setPositiveButton(R.string.share_to_app_stop_dialog_button) { _, _ -> - stopAction.invoke() - } + setPositiveButton( + R.string.share_to_app_stop_dialog_button, + endMediaProjectionDialogHelper.wrapStopAction(stopAction), + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt index ddebd3a0e3c2..85973fca4326 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt @@ -18,6 +18,9 @@ package com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel import android.content.Context import androidx.annotation.DrawableRes +import com.android.internal.jank.Cuj +import com.android.systemui.animation.DialogCuj +import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton @@ -32,6 +35,7 @@ import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProj import com.android.systemui.statusbar.chips.sharetoapp.ui.view.EndShareToAppDialogDelegate import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel +import com.android.systemui.statusbar.chips.ui.viewmodel.ChipTransitionHelper import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener import com.android.systemui.util.time.SystemClock @@ -55,28 +59,49 @@ constructor( private val mediaProjectionChipInteractor: MediaProjectionChipInteractor, private val systemClock: SystemClock, private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper, + private val dialogTransitionAnimator: DialogTransitionAnimator, @StatusBarChipsLog private val logger: LogBuffer, ) : OngoingActivityChipViewModel { - override val chip: StateFlow<OngoingActivityChipModel> = + private val internalChip = mediaProjectionChipInteractor.projection .map { projectionModel -> when (projectionModel) { - is ProjectionChipModel.NotProjecting -> OngoingActivityChipModel.Hidden + is ProjectionChipModel.NotProjecting -> OngoingActivityChipModel.Hidden() is ProjectionChipModel.Projecting -> { if (projectionModel.type != ProjectionChipModel.Type.SHARE_TO_APP) { - OngoingActivityChipModel.Hidden + OngoingActivityChipModel.Hidden() } else { createShareToAppChip(projectionModel) } } } } - // See b/347726238. - .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden) + // See b/347726238 for [SharingStarted.Lazily] reasoning. + .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden()) + + private val chipTransitionHelper = ChipTransitionHelper(scope) + + override val chip: StateFlow<OngoingActivityChipModel> = + chipTransitionHelper.createChipFlow(internalChip) + + /** + * Notifies this class that the user just stopped a screen recording from the dialog that's + * shown when you tap the recording chip. + */ + fun onRecordingStoppedFromDialog() { + // When a screen recording is active, share-to-app is also active (screen recording is just + // a special case of share-to-app, where the specific app receiving the share is System UI). + // When a screen recording is stopped, we immediately hide the screen recording chip in + // [com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel.ScreenRecordChipViewModel]. + // We *also* need to immediately hide the share-to-app chip so it doesn't briefly show. + // See b/350891338. + chipTransitionHelper.onActivityStoppedFromDialog() + } /** Stops the currently active projection. */ - private fun stopProjecting() { - logger.log(TAG, LogLevel.INFO, {}, { "Stop sharing requested" }) + private fun stopProjectingFromDialog() { + logger.log(TAG, LogLevel.INFO, {}, { "Stop sharing requested from dialog" }) + chipTransitionHelper.onActivityStoppedFromDialog() mediaProjectionChipInteractor.stopProjecting() } @@ -92,7 +117,16 @@ constructor( colors = ColorsModel.Red, // TODO(b/332662551): Maybe use a MediaProjection API to fetch this time. startTimeMs = systemClock.elapsedRealtime(), - createDialogLaunchOnClickListener(createShareToAppDialogDelegate(state), logger, TAG), + createDialogLaunchOnClickListener( + createShareToAppDialogDelegate(state), + dialogTransitionAnimator, + DialogCuj( + Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, + tag = "Share to app", + ), + logger, + TAG, + ), ) } @@ -100,7 +134,7 @@ constructor( EndShareToAppDialogDelegate( endMediaProjectionDialogHelper, context, - stopAction = this::stopProjecting, + stopAction = this::stopProjectingFromDialog, state, ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt index 40f86f924cd5..17cf60bf2dc5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt @@ -24,9 +24,15 @@ sealed class OngoingActivityChipModel { /** Condensed name representing the model, used for logs. */ abstract val logName: String - /** This chip shouldn't be shown. */ - data object Hidden : OngoingActivityChipModel() { - override val logName = "Hidden" + /** + * This chip shouldn't be shown. + * + * @property shouldAnimate true if the transition from [Shown] to [Hidden] should be animated, + * and false if that transition should *not* be animated (i.e. the chip view should + * immediately disappear). + */ + data class Hidden(val shouldAnimate: Boolean = true) : OngoingActivityChipModel() { + override val logName = "Hidden(anim=$shouldAnimate)" } /** This chip should be shown with the given information. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelper.kt new file mode 100644 index 000000000000..92e72c29519a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelper.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.statusbar.chips.ui.viewmodel + +import android.annotation.SuppressLint +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.transformLatest +import kotlinx.coroutines.launch + +/** + * A class that can help [OngoingActivityChipViewModel] instances with various transition states. + * + * For now, this class's only functionality is immediately hiding the chip if the user has tapped an + * activity chip and then clicked "Stop" on the resulting dialog. There's a bit of a delay between + * when the user clicks "Stop" and when the system services notify SysUI that the activity has + * indeed stopped. We don't want the chip to briefly show for a few frames during that delay, so + * this class helps us immediately hide the chip as soon as the user clicks "Stop" in the dialog. + * See b/353249803#comment4. + */ +@OptIn(ExperimentalCoroutinesApi::class) +class ChipTransitionHelper(@Application private val scope: CoroutineScope) { + /** A flow that emits each time the user has clicked "Stop" on the dialog. */ + @SuppressLint("SharedFlowCreation") + private val activityStoppedFromDialogEvent = MutableSharedFlow<Unit>() + + /** True if the user recently stopped the activity from the dialog. */ + private val wasActivityRecentlyStoppedFromDialog: Flow<Boolean> = + activityStoppedFromDialogEvent + .transformLatest { + // Give system services 500ms to stop the activity and notify SysUI. Once more than + // 500ms has elapsed, we should go back to using the current system service + // information as the source of truth. + emit(true) + delay(500) + emit(false) + } + // Use stateIn so that the flow created in [createChipFlow] is guaranteed to + // emit. (`combine`s require that all input flows have emitted.) + .stateIn(scope, SharingStarted.Lazily, false) + + /** + * Notifies this class that the user just clicked "Stop" on the stop dialog that's shown when + * the chip is tapped. + * + * Call this method in order to immediately hide the chip. + */ + fun onActivityStoppedFromDialog() { + // Because this event causes UI changes, make sure it's launched on the main thread scope. + scope.launch { activityStoppedFromDialogEvent.emit(Unit) } + } + + /** + * Creates a flow that will forcibly hide the chip if the user recently stopped the activity + * (see [onActivityStoppedFromDialog]). In general, this flow just uses value in [chip]. + */ + fun createChipFlow(chip: Flow<OngoingActivityChipModel>): StateFlow<OngoingActivityChipModel> { + return combine( + chip, + wasActivityRecentlyStoppedFromDialog, + ) { chipModel, activityRecentlyStopped -> + if (activityRecentlyStopped) { + // There's a bit of a delay between when the user stops an activity via + // SysUI and when the system services notify SysUI that the activity has + // indeed stopped. Prevent the chip from showing during this delay by + // immediately hiding it without any animation. + OngoingActivityChipModel.Hidden(shouldAnimate = false) + } else { + chipModel + } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden()) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt index ee010f7a818b..2fc366b7f078 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt @@ -17,10 +17,14 @@ package com.android.systemui.statusbar.chips.ui.viewmodel import android.view.View +import com.android.systemui.animation.DialogCuj +import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel +import com.android.systemui.res.R import com.android.systemui.statusbar.chips.StatusBarChipsLog import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel +import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.phone.SystemUIDialog import kotlinx.coroutines.flow.StateFlow @@ -36,13 +40,19 @@ interface OngoingActivityChipViewModel { /** Creates a chip click listener that launches a dialog created by [dialogDelegate]. */ fun createDialogLaunchOnClickListener( dialogDelegate: SystemUIDialog.Delegate, + dialogTransitionAnimator: DialogTransitionAnimator, + cuj: DialogCuj, @StatusBarChipsLog logger: LogBuffer, tag: String, ): View.OnClickListener { - return View.OnClickListener { _ -> + return View.OnClickListener { view -> logger.log(tag, LogLevel.INFO, {}, { "Chip clicked" }) val dialog = dialogDelegate.createDialog() - dialog.show() + val launchableView = + view.requireViewById<ChipBackgroundContainer>( + R.id.ongoing_activity_chip_background + ) + dialogTransitionAnimator.showFromView(dialog, launchableView, cuj) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt index 15c348ed2f67..b0d897def53f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt @@ -26,11 +26,14 @@ import com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel.CastT import com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel.ScreenRecordChipViewModel import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.ShareToAppChipViewModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel +import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn /** @@ -50,49 +53,132 @@ constructor( callChipViewModel: CallChipViewModel, @StatusBarChipsLog private val logger: LogBuffer, ) { + private enum class ChipType { + ScreenRecord, + ShareToApp, + CastToOtherDevice, + Call, + } + + /** Model that helps us internally track the various chip states from each of the types. */ + private sealed interface InternalChipModel { + /** + * Represents that we've internally decided to show the chip with type [type] with the given + * [model] information. + */ + data class Shown(val type: ChipType, val model: OngoingActivityChipModel.Shown) : + InternalChipModel + + /** + * Represents that all chip types would like to be hidden. Each value specifies *how* that + * chip type should get hidden. + */ + data class Hidden( + val screenRecord: OngoingActivityChipModel.Hidden, + val shareToApp: OngoingActivityChipModel.Hidden, + val castToOtherDevice: OngoingActivityChipModel.Hidden, + val call: OngoingActivityChipModel.Hidden, + ) : InternalChipModel + } + + private val internalChip: Flow<InternalChipModel> = + combine( + screenRecordChipViewModel.chip, + shareToAppChipViewModel.chip, + castToOtherDeviceChipViewModel.chip, + callChipViewModel.chip, + ) { screenRecord, shareToApp, castToOtherDevice, call -> + logger.log( + TAG, + LogLevel.INFO, + { + str1 = screenRecord.logName + str2 = shareToApp.logName + str3 = castToOtherDevice.logName + }, + { "Chips: ScreenRecord=$str1 > ShareToApp=$str2 > CastToOther=$str3..." }, + ) + logger.log(TAG, LogLevel.INFO, { str1 = call.logName }, { "... > Call=$str1" }) + // This `when` statement shows the priority order of the chips. + when { + // Screen recording also activates the media projection APIs, so whenever the + // screen recording chip is active, the media projection chip would also be + // active. We want the screen-recording-specific chip shown in this case, so we + // give the screen recording chip priority. See b/296461748. + screenRecord is OngoingActivityChipModel.Shown -> + InternalChipModel.Shown(ChipType.ScreenRecord, screenRecord) + shareToApp is OngoingActivityChipModel.Shown -> + InternalChipModel.Shown(ChipType.ShareToApp, shareToApp) + castToOtherDevice is OngoingActivityChipModel.Shown -> + InternalChipModel.Shown(ChipType.CastToOtherDevice, castToOtherDevice) + call is OngoingActivityChipModel.Shown -> + InternalChipModel.Shown(ChipType.Call, call) + else -> { + // We should only get here if all chip types are hidden + check(screenRecord is OngoingActivityChipModel.Hidden) + check(shareToApp is OngoingActivityChipModel.Hidden) + check(castToOtherDevice is OngoingActivityChipModel.Hidden) + check(call is OngoingActivityChipModel.Hidden) + InternalChipModel.Hidden( + screenRecord = screenRecord, + shareToApp = shareToApp, + castToOtherDevice = castToOtherDevice, + call = call, + ) + } + } + } + /** * A flow modeling the chip that should be shown in the status bar after accounting for possibly - * multiple ongoing activities. + * multiple ongoing activities and animation requirements. * * [com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment] is responsible for * actually displaying the chip. */ val chip: StateFlow<OngoingActivityChipModel> = - combine( - screenRecordChipViewModel.chip, - shareToAppChipViewModel.chip, - castToOtherDeviceChipViewModel.chip, - callChipViewModel.chip, - ) { screenRecord, shareToApp, castToOtherDevice, call -> - logger.log( - TAG, - LogLevel.INFO, - { - str1 = screenRecord.logName - str2 = shareToApp.logName - str3 = castToOtherDevice.logName - }, - { "Chips: ScreenRecord=$str1 > ShareToApp=$str2 > CastToOther=$str3..." }, - ) - logger.log(TAG, LogLevel.INFO, { str1 = call.logName }, { "... > Call=$str1" }) - // This `when` statement shows the priority order of the chips - when { - // Screen recording also activates the media projection APIs, so whenever the - // screen recording chip is active, the media projection chip would also be - // active. We want the screen-recording-specific chip shown in this case, so we - // give the screen recording chip priority. See b/296461748. - screenRecord is OngoingActivityChipModel.Shown -> screenRecord - shareToApp is OngoingActivityChipModel.Shown -> shareToApp - castToOtherDevice is OngoingActivityChipModel.Shown -> castToOtherDevice - else -> call + internalChip + .pairwise(initialValue = DEFAULT_INTERNAL_HIDDEN_MODEL) + .map { (old, new) -> + if (old is InternalChipModel.Shown && new is InternalChipModel.Hidden) { + // If we're transitioning from showing the chip to hiding the chip, different + // chips require different animation behaviors. For example, the screen share + // chips shouldn't animate if the user stopped the screen share from the dialog + // (see b/353249803#comment4), but the call chip should always animate. + // + // This `when` block makes sure that when we're transitioning from Shown to + // Hidden, we check what chip type was previously showing and we use that chip + // type's hide animation behavior. + when (old.type) { + ChipType.ScreenRecord -> new.screenRecord + ChipType.ShareToApp -> new.shareToApp + ChipType.CastToOtherDevice -> new.castToOtherDevice + ChipType.Call -> new.call + } + } else if (new is InternalChipModel.Shown) { + // If we have a chip to show, always show it. + new.model + } else { + // In the Hidden -> Hidden transition, it shouldn't matter which hidden model we + // choose because no animation should happen regardless. + OngoingActivityChipModel.Hidden() } } // Some of the chips could have timers in them and we don't want the start time // for those timers to get reset for any reason. So, as soon as any subscriber has - // requested the chip information, we need to maintain it forever. See b/347726238. - .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden) + // requested the chip information, we maintain it forever by using + // [SharingStarted.Lazily]. See b/347726238. + .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden()) companion object { private const val TAG = "ChipsViewModel" + + private val DEFAULT_INTERNAL_HIDDEN_MODEL = + InternalChipModel.Hidden( + screenRecord = OngoingActivityChipModel.Hidden(), + shareToApp = OngoingActivityChipModel.Hidden(), + castToOtherDevice = OngoingActivityChipModel.Hidden(), + call = OngoingActivityChipModel.Hidden(), + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt index 55c6790d4fb1..b1b2a653fde2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt @@ -16,62 +16,15 @@ package com.android.systemui.statusbar.notification.collection.coordinator -import android.app.NotificationManager -import android.os.UserHandle -import android.provider.Settings -import androidx.annotation.VisibleForTesting -import com.android.systemui.Dumpable -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.dump.DumpManager -import com.android.systemui.keyguard.data.repository.KeyguardRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.StatusBarState -import com.android.systemui.statusbar.expansionChanges -import com.android.systemui.statusbar.notification.collection.GroupEntry -import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner -import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider -import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider -import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype -import com.android.systemui.statusbar.notification.stack.BUCKET_TOP_ONGOING -import com.android.systemui.statusbar.notification.stack.BUCKET_TOP_UNSEEN -import com.android.systemui.statusbar.policy.HeadsUpManager -import com.android.systemui.statusbar.policy.headsUpEvents -import com.android.systemui.util.asIndenting -import com.android.systemui.util.indentIfPossible -import com.android.systemui.util.settings.SecureSettings -import com.android.systemui.util.settings.SettingsProxyExt.observerFlow -import java.io.PrintWriter import javax.inject.Inject -import kotlin.time.Duration.Companion.seconds -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.conflate -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.launch -import kotlinx.coroutines.yield /** * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section @@ -82,24 +35,10 @@ import kotlinx.coroutines.yield class KeyguardCoordinator @Inject constructor( - @Background private val bgDispatcher: CoroutineDispatcher, - private val dumpManager: DumpManager, - private val headsUpManager: HeadsUpManager, private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider, - private val keyguardRepository: KeyguardRepository, - private val keyguardTransitionInteractor: KeyguardTransitionInteractor, - private val logger: KeyguardCoordinatorLogger, - @Application private val scope: CoroutineScope, private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider, - private val secureSettings: SecureSettings, - private val seenNotificationsInteractor: SeenNotificationsInteractor, private val statusBarStateController: StatusBarStateController, -) : Coordinator, Dumpable { - - private val unseenNotifications = mutableSetOf<NotificationEntry>() - private val unseenEntryAdded = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1) - private val unseenEntryRemoved = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1) - private var unseenFilterEnabled = false +) : Coordinator { override fun attach(pipeline: NotifPipeline) { setupInvalidateNotifListCallbacks() @@ -107,385 +46,14 @@ constructor( pipeline.addFinalizeFilter(notifFilter) keyguardNotificationVisibilityProvider.addOnStateChangedListener(::invalidateListFromFilter) updateSectionHeadersVisibility() - attachUnseenFilter(pipeline) - } - - private fun attachUnseenFilter(pipeline: NotifPipeline) { - if (NotificationMinimalismPrototype.V2.isEnabled) { - pipeline.addPromoter(unseenNotifPromoter) - pipeline.addOnBeforeTransformGroupsListener(::pickOutTopUnseenNotifs) - } - pipeline.addFinalizeFilter(unseenNotifFilter) - pipeline.addCollectionListener(collectionListener) - scope.launch { trackUnseenFilterSettingChanges() } - dumpManager.registerDumpable(this) - } - - private suspend fun trackSeenNotifications() { - // Whether or not keyguard is visible (or occluded). - val isKeyguardPresent: Flow<Boolean> = - keyguardTransitionInteractor - .transitionValue( - scene = Scenes.Gone, - stateWithoutSceneContainer = KeyguardState.GONE, - ) - .map { it == 0f } - .distinctUntilChanged() - .onEach { trackingUnseen -> logger.logTrackingUnseen(trackingUnseen) } - - // Separately track seen notifications while the device is locked, applying once the device - // is unlocked. - val notificationsSeenWhileLocked = mutableSetOf<NotificationEntry>() - - // Use [collectLatest] to cancel any running jobs when [trackingUnseen] changes. - isKeyguardPresent.collectLatest { isKeyguardPresent: Boolean -> - if (isKeyguardPresent) { - // Keyguard is not gone, notifications need to be visible for a certain threshold - // before being marked as seen - trackSeenNotificationsWhileLocked(notificationsSeenWhileLocked) - } else { - // Mark all seen-while-locked notifications as seen for real. - if (notificationsSeenWhileLocked.isNotEmpty()) { - unseenNotifications.removeAll(notificationsSeenWhileLocked) - logger.logAllMarkedSeenOnUnlock( - seenCount = notificationsSeenWhileLocked.size, - remainingUnseenCount = unseenNotifications.size - ) - notificationsSeenWhileLocked.clear() - } - unseenNotifFilter.invalidateList("keyguard no longer showing") - // Keyguard is gone, notifications can be immediately marked as seen when they - // become visible. - trackSeenNotificationsWhileUnlocked() - } - } - } - - /** - * Keep [notificationsSeenWhileLocked] updated to represent which notifications have actually - * been "seen" while the device is on the keyguard. - */ - private suspend fun trackSeenNotificationsWhileLocked( - notificationsSeenWhileLocked: MutableSet<NotificationEntry>, - ) = coroutineScope { - // Remove removed notifications from the set - launch { - unseenEntryRemoved.collect { entry -> - if (notificationsSeenWhileLocked.remove(entry)) { - logger.logRemoveSeenOnLockscreen(entry) - } - } - } - // Use collectLatest so that the timeout delay is cancelled if the device enters doze, and - // is restarted when doze ends. - keyguardRepository.isDozing.collectLatest { isDozing -> - if (!isDozing) { - trackSeenNotificationsWhileLockedAndNotDozing(notificationsSeenWhileLocked) - } - } - } - - /** - * Keep [notificationsSeenWhileLocked] updated to represent which notifications have actually - * been "seen" while the device is on the keyguard and not dozing. Any new and existing unseen - * notifications are not marked as seen until they are visible for the [SEEN_TIMEOUT] duration. - */ - private suspend fun trackSeenNotificationsWhileLockedAndNotDozing( - notificationsSeenWhileLocked: MutableSet<NotificationEntry> - ) = coroutineScope { - // All child tracking jobs will be cancelled automatically when this is cancelled. - val trackingJobsByEntry = mutableMapOf<NotificationEntry, Job>() - - /** - * Wait for the user to spend enough time on the lock screen before removing notification - * from unseen set upon unlock. - */ - suspend fun trackSeenDurationThreshold(entry: NotificationEntry) { - if (notificationsSeenWhileLocked.remove(entry)) { - logger.logResetSeenOnLockscreen(entry) - } - delay(SEEN_TIMEOUT) - notificationsSeenWhileLocked.add(entry) - trackingJobsByEntry.remove(entry) - logger.logSeenOnLockscreen(entry) - } - - /** Stop any unseen tracking when a notification is removed. */ - suspend fun stopTrackingRemovedNotifs(): Nothing = - unseenEntryRemoved.collect { entry -> - trackingJobsByEntry.remove(entry)?.let { - it.cancel() - logger.logStopTrackingLockscreenSeenDuration(entry) - } - } - - /** Start tracking new notifications when they are posted. */ - suspend fun trackNewUnseenNotifs(): Nothing = coroutineScope { - unseenEntryAdded.collect { entry -> - logger.logTrackingLockscreenSeenDuration(entry) - // If this is an update, reset the tracking. - trackingJobsByEntry[entry]?.let { - it.cancel() - logger.logResetSeenOnLockscreen(entry) - } - trackingJobsByEntry[entry] = launch { trackSeenDurationThreshold(entry) } - } - } - - // Start tracking for all notifications that are currently unseen. - logger.logTrackingLockscreenSeenDuration(unseenNotifications) - unseenNotifications.forEach { entry -> - trackingJobsByEntry[entry] = launch { trackSeenDurationThreshold(entry) } - } - - launch { trackNewUnseenNotifs() } - launch { stopTrackingRemovedNotifs() } - } - - // Track "seen" notifications, marking them as such when either shade is expanded or the - // notification becomes heads up. - private suspend fun trackSeenNotificationsWhileUnlocked() { - coroutineScope { - launch { clearUnseenNotificationsWhenShadeIsExpanded() } - launch { markHeadsUpNotificationsAsSeen() } - } - } - - private suspend fun clearUnseenNotificationsWhenShadeIsExpanded() { - statusBarStateController.expansionChanges.collectLatest { isExpanded -> - // Give keyguard events time to propagate, in case this expansion is part of the - // keyguard transition and not the user expanding the shade - yield() - if (isExpanded) { - logger.logShadeExpanded() - unseenNotifications.clear() - } - } - } - - private suspend fun markHeadsUpNotificationsAsSeen() { - headsUpManager.allEntries - .filter { it.isRowPinned } - .forEach { unseenNotifications.remove(it) } - headsUpManager.headsUpEvents.collect { (entry, isHun) -> - if (isHun) { - logger.logUnseenHun(entry.key) - unseenNotifications.remove(entry) - } - } } - private fun unseenFeatureEnabled(): Flow<Boolean> { - if ( - NotificationMinimalismPrototype.V1.isEnabled || - NotificationMinimalismPrototype.V2.isEnabled - ) { - return flowOf(true) - } - return secureSettings - // emit whenever the setting has changed - .observerFlow( - UserHandle.USER_ALL, - Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, - ) - // perform a query immediately - .onStart { emit(Unit) } - // for each change, lookup the new value - .map { - secureSettings.getIntForUser( - name = Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, - def = 0, - userHandle = UserHandle.USER_CURRENT, - ) == 1 - } - // don't emit anything if nothing has changed - .distinctUntilChanged() - // perform lookups on the bg thread pool - .flowOn(bgDispatcher) - // only track the most recent emission, if events are happening faster than they can be - // consumed - .conflate() - } - - private suspend fun trackUnseenFilterSettingChanges() { - unseenFeatureEnabled().collectLatest { setting -> - // update local field and invalidate if necessary - if (setting != unseenFilterEnabled) { - unseenFilterEnabled = setting - unseenNotifFilter.invalidateList("unseen setting changed") - } - // if the setting is enabled, then start tracking and filtering unseen notifications - if (setting) { - trackSeenNotifications() - } - } - } - - private val collectionListener = - object : NotifCollectionListener { - override fun onEntryAdded(entry: NotificationEntry) { - if ( - keyguardRepository.isKeyguardShowing() || !statusBarStateController.isExpanded - ) { - logger.logUnseenAdded(entry.key) - unseenNotifications.add(entry) - unseenEntryAdded.tryEmit(entry) - } - } - - override fun onEntryUpdated(entry: NotificationEntry) { - if ( - keyguardRepository.isKeyguardShowing() || !statusBarStateController.isExpanded - ) { - logger.logUnseenUpdated(entry.key) - unseenNotifications.add(entry) - unseenEntryAdded.tryEmit(entry) - } - } - - override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { - if (unseenNotifications.remove(entry)) { - logger.logUnseenRemoved(entry.key) - unseenEntryRemoved.tryEmit(entry) - } - } - } - - private fun pickOutTopUnseenNotifs(list: List<ListEntry>) { - if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return - // Only ever elevate a top unseen notification on keyguard, not even locked shade - if (statusBarStateController.state != StatusBarState.KEYGUARD) { - seenNotificationsInteractor.setTopOngoingNotification(null) - seenNotificationsInteractor.setTopUnseenNotification(null) - return - } - // On keyguard pick the top-ranked unseen or ongoing notification to elevate - val nonSummaryEntries: Sequence<NotificationEntry> = - list - .asSequence() - .flatMap { - when (it) { - is NotificationEntry -> listOfNotNull(it) - is GroupEntry -> it.children - else -> error("unhandled type of $it") - } - } - .filter { it.importance >= NotificationManager.IMPORTANCE_DEFAULT } - seenNotificationsInteractor.setTopOngoingNotification( - nonSummaryEntries - .filter { ColorizedFgsCoordinator.isRichOngoing(it) } - .minByOrNull { it.ranking.rank } - ) - seenNotificationsInteractor.setTopUnseenNotification( - nonSummaryEntries - .filter { !ColorizedFgsCoordinator.isRichOngoing(it) && it in unseenNotifications } - .minByOrNull { it.ranking.rank } - ) - } - - @VisibleForTesting - internal val unseenNotifPromoter = - object : NotifPromoter("$TAG-unseen") { - override fun shouldPromoteToTopLevel(child: NotificationEntry): Boolean = - if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) false - else if (!NotificationMinimalismPrototype.V2.ungroupTopUnseen) false - else - seenNotificationsInteractor.isTopOngoingNotification(child) || - seenNotificationsInteractor.isTopUnseenNotification(child) - } - - val topOngoingSectioner = - object : NotifSectioner("TopOngoing", BUCKET_TOP_ONGOING) { - override fun isInSection(entry: ListEntry): Boolean { - if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return false - return entry.anyEntry { notificationEntry -> - seenNotificationsInteractor.isTopOngoingNotification(notificationEntry) - } - } - } - - val topUnseenSectioner = - object : NotifSectioner("TopUnseen", BUCKET_TOP_UNSEEN) { - override fun isInSection(entry: ListEntry): Boolean { - if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return false - return entry.anyEntry { notificationEntry -> - seenNotificationsInteractor.isTopUnseenNotification(notificationEntry) - } - } - } - - private fun ListEntry.anyEntry(predicate: (NotificationEntry?) -> Boolean) = - when { - predicate(representativeEntry) -> true - this !is GroupEntry -> false - else -> children.any(predicate) - } - - @VisibleForTesting - internal val unseenNotifFilter = - object : NotifFilter("$TAG-unseen") { - - var hasFilteredAnyNotifs = false - - /** - * Encapsulates a definition of "being on the keyguard". Note that these two definitions - * are wildly different: [StatusBarState.KEYGUARD] is when on the lock screen and does - * not include shade or occluded states, whereas [KeyguardRepository.isKeyguardShowing] - * is any state where the keyguard has not been dismissed, including locked shade and - * occluded lock screen. - * - * Returning false for locked shade and occluded states means that this filter will - * allow seen notifications to appear in the locked shade. - */ - private fun isOnKeyguard(): Boolean = - if (NotificationMinimalismPrototype.V2.isEnabled) { - false // disable this feature under this prototype - } else if ( - NotificationMinimalismPrototype.V1.isEnabled && - NotificationMinimalismPrototype.V1.showOnLockedShade - ) { - statusBarStateController.state == StatusBarState.KEYGUARD - } else { - keyguardRepository.isKeyguardShowing() - } - - override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean = - when { - // Don't apply filter if the setting is disabled - !unseenFilterEnabled -> false - // Don't apply filter if the keyguard isn't currently showing - !isOnKeyguard() -> false - // Don't apply the filter if the notification is unseen - unseenNotifications.contains(entry) -> false - // Don't apply the filter to (non-promoted) group summaries - // - summary will be pruned if necessary, depending on if children are filtered - entry.parent?.summary == entry -> false - // Check that the entry satisfies certain characteristics that would bypass the - // filter - shouldIgnoreUnseenCheck(entry) -> false - else -> true - }.also { hasFiltered -> hasFilteredAnyNotifs = hasFilteredAnyNotifs || hasFiltered } - - override fun onCleanup() { - logger.logProviderHasFilteredOutSeenNotifs(hasFilteredAnyNotifs) - seenNotificationsInteractor.setHasFilteredOutSeenNotifications(hasFilteredAnyNotifs) - hasFilteredAnyNotifs = false - } - } - private val notifFilter: NotifFilter = object : NotifFilter(TAG) { override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean = keyguardNotificationVisibilityProvider.shouldHideNotification(entry) } - private fun shouldIgnoreUnseenCheck(entry: NotificationEntry): Boolean = - when { - entry.isMediaNotification -> true - entry.sbn.isOngoing -> true - else -> false - } - // TODO(b/206118999): merge this class with SensitiveContentCoordinator which also depends on // these same updates private fun setupInvalidateNotifListCallbacks() {} @@ -502,22 +70,7 @@ constructor( sectionHeaderVisibilityProvider.sectionHeadersVisible = showSections } - override fun dump(pw: PrintWriter, args: Array<out String>) = - with(pw.asIndenting()) { - println( - "notificationListInteractor.hasFilteredOutSeenNotifications.value=" + - seenNotificationsInteractor.hasFilteredOutSeenNotifications.value - ) - println("unseen notifications:") - indentIfPossible { - for (notification in unseenNotifications) { - println(notification.key) - } - } - } - companion object { private const val TAG = "KeyguardCoordinator" - private val SEEN_TIMEOUT = 5.seconds } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt index e0389820aedf..99327d1fe116 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt @@ -17,7 +17,11 @@ package com.android.systemui.statusbar.notification.collection.coordinator import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED -import com.android.systemui.statusbar.notification.collection.* +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationClassificationFlag +import com.android.systemui.statusbar.notification.collection.PipelineDumpable +import com.android.systemui.statusbar.notification.collection.PipelineDumper +import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider @@ -42,6 +46,7 @@ constructor( hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator, hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator, keyguardCoordinator: KeyguardCoordinator, + unseenKeyguardCoordinator: OriginalUnseenKeyguardCoordinator, rankingCoordinator: RankingCoordinator, colorizedFgsCoordinator: ColorizedFgsCoordinator, deviceProvisionedCoordinator: DeviceProvisionedCoordinator, @@ -82,6 +87,7 @@ constructor( mCoordinators.add(hideLocallyDismissedNotifsCoordinator) mCoordinators.add(hideNotifsForOtherUsersCoordinator) mCoordinators.add(keyguardCoordinator) + mCoordinators.add(unseenKeyguardCoordinator) mCoordinators.add(rankingCoordinator) mCoordinators.add(colorizedFgsCoordinator) mCoordinators.add(deviceProvisionedCoordinator) @@ -115,11 +121,11 @@ constructor( // Manually add Ordered Sections if (NotificationMinimalismPrototype.V2.isEnabled) { - mOrderedSections.add(keyguardCoordinator.topOngoingSectioner) // Top Ongoing + mOrderedSections.add(unseenKeyguardCoordinator.topOngoingSectioner) // Top Ongoing } mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp if (NotificationMinimalismPrototype.V2.isEnabled) { - mOrderedSections.add(keyguardCoordinator.topUnseenSectioner) // Top Unseen + mOrderedSections.add(unseenKeyguardCoordinator.topUnseenSectioner) // Top Unseen } mOrderedSections.add(colorizedFgsCoordinator.sectioner) // ForegroundService if (PriorityPeopleSection.isEnabled) { @@ -131,10 +137,10 @@ constructor( } mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting if (NotificationClassificationFlag.isEnabled) { - mOrderedSections.add(bundleCoordinator.newsSectioner); - mOrderedSections.add(bundleCoordinator.socialSectioner); - mOrderedSections.add(bundleCoordinator.recsSectioner); - mOrderedSections.add(bundleCoordinator.promoSectioner); + mOrderedSections.add(bundleCoordinator.newsSectioner) + mOrderedSections.add(bundleCoordinator.socialSectioner) + mOrderedSections.add(bundleCoordinator.recsSectioner) + mOrderedSections.add(bundleCoordinator.promoSectioner) } mOrderedSections.add(rankingCoordinator.silentSectioner) // Silent mOrderedSections.add(rankingCoordinator.minimizedSectioner) // Minimized diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt new file mode 100644 index 000000000000..5dd1663f712f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt @@ -0,0 +1,490 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.coordinator + +import android.annotation.SuppressLint +import android.app.NotificationManager +import android.os.UserHandle +import android.provider.Settings +import androidx.annotation.VisibleForTesting +import com.android.systemui.Dumpable +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dump.DumpManager +import com.android.systemui.keyguard.data.repository.KeyguardRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.expansionChanges +import com.android.systemui.statusbar.notification.collection.GroupEntry +import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener +import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor +import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype +import com.android.systemui.statusbar.notification.stack.BUCKET_TOP_ONGOING +import com.android.systemui.statusbar.notification.stack.BUCKET_TOP_UNSEEN +import com.android.systemui.statusbar.policy.HeadsUpManager +import com.android.systemui.statusbar.policy.headsUpEvents +import com.android.systemui.util.asIndenting +import com.android.systemui.util.indentIfPossible +import com.android.systemui.util.settings.SecureSettings +import com.android.systemui.util.settings.SettingsProxyExt.observerFlow +import java.io.PrintWriter +import javax.inject.Inject +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.launch +import kotlinx.coroutines.yield + +/** + * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section + * headers on the lockscreen. If enabled, it will also track and hide seen notifications on the + * lockscreen. + */ +@CoordinatorScope +@SuppressLint("SharedFlowCreation") +class OriginalUnseenKeyguardCoordinator +@Inject +constructor( + @Background private val bgDispatcher: CoroutineDispatcher, + private val dumpManager: DumpManager, + private val headsUpManager: HeadsUpManager, + private val keyguardRepository: KeyguardRepository, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, + private val logger: KeyguardCoordinatorLogger, + @Application private val scope: CoroutineScope, + private val secureSettings: SecureSettings, + private val seenNotificationsInteractor: SeenNotificationsInteractor, + private val statusBarStateController: StatusBarStateController, +) : Coordinator, Dumpable { + + private val unseenNotifications = mutableSetOf<NotificationEntry>() + private val unseenEntryAdded = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1) + private val unseenEntryRemoved = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1) + private var unseenFilterEnabled = false + + override fun attach(pipeline: NotifPipeline) { + if (NotificationMinimalismPrototype.V2.isEnabled) { + pipeline.addPromoter(unseenNotifPromoter) + pipeline.addOnBeforeTransformGroupsListener(::pickOutTopUnseenNotifs) + } + pipeline.addFinalizeFilter(unseenNotifFilter) + pipeline.addCollectionListener(collectionListener) + scope.launch { trackUnseenFilterSettingChanges() } + dumpManager.registerDumpable(this) + } + + private suspend fun trackSeenNotifications() { + // Whether or not keyguard is visible (or occluded). + val isKeyguardPresentFlow: Flow<Boolean> = + keyguardTransitionInteractor + .transitionValue( + scene = Scenes.Gone, + stateWithoutSceneContainer = KeyguardState.GONE, + ) + .map { it == 0f } + .distinctUntilChanged() + .onEach { trackingUnseen -> logger.logTrackingUnseen(trackingUnseen) } + + // Separately track seen notifications while the device is locked, applying once the device + // is unlocked. + val notificationsSeenWhileLocked = mutableSetOf<NotificationEntry>() + + // Use [collectLatest] to cancel any running jobs when [trackingUnseen] changes. + isKeyguardPresentFlow.collectLatest { isKeyguardPresent: Boolean -> + if (isKeyguardPresent) { + // Keyguard is not gone, notifications need to be visible for a certain threshold + // before being marked as seen + trackSeenNotificationsWhileLocked(notificationsSeenWhileLocked) + } else { + // Mark all seen-while-locked notifications as seen for real. + if (notificationsSeenWhileLocked.isNotEmpty()) { + unseenNotifications.removeAll(notificationsSeenWhileLocked) + logger.logAllMarkedSeenOnUnlock( + seenCount = notificationsSeenWhileLocked.size, + remainingUnseenCount = unseenNotifications.size + ) + notificationsSeenWhileLocked.clear() + } + unseenNotifFilter.invalidateList("keyguard no longer showing") + // Keyguard is gone, notifications can be immediately marked as seen when they + // become visible. + trackSeenNotificationsWhileUnlocked() + } + } + } + + /** + * Keep [notificationsSeenWhileLocked] updated to represent which notifications have actually + * been "seen" while the device is on the keyguard. + */ + private suspend fun trackSeenNotificationsWhileLocked( + notificationsSeenWhileLocked: MutableSet<NotificationEntry>, + ) = coroutineScope { + // Remove removed notifications from the set + launch { + unseenEntryRemoved.collect { entry -> + if (notificationsSeenWhileLocked.remove(entry)) { + logger.logRemoveSeenOnLockscreen(entry) + } + } + } + // Use collectLatest so that the timeout delay is cancelled if the device enters doze, and + // is restarted when doze ends. + keyguardRepository.isDozing.collectLatest { isDozing -> + if (!isDozing) { + trackSeenNotificationsWhileLockedAndNotDozing(notificationsSeenWhileLocked) + } + } + } + + /** + * Keep [notificationsSeenWhileLocked] updated to represent which notifications have actually + * been "seen" while the device is on the keyguard and not dozing. Any new and existing unseen + * notifications are not marked as seen until they are visible for the [SEEN_TIMEOUT] duration. + */ + private suspend fun trackSeenNotificationsWhileLockedAndNotDozing( + notificationsSeenWhileLocked: MutableSet<NotificationEntry> + ) = coroutineScope { + // All child tracking jobs will be cancelled automatically when this is cancelled. + val trackingJobsByEntry = mutableMapOf<NotificationEntry, Job>() + + /** + * Wait for the user to spend enough time on the lock screen before removing notification + * from unseen set upon unlock. + */ + suspend fun trackSeenDurationThreshold(entry: NotificationEntry) { + if (notificationsSeenWhileLocked.remove(entry)) { + logger.logResetSeenOnLockscreen(entry) + } + delay(SEEN_TIMEOUT) + notificationsSeenWhileLocked.add(entry) + trackingJobsByEntry.remove(entry) + logger.logSeenOnLockscreen(entry) + } + + /** Stop any unseen tracking when a notification is removed. */ + suspend fun stopTrackingRemovedNotifs(): Nothing = + unseenEntryRemoved.collect { entry -> + trackingJobsByEntry.remove(entry)?.let { + it.cancel() + logger.logStopTrackingLockscreenSeenDuration(entry) + } + } + + /** Start tracking new notifications when they are posted. */ + suspend fun trackNewUnseenNotifs(): Nothing = coroutineScope { + unseenEntryAdded.collect { entry -> + logger.logTrackingLockscreenSeenDuration(entry) + // If this is an update, reset the tracking. + trackingJobsByEntry[entry]?.let { + it.cancel() + logger.logResetSeenOnLockscreen(entry) + } + trackingJobsByEntry[entry] = launch { trackSeenDurationThreshold(entry) } + } + } + + // Start tracking for all notifications that are currently unseen. + logger.logTrackingLockscreenSeenDuration(unseenNotifications) + unseenNotifications.forEach { entry -> + trackingJobsByEntry[entry] = launch { trackSeenDurationThreshold(entry) } + } + + launch { trackNewUnseenNotifs() } + launch { stopTrackingRemovedNotifs() } + } + + // Track "seen" notifications, marking them as such when either shade is expanded or the + // notification becomes heads up. + private suspend fun trackSeenNotificationsWhileUnlocked() { + coroutineScope { + launch { clearUnseenNotificationsWhenShadeIsExpanded() } + launch { markHeadsUpNotificationsAsSeen() } + } + } + + private suspend fun clearUnseenNotificationsWhenShadeIsExpanded() { + statusBarStateController.expansionChanges.collectLatest { isExpanded -> + // Give keyguard events time to propagate, in case this expansion is part of the + // keyguard transition and not the user expanding the shade + yield() + if (isExpanded) { + logger.logShadeExpanded() + unseenNotifications.clear() + } + } + } + + private suspend fun markHeadsUpNotificationsAsSeen() { + headsUpManager.allEntries + .filter { it.isRowPinned } + .forEach { unseenNotifications.remove(it) } + headsUpManager.headsUpEvents.collect { (entry, isHun) -> + if (isHun) { + logger.logUnseenHun(entry.key) + unseenNotifications.remove(entry) + } + } + } + + private fun unseenFeatureEnabled(): Flow<Boolean> { + if ( + NotificationMinimalismPrototype.V1.isEnabled || + NotificationMinimalismPrototype.V2.isEnabled + ) { + return flowOf(true) + } + return secureSettings + // emit whenever the setting has changed + .observerFlow( + UserHandle.USER_ALL, + Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, + ) + // perform a query immediately + .onStart { emit(Unit) } + // for each change, lookup the new value + .map { + secureSettings.getIntForUser( + name = Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, + def = 0, + userHandle = UserHandle.USER_CURRENT, + ) == 1 + } + // don't emit anything if nothing has changed + .distinctUntilChanged() + // perform lookups on the bg thread pool + .flowOn(bgDispatcher) + // only track the most recent emission, if events are happening faster than they can be + // consumed + .conflate() + } + + private suspend fun trackUnseenFilterSettingChanges() { + unseenFeatureEnabled().collectLatest { setting -> + // update local field and invalidate if necessary + if (setting != unseenFilterEnabled) { + unseenFilterEnabled = setting + unseenNotifFilter.invalidateList("unseen setting changed") + } + // if the setting is enabled, then start tracking and filtering unseen notifications + if (setting) { + trackSeenNotifications() + } + } + } + + private val collectionListener = + object : NotifCollectionListener { + override fun onEntryAdded(entry: NotificationEntry) { + if ( + keyguardRepository.isKeyguardShowing() || !statusBarStateController.isExpanded + ) { + logger.logUnseenAdded(entry.key) + unseenNotifications.add(entry) + unseenEntryAdded.tryEmit(entry) + } + } + + override fun onEntryUpdated(entry: NotificationEntry) { + if ( + keyguardRepository.isKeyguardShowing() || !statusBarStateController.isExpanded + ) { + logger.logUnseenUpdated(entry.key) + unseenNotifications.add(entry) + unseenEntryAdded.tryEmit(entry) + } + } + + override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { + if (unseenNotifications.remove(entry)) { + logger.logUnseenRemoved(entry.key) + unseenEntryRemoved.tryEmit(entry) + } + } + } + + private fun pickOutTopUnseenNotifs(list: List<ListEntry>) { + if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return + // Only ever elevate a top unseen notification on keyguard, not even locked shade + if (statusBarStateController.state != StatusBarState.KEYGUARD) { + seenNotificationsInteractor.setTopOngoingNotification(null) + seenNotificationsInteractor.setTopUnseenNotification(null) + return + } + // On keyguard pick the top-ranked unseen or ongoing notification to elevate + val nonSummaryEntries: Sequence<NotificationEntry> = + list + .asSequence() + .flatMap { + when (it) { + is NotificationEntry -> listOfNotNull(it) + is GroupEntry -> it.children + else -> error("unhandled type of $it") + } + } + .filter { it.importance >= NotificationManager.IMPORTANCE_DEFAULT } + seenNotificationsInteractor.setTopOngoingNotification( + nonSummaryEntries + .filter { ColorizedFgsCoordinator.isRichOngoing(it) } + .minByOrNull { it.ranking.rank } + ) + seenNotificationsInteractor.setTopUnseenNotification( + nonSummaryEntries + .filter { !ColorizedFgsCoordinator.isRichOngoing(it) && it in unseenNotifications } + .minByOrNull { it.ranking.rank } + ) + } + + @VisibleForTesting + val unseenNotifPromoter = + object : NotifPromoter("$TAG-unseen") { + override fun shouldPromoteToTopLevel(child: NotificationEntry): Boolean = + if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) false + else if (!NotificationMinimalismPrototype.V2.ungroupTopUnseen) false + else + seenNotificationsInteractor.isTopOngoingNotification(child) || + seenNotificationsInteractor.isTopUnseenNotification(child) + } + + val topOngoingSectioner = + object : NotifSectioner("TopOngoing", BUCKET_TOP_ONGOING) { + override fun isInSection(entry: ListEntry): Boolean { + if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return false + return entry.anyEntry { notificationEntry -> + seenNotificationsInteractor.isTopOngoingNotification(notificationEntry) + } + } + } + + val topUnseenSectioner = + object : NotifSectioner("TopUnseen", BUCKET_TOP_UNSEEN) { + override fun isInSection(entry: ListEntry): Boolean { + if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return false + return entry.anyEntry { notificationEntry -> + seenNotificationsInteractor.isTopUnseenNotification(notificationEntry) + } + } + } + + private fun ListEntry.anyEntry(predicate: (NotificationEntry?) -> Boolean) = + when { + predicate(representativeEntry) -> true + this !is GroupEntry -> false + else -> children.any(predicate) + } + + @VisibleForTesting + val unseenNotifFilter = + object : NotifFilter("$TAG-unseen") { + + var hasFilteredAnyNotifs = false + + /** + * Encapsulates a definition of "being on the keyguard". Note that these two definitions + * are wildly different: [StatusBarState.KEYGUARD] is when on the lock screen and does + * not include shade or occluded states, whereas [KeyguardRepository.isKeyguardShowing] + * is any state where the keyguard has not been dismissed, including locked shade and + * occluded lock screen. + * + * Returning false for locked shade and occluded states means that this filter will + * allow seen notifications to appear in the locked shade. + */ + private fun isOnKeyguard(): Boolean = + if (NotificationMinimalismPrototype.V2.isEnabled) { + false // disable this feature under this prototype + } else if ( + NotificationMinimalismPrototype.V1.isEnabled && + NotificationMinimalismPrototype.V1.showOnLockedShade + ) { + statusBarStateController.state == StatusBarState.KEYGUARD + } else { + keyguardRepository.isKeyguardShowing() + } + + override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean = + when { + // Don't apply filter if the setting is disabled + !unseenFilterEnabled -> false + // Don't apply filter if the keyguard isn't currently showing + !isOnKeyguard() -> false + // Don't apply the filter if the notification is unseen + unseenNotifications.contains(entry) -> false + // Don't apply the filter to (non-promoted) group summaries + // - summary will be pruned if necessary, depending on if children are filtered + entry.parent?.summary == entry -> false + // Check that the entry satisfies certain characteristics that would bypass the + // filter + shouldIgnoreUnseenCheck(entry) -> false + else -> true + }.also { hasFiltered -> hasFilteredAnyNotifs = hasFilteredAnyNotifs || hasFiltered } + + override fun onCleanup() { + logger.logProviderHasFilteredOutSeenNotifs(hasFilteredAnyNotifs) + seenNotificationsInteractor.setHasFilteredOutSeenNotifications(hasFilteredAnyNotifs) + hasFilteredAnyNotifs = false + } + } + + private fun shouldIgnoreUnseenCheck(entry: NotificationEntry): Boolean = + when { + entry.isMediaNotification -> true + entry.sbn.isOngoing -> true + else -> false + } + + override fun dump(pw: PrintWriter, args: Array<out String>) = + with(pw.asIndenting()) { + println( + "notificationListInteractor.hasFilteredOutSeenNotifications.value=" + + seenNotificationsInteractor.hasFilteredOutSeenNotifications.value + ) + println("unseen notifications:") + indentIfPossible { + for (notification in unseenNotifications) { + println(notification.key) + } + } + } + + companion object { + private const val TAG = "OriginalUnseenKeyguardCoordinator" + private val SEEN_TIMEOUT = 5.seconds + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index fbddc060bcee..7c3072df0875 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -66,6 +66,8 @@ public class AmbientState implements Dumpable { private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private float mStackTop; private float mStackCutoff; + private float mHeadsUpTop; + private float mHeadsUpBottom; private int mScrollY; private float mOverScrollTopAmount; private float mOverScrollBottomAmount; @@ -377,6 +379,30 @@ public class AmbientState implements Dumpable { this.mStackCutoff = stackCutoff; } + /** y coordinate of the top position of a pinned HUN */ + public float getHeadsUpTop() { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0f; + return mHeadsUpTop; + } + + /** @see #getHeadsUpTop() */ + public void setHeadsUpTop(float mHeadsUpTop) { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return; + this.mHeadsUpTop = mHeadsUpTop; + } + + /** the bottom-most y position where we can draw pinned HUNs */ + public float getHeadsUpBottom() { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0f; + return mHeadsUpBottom; + } + + /** @see #getHeadsUpBottom() */ + public void setHeadsUpBottom(float headsUpBottom) { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return; + mHeadsUpBottom = headsUpBottom; + } + public int getScrollY() { return mScrollY; } @@ -784,7 +810,9 @@ public class AmbientState implements Dumpable { @Override public void dump(PrintWriter pw, String[] args) { pw.println("mStackTop=" + mStackTop); - pw.println("mStackCutoff" + mStackCutoff); + pw.println("mStackCutoff=" + mStackCutoff); + pw.println("mHeadsUpTop=" + mHeadsUpTop); + pw.println("mHeadsUpBottom=" + mHeadsUpBottom); pw.println("mTopPadding=" + mTopPadding); pw.println("mStackTopMargin=" + mStackTopMargin); pw.println("mStackTranslation=" + mStackTranslation); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 2081adc076d1..cec1ef3f1e7b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -834,7 +834,7 @@ public class NotificationStackScrollLayout y = (int) mAmbientState.getStackCutoff(); drawDebugInfo(canvas, y, Color.MAGENTA, /* label= */ "getStackCutoff() = " + y); - y = (int) mScrollViewFields.getHeadsUpTop(); + y = (int) mAmbientState.getHeadsUpTop(); drawDebugInfo(canvas, y, Color.GREEN, /* label= */ "getHeadsUpTop() = " + y); y += getTopHeadsUpHeight(); @@ -1222,7 +1222,12 @@ public class NotificationStackScrollLayout @Override public void setHeadsUpTop(float headsUpTop) { - mScrollViewFields.setHeadsUpTop(headsUpTop); + mAmbientState.setHeadsUpTop(headsUpTop); + } + + @Override + public void setHeadsUpBottom(float headsUpBottom) { + mAmbientState.setHeadsUpBottom(headsUpBottom); } @Override @@ -4894,6 +4899,7 @@ public class NotificationStackScrollLayout * @param bottomBarHeight the height of the bar on the bottom */ public void setHeadsUpBoundaries(int height, int bottomBarHeight) { + SceneContainerFlag.assertInLegacyMode(); mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight); mStackScrollAlgorithm.setHeadsUpAppearHeightBottom(height); mStateAnimator.setHeadsUpAppearHeightBottom(height); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt index 97ec39192cc8..383d8b3b26b5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt @@ -32,8 +32,6 @@ import java.util.function.Consumer class ScrollViewFields { /** Used to produce the clipping path */ var scrimClippingShape: ShadeScrimShape? = null - /** Y coordinate in view pixels of the top of the HUN */ - var headsUpTop: Float = 0f /** Whether the notifications are scrolled all the way to the top (i.e. when freshly opened) */ var isScrolledToTop: Boolean = true @@ -74,7 +72,6 @@ class ScrollViewFields { fun dump(pw: IndentingPrintWriter) { pw.printSection("StackViewStates") { pw.println("scrimClippingShape", scrimClippingShape) - pw.println("headsUpTop", headsUpTop) pw.println("isScrolledToTop", isScrolledToTop) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index 4282fa25a4eb..b801e5d89b84 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -79,9 +79,7 @@ public class StackScrollAlgorithm { private int mHeadsUpAppearHeightBottom; private int mHeadsUpCyclingPadding; - public StackScrollAlgorithm( - Context context, - ViewGroup hostView) { + public StackScrollAlgorithm(Context context, ViewGroup hostView) { mHostView = hostView; initView(context); } @@ -865,7 +863,10 @@ public class StackScrollAlgorithm { // Move the tracked heads up into position during the appear animation, by interpolating // between the HUN inset (where it will appear as a HUN) and the end position in the shade - float headsUpTranslation = mHeadsUpInset - ambientState.getStackTopMargin(); + float headsUpTranslation = + SceneContainerFlag.isEnabled() + ? ambientState.getHeadsUpTop() + : mHeadsUpInset - ambientState.getStackTopMargin(); ExpandableNotificationRow trackedHeadsUpRow = ambientState.getTrackedHeadsUpRow(); if (trackedHeadsUpRow != null) { ExpandableViewState childState = trackedHeadsUpRow.getViewState(); @@ -894,21 +895,44 @@ public class StackScrollAlgorithm { boolean isTopEntry = topHeadsUpEntry == row; float unmodifiedEndLocation = childState.getYTranslation() + childState.height; if (mIsExpanded) { - if (shouldHunBeVisibleWhenScrolled(row.mustStayOnScreen(), - childState.headsUpIsVisible, row.showingPulsing(), - ambientState.isOnKeyguard(), row.getEntry().isStickyAndNotDemoted())) { - // Ensure that the heads up is always visible even when scrolled off. - // NSSL y starts at top of screen in non-split-shade, but below the qs offset - // in split shade, so we only need to inset by the scrim padding in split shade. - // TODO(b/332574413) get the clamp inset from HeadsUpNotificationPlaceholder - final float clampInset = ambientState.getUseSplitShade() - ? mNotificationScrimPadding : mQuickQsOffsetHeight; - clampHunToTop(clampInset, ambientState.getStackTranslation(), - row.getCollapsedHeight(), childState); - if (isTopEntry && row.isAboveShelf()) { - // the first hun can't get off screen. - clampHunToMaxTranslation(ambientState, row, childState); - childState.hidden = false; + if (SceneContainerFlag.isEnabled()) { + if (shouldHunBeVisibleWhenScrolled(row.mustStayOnScreen(), + childState.headsUpIsVisible, row.showingPulsing(), + ambientState.isOnKeyguard(), row.getEntry().isStickyAndNotDemoted())) { + clampHunToTop( + /* headsUpTop = */ headsUpTranslation, + /* collapsedHeight = */ row.getCollapsedHeight(), + /* viewState = */ childState + ); + if (isTopEntry && row.isAboveShelf()) { + clampHunToMaxTranslation( + /* headsUpTop = */ headsUpTranslation, + /* headsUpBottom = */ ambientState.getHeadsUpBottom(), + /* viewState = */ childState + ); + updateCornerRoundnessForPinnedHun(row, ambientState.getStackTop()); + childState.hidden = false; + } + } + } else { + if (shouldHunBeVisibleWhenScrolled(row.mustStayOnScreen(), + childState.headsUpIsVisible, row.showingPulsing(), + ambientState.isOnKeyguard(), row.getEntry().isStickyAndNotDemoted())) { + // Ensure that the heads up is always visible even when scrolled off. + // NSSL y starts at top of screen in non-split-shade, but below the qs + // offset + // in split shade, so we only need to inset by the scrim padding in split + // shade. + final float clampInset = ambientState.getUseSplitShade() + ? mNotificationScrimPadding : mQuickQsOffsetHeight; + clampHunToTop(clampInset, ambientState.getStackTranslation(), + row.getCollapsedHeight(), childState); + if (isTopEntry && row.isAboveShelf()) { + // the first hun can't get off screen. + clampHunToMaxTranslation(ambientState, row, childState); + updateCornerRoundnessForPinnedHun(row, ambientState.getStackY()); + childState.hidden = false; + } } } } @@ -1005,9 +1029,13 @@ public class StackScrollAlgorithm { @VisibleForTesting void clampHunToTop(float clampInset, float stackTranslation, float collapsedHeight, ExpandableViewState viewState) { + SceneContainerFlag.assertInLegacyMode(); + clampHunToTop(clampInset + stackTranslation, collapsedHeight, viewState); + } - final float newTranslation = Math.max(clampInset + stackTranslation, - viewState.getYTranslation()); + @VisibleForTesting + void clampHunToTop(float headsUpTop, float collapsedHeight, ExpandableViewState viewState) { + final float newTranslation = Math.max(headsUpTop, viewState.getYTranslation()); // Transition from collapsed pinned state to fully expanded state // when the pinned HUN approaches its actual location (when scrolling back to top). @@ -1020,9 +1048,12 @@ public class StackScrollAlgorithm { // while the rest of notifications are scrolled offscreen. private void clampHunToMaxTranslation(AmbientState ambientState, ExpandableNotificationRow row, ExpandableViewState childState) { + SceneContainerFlag.assertInLegacyMode(); float maxHeadsUpTranslation = ambientState.getMaxHeadsUpTranslation(); - final float maxShelfPosition = ambientState.getInnerHeight() + ambientState.getTopPadding() - + ambientState.getStackTranslation(); + final float maxShelfPosition = + ambientState.getInnerHeight() + + ambientState.getTopPadding() + + ambientState.getStackTranslation(); maxHeadsUpTranslation = Math.min(maxHeadsUpTranslation, maxShelfPosition); final float bottomPosition = maxHeadsUpTranslation - row.getCollapsedHeight(); @@ -1030,13 +1061,20 @@ public class StackScrollAlgorithm { childState.height = (int) Math.min(childState.height, maxHeadsUpTranslation - newTranslation); childState.setYTranslation(newTranslation); + } + + private void clampHunToMaxTranslation(float headsUpTop, float headsUpBottom, + ExpandableViewState viewState) { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return; + final float maxHeight = headsUpTop - headsUpBottom; + viewState.setYTranslation(Math.min(headsUpTop, viewState.getYTranslation())); + viewState.height = (int) Math.min(maxHeight, viewState.height); + } + private void updateCornerRoundnessForPinnedHun(ExpandableNotificationRow row, float stackTop) { // Animate pinned HUN bottom corners to and from original roundness. final float originalCornerRadius = row.isLastInSection() ? 1f : (mSmallCornerRadius / mLargeCornerRadius); - final float stackTop = SceneContainerFlag.isEnabled() - ? ambientState.getStackTop() - : ambientState.getStackY(); final float bottomValue = computeCornerRoundnessForPinnedHun(mHostView.getHeight(), stackTop, getMaxAllowedChildHeight(row), originalCornerRadius); row.requestBottomRoundness(bottomValue, STACK_SCROLL_ALGO); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt index 762c507220fa..6226fe7952f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt @@ -59,6 +59,9 @@ interface NotificationScrollView { /** set the y position in px of the top of the HUN in this view's coordinates */ fun setHeadsUpTop(headsUpTop: Float) + /** set the bottom-most y position in px, where we can draw HUNs in this view's coordinates */ + fun setHeadsUpBottom(headsUpBottom: Float) + /** set whether the view has been scrolled all the way to the top */ fun setScrolledToTop(scrolledToTop: Boolean) 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 96127b633f70..8f2ad40e7130 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -55,6 +55,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.KeyguardViewController; import com.android.keyguard.TrustGrantFlags; import com.android.keyguard.ViewMediatorCallback; +import com.android.systemui.Flags; import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor; @@ -708,15 +709,30 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * {@link #needsFullscreenBouncer()}. */ protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing) { - if (needsFullscreenBouncer() && !mDozing) { + boolean isDozing = mDozing; + if (Flags.simPinRaceConditionOnRestart()) { + KeyguardState toState = mKeyguardTransitionInteractor.getTransitionState().getValue() + .getTo(); + isDozing = mDozing || toState == KeyguardState.DOZING || toState == KeyguardState.AOD; + } + if (needsFullscreenBouncer() && !isDozing) { // The keyguard might be showing (already). So we need to hide it. if (!primaryBouncerIsShowing()) { - mCentralSurfaces.hideKeyguard(); if (SceneContainerFlag.isEnabled()) { + mCentralSurfaces.hideKeyguard(); mSceneInteractorLazy.get().changeScene( Scenes.Bouncer, "StatusBarKeyguardViewManager.showBouncerOrKeyguard"); } else { - mPrimaryBouncerInteractor.show(/* isScrimmed= */ true); + if (Flags.simPinRaceConditionOnRestart()) { + if (mPrimaryBouncerInteractor.show(/* isScrimmed= */ true)) { + mCentralSurfaces.hideKeyguard(); + } else { + mCentralSurfaces.showKeyguard(); + } + } else { + mCentralSurfaces.hideKeyguard(); + mPrimaryBouncerInteractor.show(/* isScrimmed= */ true); + } } } else { Log.e(TAG, "Attempted to show the sim bouncer when it is already showing."); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index aced0be4cc46..0320a7ae103b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -528,9 +528,10 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } @Override - public void onOngoingActivityStatusChanged(boolean hasOngoingActivity) { + public void onOngoingActivityStatusChanged( + boolean hasOngoingActivity, boolean shouldAnimate) { mHasOngoingActivity = hasOngoingActivity; - updateStatusBarVisibilities(/* animate= */ true); + updateStatusBarVisibilities(shouldAnimate); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt index ae1898bc479c..4c97854bb5c9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt @@ -122,7 +122,8 @@ class CollapsedStatusBarViewBinderImpl @Inject constructor() : CollapsedStatusBa // Notify listeners listener.onOngoingActivityStatusChanged( - hasOngoingActivity = true + hasOngoingActivity = true, + shouldAnimate = true, ) } is OngoingActivityChipModel.Hidden -> { @@ -130,7 +131,8 @@ class CollapsedStatusBarViewBinderImpl @Inject constructor() : CollapsedStatusBa // b/192243808 and [Chronometer.start]. chipTimeView.stop() listener.onOngoingActivityStatusChanged( - hasOngoingActivity = false + hasOngoingActivity = false, + shouldAnimate = chipModel.shouldAnimate, ) } } @@ -266,8 +268,13 @@ interface StatusBarVisibilityChangeListener { /** Called when a transition from lockscreen to dream has started. */ fun onTransitionFromLockscreenToDreamStarted() - /** Called when the status of the ongoing activity chip (active or not active) has changed. */ - fun onOngoingActivityStatusChanged(hasOngoingActivity: Boolean) + /** + * Called when the status of the ongoing activity chip (active or not active) has changed. + * + * @param shouldAnimate true if the chip should animate in/out, and false if the chip should + * immediately appear/disappear. + */ + fun onOngoingActivityStatusChanged(hasOngoingActivity: Boolean, shouldAnimate: Boolean) /** * Called when the scene state has changed such that the home status bar is newly allowed or no 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 new file mode 100644 index 000000000000..6db1eacaa706 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt @@ -0,0 +1,81 @@ +/* + * 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.content.Intent +import android.provider.Settings +import androidx.compose.material3.Text +import androidx.compose.ui.res.stringResource +import com.android.compose.PlatformButton +import com.android.compose.PlatformOutlinedButton +import com.android.systemui.animation.DialogTransitionAnimator +import com.android.systemui.dialog.ui.composable.AlertDialogContent +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.res.R +import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.statusbar.phone.SystemUIDialogFactory +import com.android.systemui.statusbar.phone.create +import javax.inject.Inject + +class ModesDialogDelegate +@Inject +constructor( + private val sysuiDialogFactory: SystemUIDialogFactory, + private val dialogTransitionAnimator: DialogTransitionAnimator, + private val activityStarter: ActivityStarter, +) : SystemUIDialog.Delegate { + override fun createDialog(): SystemUIDialog { + return sysuiDialogFactory.create { dialog -> + AlertDialogContent( + title = { Text(stringResource(R.string.zen_modes_dialog_title)) }, + content = { Text("Under construction") }, + neutralButton = { + PlatformOutlinedButton( + onClick = { + val animationController = + dialogTransitionAnimator.createActivityTransitionController( + dialog.getButton(SystemUIDialog.BUTTON_NEUTRAL) + ) + if (animationController == null) { + // The controller will take care of dismissing for us after the + // animation, but let's make sure we dismiss the dialog if we don't + // animate it. + dialog.dismiss() + } + activityStarter.startActivity( + ZEN_MODE_SETTINGS_INTENT, + true /* dismissShade */, + animationController + ) + } + ) { + Text(stringResource(R.string.zen_modes_dialog_settings)) + } + }, + positiveButton = { + PlatformButton(onClick = { dialog.dismiss() }) { + Text(stringResource(R.string.zen_modes_dialog_done)) + } + }, + ) + } + } + + companion object { + private val ZEN_MODE_SETTINGS_INTENT = Intent(Settings.ACTION_ZEN_MODE_SETTINGS) + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index 79933ee3b018..a94ef36bda29 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -16,6 +16,7 @@ package com.android.keyguard import android.content.BroadcastReceiver +import android.platform.test.annotations.DisableFlags import android.view.View import android.view.ViewTreeObserver import android.widget.FrameLayout @@ -263,9 +264,9 @@ class ClockEventControllerTest : SysuiTestCase() { } @Test + @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun keyguardCallback_visibilityChanged_clockDozeCalled() = runBlocking(IMMEDIATE) { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) val captor = argumentCaptor<KeyguardUpdateMonitorCallback>() verify(keyguardUpdateMonitor).registerCallback(capture(captor)) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java index 1d7816848cd0..892375d002c1 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -29,6 +29,7 @@ import static org.mockito.Mockito.when; import android.database.ContentObserver; import android.os.UserHandle; +import android.platform.test.annotations.DisableFlags; import android.provider.Settings; import android.view.View; @@ -48,11 +49,10 @@ import org.mockito.verification.VerificationMode; @SmallTest @RunWith(AndroidJUnit4.class) +@DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchControllerBaseTest { @Test public void testInit_viewAlreadyAttached() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - mController.init(); verifyAttachment(times(1)); @@ -60,8 +60,6 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testInit_viewNotYetAttached() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - ArgumentCaptor<View.OnAttachStateChangeListener> listenerArgumentCaptor = ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class); @@ -78,16 +76,12 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testInitSubControllers() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - mController.init(); verify(mKeyguardSliceViewController).init(); } @Test public void testInit_viewDetached() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - ArgumentCaptor<View.OnAttachStateChangeListener> listenerArgumentCaptor = ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class); mController.init(); @@ -101,8 +95,6 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testPluginPassesStatusBarState() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - ArgumentCaptor<ClockRegistry.ClockChangeListener> listenerArgumentCaptor = ArgumentCaptor.forClass(ClockRegistry.ClockChangeListener.class); @@ -116,8 +108,6 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testSmartspaceEnabledRemovesKeyguardStatusArea() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - when(mSmartspaceController.isEnabled()).thenReturn(true); mController.init(); @@ -126,8 +116,6 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void onLocaleListChangedRebuildsSmartspaceView() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - when(mSmartspaceController.isEnabled()).thenReturn(true); mController.init(); @@ -138,8 +126,6 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void onLocaleListChanged_rebuildsSmartspaceViews_whenDecouplingEnabled() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - when(mSmartspaceController.isEnabled()).thenReturn(true); when(mSmartspaceController.isDateWeatherDecoupled()).thenReturn(true); mController.init(); @@ -153,8 +139,6 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testSmartspaceDisabledShowsKeyguardStatusArea() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - when(mSmartspaceController.isEnabled()).thenReturn(false); mController.init(); @@ -163,8 +147,6 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testRefresh() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - mController.refresh(); verify(mSmartspaceController).requestSmartspaceUpdate(); @@ -172,8 +154,6 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testChangeToDoubleLineClockSetsSmallClock() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - when(mSecureSettings.getIntForUser(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1, UserHandle.USER_CURRENT)) .thenReturn(0); @@ -197,15 +177,11 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testGetClock_ForwardsToClock() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - assertEquals(mClockController, mController.getClock()); } @Test public void testGetLargeClockBottom_returnsExpectedValue() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - when(mLargeClockFrame.getVisibility()).thenReturn(View.VISIBLE); when(mLargeClockFrame.getHeight()).thenReturn(100); when(mSmallClockFrame.getHeight()).thenReturn(50); @@ -218,8 +194,6 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testGetSmallLargeClockBottom_returnsExpectedValue() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - when(mLargeClockFrame.getVisibility()).thenReturn(View.GONE); when(mLargeClockFrame.getHeight()).thenReturn(100); when(mSmallClockFrame.getHeight()).thenReturn(50); @@ -232,16 +206,12 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testGetClockBottom_nullClock_returnsZero() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - when(mClockEventController.getClock()).thenReturn(null); assertEquals(0, mController.getClockBottom(10)); } @Test public void testChangeLockscreenWeatherEnabledSetsWeatherViewVisible() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - when(mSmartspaceController.isWeatherEnabled()).thenReturn(true); ArgumentCaptor<ContentObserver> observerCaptor = ArgumentCaptor.forClass(ContentObserver.class); @@ -260,8 +230,6 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testChangeClockDateWeatherEnabled_SetsDateWeatherViewVisibility() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - ArgumentCaptor<ClockRegistry.ClockChangeListener> listenerArgumentCaptor = ArgumentCaptor.forClass(ClockRegistry.ClockChangeListener.class); when(mSmartspaceController.isEnabled()).thenReturn(true); @@ -284,15 +252,11 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testGetClock_nullClock_returnsNull() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - when(mClockEventController.getClock()).thenReturn(null); assertNull(mController.getClock()); } private void verifyAttachment(VerificationMode times) { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - verify(mClockRegistry, times).registerClockChangeListener( any(ClockRegistry.ClockChangeListener.class)); verify(mClockEventController, times).registerListeners(mView); @@ -300,8 +264,6 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testSplitShadeEnabledSetToSmartspaceController() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - mController.setSplitShadeEnabled(true); verify(mSmartspaceController, times(1)).setSplitShadeEnabled(true); verify(mSmartspaceController, times(0)).setSplitShadeEnabled(false); @@ -309,8 +271,6 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testSplitShadeDisabledSetToSmartspaceController() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - mController.setSplitShadeEnabled(false); verify(mSmartspaceController, times(1)).setSplitShadeEnabled(false); verify(mSmartspaceController, times(0)).setSplitShadeEnabled(true); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java index 83443bee744e..0bf9d12a09d5 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java @@ -30,6 +30,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.content.Context; +import android.platform.test.annotations.DisableFlags; import android.testing.TestableLooper.RunWithLooper; import android.util.AttributeSet; import android.view.LayoutInflater; @@ -60,6 +61,7 @@ import org.mockito.MockitoAnnotations; // the main thread before acquiring a wake lock. This class is constructed when // the keyguard_clock_switch layout is inflated. @RunWithLooper(setAsMainLooper = true) +@DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public class KeyguardClockSwitchTest extends SysuiTestCase { @Mock ViewGroup mMockKeyguardSliceView; @@ -81,8 +83,6 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { @Before public void setUp() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - MockitoAnnotations.initMocks(this); when(mMockKeyguardSliceView.getContext()).thenReturn(mContext); when(mMockKeyguardSliceView.findViewById(R.id.keyguard_status_area)) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java index b09357f853b9..c51aa04fc7b6 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java @@ -151,6 +151,7 @@ public class LegacyLockIconViewControllerBaseTest extends SysuiTestCase { if (!SceneContainerFlag.isEnabled()) { mSetFlagsRule.disableFlags(Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR); + //TODO move this to use @DisableFlags annotation if needed mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt index 6dc4b10a57da..bbff5392f59c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt @@ -18,6 +18,10 @@ package com.android.systemui.biometrics import android.graphics.Point import android.hardware.biometrics.BiometricSourceType +import android.hardware.biometrics.ComponentInfoInternal +import android.hardware.biometrics.SensorLocationInternal +import android.hardware.biometrics.SensorProperties +import android.hardware.fingerprint.FingerprintSensorProperties import android.hardware.fingerprint.FingerprintSensorPropertiesInternal import android.testing.TestableLooper.RunWithLooper import android.util.DisplayMetrics @@ -43,6 +47,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.leak.RotationUtils import com.android.systemui.util.mockito.any +import javax.inject.Provider import kotlinx.coroutines.ExperimentalCoroutinesApi import org.junit.After import org.junit.Assert.assertFalse @@ -62,8 +67,6 @@ import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations import org.mockito.MockitoSession import org.mockito.quality.Strictness -import javax.inject.Provider - @ExperimentalCoroutinesApi @SmallTest @@ -79,35 +82,28 @@ class AuthRippleControllerTest : SysuiTestCase() { @Mock private lateinit var authController: AuthController @Mock private lateinit var authRippleInteractor: AuthRippleInteractor @Mock private lateinit var keyguardStateController: KeyguardStateController - @Mock - private lateinit var wakefulnessLifecycle: WakefulnessLifecycle - @Mock - private lateinit var notificationShadeWindowController: NotificationShadeWindowController - @Mock - private lateinit var biometricUnlockController: BiometricUnlockController - @Mock - private lateinit var udfpsControllerProvider: Provider<UdfpsController> - @Mock - private lateinit var udfpsController: UdfpsController - @Mock - private lateinit var statusBarStateController: StatusBarStateController - @Mock - private lateinit var lightRevealScrim: LightRevealScrim - @Mock - private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal + @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle + @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController + @Mock private lateinit var biometricUnlockController: BiometricUnlockController + @Mock private lateinit var udfpsControllerProvider: Provider<UdfpsController> + @Mock private lateinit var udfpsController: UdfpsController + @Mock private lateinit var statusBarStateController: StatusBarStateController + @Mock private lateinit var lightRevealScrim: LightRevealScrim + @Mock private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal private val facePropertyRepository = FakeFacePropertyRepository() private val displayMetrics = DisplayMetrics() @Captor private lateinit var biometricUnlockListener: - ArgumentCaptor<BiometricUnlockController.BiometricUnlockEventsListener> + ArgumentCaptor<BiometricUnlockController.BiometricUnlockEventsListener> @Before fun setUp() { mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) MockitoAnnotations.initMocks(this) - staticMockSession = mockitoSession() + staticMockSession = + mockitoSession() .mockStatic(RotationUtils::class.java) .strictness(Strictness.LENIENT) .startMocking() @@ -116,25 +112,26 @@ class AuthRippleControllerTest : SysuiTestCase() { `when`(authController.udfpsProps).thenReturn(listOf(fpSensorProp)) `when`(udfpsControllerProvider.get()).thenReturn(udfpsController) - controller = AuthRippleController( - context, - authController, - configurationController, - keyguardUpdateMonitor, - keyguardStateController, - wakefulnessLifecycle, - commandRegistry, - notificationShadeWindowController, - udfpsControllerProvider, - statusBarStateController, - displayMetrics, - KeyguardLogger(logcatLogBuffer(AuthRippleController.TAG)), - biometricUnlockController, - lightRevealScrim, - authRippleInteractor, - facePropertyRepository, - rippleView, - ) + controller = + AuthRippleController( + context, + authController, + configurationController, + keyguardUpdateMonitor, + keyguardStateController, + wakefulnessLifecycle, + commandRegistry, + notificationShadeWindowController, + udfpsControllerProvider, + statusBarStateController, + displayMetrics, + KeyguardLogger(logcatLogBuffer(AuthRippleController.TAG)), + biometricUnlockController, + lightRevealScrim, + authRippleInteractor, + facePropertyRepository, + rippleView, + ) controller.init() } @@ -150,13 +147,18 @@ class AuthRippleControllerTest : SysuiTestCase() { `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation) controller.onViewAttached() `when`(keyguardStateController.isShowing).thenReturn(true) - `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( - eq(BiometricSourceType.FINGERPRINT))).thenReturn(true) + `when`( + keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + eq(BiometricSourceType.FINGERPRINT) + ) + ) + .thenReturn(true) // WHEN fingerprint authenticated verify(biometricUnlockController).addListener(biometricUnlockListener.capture()) - biometricUnlockListener.value - .onBiometricUnlockedWithKeyguardDismissal(BiometricSourceType.FINGERPRINT) + biometricUnlockListener.value.onBiometricUnlockedWithKeyguardDismissal( + BiometricSourceType.FINGERPRINT + ) // THEN update sensor location and show ripple verify(rippleView).setFingerprintSensorLocation(fpsLocation, 0f) @@ -169,8 +171,12 @@ class AuthRippleControllerTest : SysuiTestCase() { val fpsLocation = Point(5, 5) `when`(authController.udfpsLocation).thenReturn(fpsLocation) controller.onViewAttached() - `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( - eq(BiometricSourceType.FINGERPRINT))).thenReturn(true) + `when`( + keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + eq(BiometricSourceType.FINGERPRINT) + ) + ) + .thenReturn(true) // WHEN keyguard is NOT showing & fingerprint authenticated `when`(keyguardStateController.isShowing).thenReturn(false) @@ -179,7 +185,8 @@ class AuthRippleControllerTest : SysuiTestCase() { captor.value.onBiometricAuthenticated( 0 /* userId */, BiometricSourceType.FINGERPRINT /* type */, - false /* isStrongBiometric */) + false /* isStrongBiometric */ + ) // THEN no ripple verify(rippleView, never()).startUnlockedRipple(any()) @@ -194,14 +201,19 @@ class AuthRippleControllerTest : SysuiTestCase() { `when`(keyguardStateController.isShowing).thenReturn(true) // WHEN unlocking with fingerprint is NOT allowed & fingerprint authenticated - `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( - eq(BiometricSourceType.FINGERPRINT))).thenReturn(false) + `when`( + keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + eq(BiometricSourceType.FINGERPRINT) + ) + ) + .thenReturn(false) val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) verify(keyguardUpdateMonitor).registerCallback(captor.capture()) captor.value.onBiometricAuthenticated( 0 /* userId */, BiometricSourceType.FINGERPRINT /* type */, - false /* isStrongBiometric */) + false /* isStrongBiometric */ + ) // THEN no ripple verify(rippleView, never()).startUnlockedRipple(any()) @@ -218,7 +230,8 @@ class AuthRippleControllerTest : SysuiTestCase() { captor.value.onBiometricAuthenticated( 0 /* userId */, BiometricSourceType.FACE /* type */, - false /* isStrongBiometric */) + false /* isStrongBiometric */ + ) verify(rippleView, never()).startUnlockedRipple(any()) } @@ -233,18 +246,17 @@ class AuthRippleControllerTest : SysuiTestCase() { captor.value.onBiometricAuthenticated( 0 /* userId */, BiometricSourceType.FINGERPRINT /* type */, - false /* isStrongBiometric */) + false /* isStrongBiometric */ + ) verify(rippleView, never()).startUnlockedRipple(any()) } @Test fun registersAndDeregisters() { controller.onViewAttached() - val captor = ArgumentCaptor - .forClass(KeyguardStateController.Callback::class.java) + val captor = ArgumentCaptor.forClass(KeyguardStateController.Callback::class.java) verify(keyguardStateController).addCallback(captor.capture()) - val captor2 = ArgumentCaptor - .forClass(WakefulnessLifecycle.Observer::class.java) + val captor2 = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java) verify(wakefulnessLifecycle).addObserver(captor2.capture()) controller.onViewDetached() verify(keyguardStateController).removeCallback(any()) @@ -259,17 +271,25 @@ class AuthRippleControllerTest : SysuiTestCase() { `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation) controller.onViewAttached() `when`(keyguardStateController.isShowing).thenReturn(true) - `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( - BiometricSourceType.FINGERPRINT)).thenReturn(true) + `when`( + keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + BiometricSourceType.FINGERPRINT + ) + ) + .thenReturn(true) `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true) controller.showUnlockRipple(BiometricSourceType.FINGERPRINT) - assertTrue("reveal didn't start on keyguardFadingAway", - controller.startLightRevealScrimOnKeyguardFadingAway) + assertTrue( + "reveal didn't start on keyguardFadingAway", + controller.startLightRevealScrimOnKeyguardFadingAway + ) `when`(keyguardStateController.isKeyguardFadingAway).thenReturn(true) controller.onKeyguardFadingAwayChanged() - assertFalse("reveal triggers multiple times", - controller.startLightRevealScrimOnKeyguardFadingAway) + assertFalse( + "reveal triggers multiple times", + controller.startLightRevealScrimOnKeyguardFadingAway + ) } @Test @@ -282,23 +302,27 @@ class AuthRippleControllerTest : SysuiTestCase() { `when`(keyguardStateController.isShowing).thenReturn(true) `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true) `when`(authController.isUdfpsFingerDown).thenReturn(true) - `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( - eq(BiometricSourceType.FACE))).thenReturn(true) + `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(eq(BiometricSourceType.FACE))) + .thenReturn(true) controller.showUnlockRipple(BiometricSourceType.FACE) - assertTrue("reveal didn't start on keyguardFadingAway", - controller.startLightRevealScrimOnKeyguardFadingAway) + assertTrue( + "reveal didn't start on keyguardFadingAway", + controller.startLightRevealScrimOnKeyguardFadingAway + ) `when`(keyguardStateController.isKeyguardFadingAway).thenReturn(true) controller.onKeyguardFadingAwayChanged() - assertFalse("reveal triggers multiple times", - controller.startLightRevealScrimOnKeyguardFadingAway) + assertFalse( + "reveal triggers multiple times", + controller.startLightRevealScrimOnKeyguardFadingAway + ) } @Test fun testUpdateRippleColor() { controller.onViewAttached() - val captor = ArgumentCaptor - .forClass(ConfigurationController.ConfigurationListener::class.java) + val captor = + ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java) verify(configurationController).addCallback(captor.capture()) reset(rippleView) @@ -333,6 +357,40 @@ class AuthRippleControllerTest : SysuiTestCase() { } @Test + fun testUltrasonicUdfps_onFingerDown_runningForDeviceEntry_doNotShowDwellRipple() { + // GIVEN UDFPS is ultrasonic + `when`(authController.udfpsProps) + .thenReturn( + listOf( + FingerprintSensorPropertiesInternal( + 0 /* sensorId */, + SensorProperties.STRENGTH_STRONG, + 5 /* maxEnrollmentsPerUser */, + listOf<ComponentInfoInternal>(), + FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC, + false /* halControlsIllumination */, + true /* resetLockoutRequiresHardwareAuthToken */, + listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT), + ) + ) + ) + + // GIVEN fingerprint detection is running on keyguard + `when`(keyguardUpdateMonitor.isFingerprintDetectionRunning).thenReturn(true) + + // GIVEN view is already attached + controller.onViewAttached() + val captor = ArgumentCaptor.forClass(UdfpsController.Callback::class.java) + verify(udfpsController).addCallback(captor.capture()) + + // WHEN finger is down + captor.value.onFingerDown() + + // THEN never show dwell ripple + verify(rippleView, never()).startDwellRipple(false) + } + + @Test fun testUdfps_onFingerDown_notDeviceEntry_doesNotShowDwellRipple() { // GIVEN fingerprint detection is NOT running on keyguard `when`(keyguardUpdateMonitor.isFingerprintDetectionRunning).thenReturn(false) diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt index 10b3ce31a895..0489d815b074 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt @@ -89,7 +89,8 @@ class DetailDialogTest : SysuiTestCase() { verify(taskView).startActivity(any(), any(), capture(optionsCaptor), any()) assertThat(optionsCaptor.value.pendingIntentBackgroundActivityStartMode) - .isEqualTo(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) + .isAnyOf(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED, + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS) assertThat(optionsCaptor.value.isPendingIntentBackgroundActivityLaunchAllowedByPermission) .isTrue() assertThat(optionsCaptor.value.taskAlwaysOnTop).isTrue() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt index 693a87761b27..7cc91853a749 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.ui.view.layout.sections import android.graphics.Point +import android.platform.test.annotations.DisableFlags import android.view.WindowManager import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet @@ -106,8 +107,8 @@ class DefaultDeviceEntrySectionTest : SysuiTestCase() { } @Test + @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun addViewsConditionally_migrateFlagOff() { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR) mSetFlagsRule.disableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) val constraintLayout = ConstraintLayout(context, null) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt index 201ee88cdd80..1c99eff0d328 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.ui.view.layout.sections +import android.platform.test.annotations.EnableFlags import android.view.View import android.widget.LinearLayout import androidx.constraintlayout.widget.ConstraintLayout @@ -48,6 +49,7 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidJUnit4::class) @SmallTest +@EnableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) class SmartspaceSectionTest : SysuiTestCase() { private lateinit var underTest: SmartspaceSection @Mock private lateinit var keyguardClockViewModel: KeyguardClockViewModel @@ -70,7 +72,6 @@ class SmartspaceSectionTest : SysuiTestCase() { @Before fun setup() { MockitoAnnotations.initMocks(this) - mSetFlagsRule.enableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) underTest = SmartspaceSection( mContext, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt index 4c77fb84d8ce..27b6ea61a922 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt @@ -27,6 +27,7 @@ import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.settingslib.notification.data.repository.FakeZenModeRepository import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -41,10 +42,12 @@ import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider import com.android.systemui.qs.tiles.viewmodel.QSTileConfigTestBuilder import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig import com.android.systemui.res.R +import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate import com.android.systemui.util.mockito.any import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.settings.SecureSettings import com.google.common.truth.Truth.assertThat +import kotlin.coroutines.EmptyCoroutineContext import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope @@ -79,6 +82,10 @@ class ModesTileTest : SysuiTestCase() { @Mock private lateinit var qsTileConfigProvider: QSTileConfigProvider + @Mock private lateinit var dialogTransitionAnimator: DialogTransitionAnimator + + @Mock private lateinit var dialogDelegate: ModesDialogDelegate + private val inputHandler = FakeQSTileIntentUserInputHandler() private val zenModeRepository = FakeZenModeRepository() private val tileDataInteractor = ModesTileDataInteractor(zenModeRepository) @@ -122,7 +129,13 @@ class ModesTileTest : SysuiTestCase() { } ) - userActionInteractor = ModesTileUserActionInteractor(inputHandler) + userActionInteractor = + ModesTileUserActionInteractor( + EmptyCoroutineContext, + inputHandler, + dialogTransitionAnimator, + dialogDelegate, + ) underTest = ModesTile( diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index b80d1a472a72..8a6b68f26f66 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -18,7 +18,6 @@ package com.android.systemui.shade; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; -import static com.android.keyguard.KeyguardClockSwitch.LARGE; import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer; import static com.google.common.truth.Truth.assertThat; @@ -404,7 +403,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mFeatureFlags.set(Flags.QS_USER_DETAIL_SHORTCUT, false); mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR); - mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); mMainDispatcher = getMainDispatcher(); @@ -801,7 +799,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { .setHeadsUpAppearanceController(mock(HeadsUpAppearanceController.class)); verify(mNotificationStackScrollLayoutController) .setOnEmptySpaceClickListener(mEmptySpaceClickListenerCaptor.capture()); - verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true); reset(mKeyguardStatusViewController); when(mNotificationPanelViewControllerLazy.get()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index 90e8ea5f34c4..905cc4cd13b2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -92,6 +92,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo * When the Back gesture starts (progress 0%), the scrim will stay at 100% scale (1.0f). */ @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testBackGesture_min_scrimAtMaxScale() { mNotificationPanelViewController.onBackProgressed(0.0f); verify(mScrimController).applyBackScaling(1.0f); @@ -101,6 +102,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo * When the Back gesture is at max (progress 100%), the scrim will be scaled to its minimum. */ @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testBackGesture_max_scrimAtMinScale() { mNotificationPanelViewController.onBackProgressed(1.0f); verify(mScrimController).applyBackScaling( @@ -108,6 +110,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onNotificationHeightChangeWhileOnKeyguardWillComputeMaxKeyguardNotifications() { mStatusBarStateController.setState(KEYGUARD); ArgumentCaptor<OnHeightChangedListener> captor = @@ -124,6 +127,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onNotificationHeightChangeWhileInShadeWillNotComputeMaxKeyguardNotifications() { mStatusBarStateController.setState(SHADE); ArgumentCaptor<OnHeightChangedListener> captor = @@ -140,6 +144,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void computeMaxKeyguardNotifications_lockscreenToShade_returnsExistingMax() { when(mAmbientState.getFractionToShade()).thenReturn(0.5f); mNotificationPanelViewController.setMaxDisplayedNotifications(-1); @@ -150,6 +155,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void computeMaxKeyguardNotifications_noTransition_updatesMax() { when(mAmbientState.getFractionToShade()).thenReturn(0f); mNotificationPanelViewController.setMaxDisplayedNotifications(-1); @@ -196,6 +202,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void getVerticalSpaceForLockscreenShelf_useLockIconBottomPadding_returnsShelfHeight() { enableSplitShade(/* enabled= */ false); setBottomPadding(/* stackScrollLayoutBottom= */ 100, @@ -213,6 +220,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void getVerticalSpaceForLockscreenShelf_useIndicationBottomPadding_returnsZero() { enableSplitShade(/* enabled= */ false); setBottomPadding(/* stackScrollLayoutBottom= */ 100, @@ -230,6 +238,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void getVerticalSpaceForLockscreenShelf_useAmbientBottomPadding_returnsZero() { enableSplitShade(/* enabled= */ false); setBottomPadding(/* stackScrollLayoutBottom= */ 100, @@ -247,6 +256,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void getVerticalSpaceForLockscreenShelf_useLockIconPadding_returnsLessThanShelfHeight() { enableSplitShade(/* enabled= */ false); setBottomPadding(/* stackScrollLayoutBottom= */ 100, @@ -264,6 +274,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void getVerticalSpaceForLockscreenShelf_splitShade() { enableSplitShade(/* enabled= */ true); setBottomPadding(/* stackScrollLayoutBottom= */ 100, @@ -281,6 +292,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testSetPanelScrimMinFractionWhenHeadsUpIsDragged() { mNotificationPanelViewController.setHeadsUpDraggingStartingHeight( mNotificationPanelViewController.getMaxPanelHeight() / 2); @@ -288,6 +300,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testSetDozing_notifiesNsslAndStateController() { mNotificationPanelViewController.setDozing(true /* dozing */, false /* animate */); verify(mNotificationStackScrollLayoutController).setDozing(eq(true), eq(false)); @@ -295,6 +308,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testOnDozeAmountChanged_positionClockAndNotificationsUsesUdfpsLocation() { // GIVEN UDFPS is enrolled and we're on the keyguard final Point udfpsLocationCenter = new Point(0, 100); @@ -332,12 +346,14 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testSetExpandedHeight() { mNotificationPanelViewController.setExpandedHeight(200); assertThat((int) mNotificationPanelViewController.getExpandedHeight()).isEqualTo(200); } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testOnTouchEvent_expansionCanBeBlocked() { onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)); onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 200f, 0)); @@ -350,6 +366,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void test_pulsing_onTouchEvent_noTracking() { // GIVEN device is pulsing mNotificationPanelViewController.setPulsing(true); @@ -367,6 +384,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void alternateBouncerVisible_onTouchEvent_notHandled() { mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); // GIVEN alternate bouncer is visible @@ -385,6 +403,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void test_onTouchEvent_startTracking() { // GIVEN device is NOT pulsing mNotificationPanelViewController.setPulsing(false); @@ -402,9 +421,8 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onInterceptTouchEvent_nsslMigrationOff_userActivity() { - mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - mTouchHandler.onInterceptTouchEvent(MotionEvent.obtain(0L /* downTime */, 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */, 0 /* metaState */)); @@ -413,9 +431,8 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @EnableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onInterceptTouchEvent_nsslMigrationOn_userActivity_not_called() { - mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - mTouchHandler.onInterceptTouchEvent(MotionEvent.obtain(0L /* downTime */, 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */, 0 /* metaState */)); @@ -424,6 +441,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testOnTouchEvent_expansionResumesAfterBriefTouch() { mFalsingManager.setIsClassifierEnabled(true); mFalsingManager.setIsFalseTouch(false); @@ -460,6 +478,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testA11y_initializeNode() { AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo(); mAccessibilityDelegate.onInitializeAccessibilityNodeInfo(mView, nodeInfo); @@ -473,6 +492,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testA11y_scrollForward() { mAccessibilityDelegate.performAccessibilityAction( mView, @@ -483,6 +503,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testA11y_scrollUp() { mAccessibilityDelegate.performAccessibilityAction( mView, @@ -493,6 +514,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testKeyguardStatusViewInSplitShade_changesConstraintsDependingOnNotifications() { mStatusBarStateController.setState(KEYGUARD); enableSplitShade(/* enabled= */ true); @@ -511,6 +533,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void keyguardStatusView_splitShade_dozing_alwaysDozingOn_isCentered() { when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true); @@ -523,6 +546,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void keyguardStatusView_splitShade_dozing_alwaysDozingOff_isNotCentered() { when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true); @@ -535,6 +559,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void keyguardStatusView_splitShade_notDozing_alwaysDozingOn_isNotCentered() { when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true); @@ -547,6 +572,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void keyguardStatusView_splitShade_pulsing_isNotCentered() { when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true); @@ -560,6 +586,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void keyguardStatusView_splitShade_notPulsing_isNotCentered() { when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true); @@ -573,6 +600,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void keyguardStatusView_singleShade_isCentered() { enableSplitShade(/* enabled= */ false); // The conditions below would make the clock NOT be centered on split shade. @@ -587,6 +615,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void keyguardStatusView_willPlayDelayedDoze_isCentered_thenNot() { when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true); @@ -602,6 +631,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void keyguardStatusView_willPlayDelayedDoze_notifiesKeyguardMediaController() { when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true); @@ -614,6 +644,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void keyguardStatusView_willPlayDelayedDoze_isCentered_thenStillCenteredIfNoNotifs() { when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0); when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(false); @@ -629,6 +660,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onKeyguardStatusViewHeightChange_animatesNextTopPaddingChangeForNSSL() { ArgumentCaptor<View.OnLayoutChangeListener> captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener.class); @@ -646,6 +678,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testCanCollapsePanelOnTouch_trueForKeyGuard() { mStatusBarStateController.setState(KEYGUARD); @@ -653,6 +686,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testCanCollapsePanelOnTouch_trueWhenScrolledToBottom() { mStatusBarStateController.setState(SHADE); when(mNotificationStackScrollLayoutController.isScrolledToBottom()).thenReturn(true); @@ -661,6 +695,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testCanCollapsePanelOnTouch_trueWhenInSettings() { mStatusBarStateController.setState(SHADE); when(mQsController.getExpanded()).thenReturn(true); @@ -669,6 +704,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testCanCollapsePanelOnTouch_falseInDualPaneShade() { mStatusBarStateController.setState(SHADE); enableSplitShade(/* enabled= */ true); @@ -695,6 +731,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testCancelSwipeWhileLocked_notifiesKeyguardState() { mStatusBarStateController.setState(KEYGUARD); @@ -707,6 +744,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testSwipe_exactlyToTarget_notifiesNssl() { // No over-expansion mNotificationPanelViewController.setOverExpansion(0f); @@ -722,6 +760,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testRotatingToSplitShadeWithQsExpanded_transitionsToShadeLocked() { mStatusBarStateController.setState(KEYGUARD); when(mQsController.getExpanded()).thenReturn(true); @@ -732,6 +771,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testUnlockedSplitShadeTransitioningToKeyguard_closesQS() { enableSplitShade(true); mStatusBarStateController.setState(SHADE); @@ -741,6 +781,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testLockedSplitShadeTransitioningToKeyguard_closesQS() { enableSplitShade(true); mStatusBarStateController.setState(SHADE_LOCKED); @@ -750,6 +791,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testSwitchesToCorrectClockInSinglePaneShade() { mStatusBarStateController.setState(KEYGUARD); @@ -765,6 +807,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testSwitchesToCorrectClockInSplitShade() { mStatusBarStateController.setState(KEYGUARD); enableSplitShade(/* enabled= */ true); @@ -785,6 +828,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testHasNotifications_switchesToLargeClockWhenEnteringSplitShade() { mStatusBarStateController.setState(KEYGUARD); when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1); @@ -796,6 +840,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testNoNotifications_switchesToLargeClockWhenEnteringSplitShade() { mStatusBarStateController.setState(KEYGUARD); when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0); @@ -807,6 +852,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testHasNotifications_switchesToSmallClockWhenExitingSplitShade() { mStatusBarStateController.setState(KEYGUARD); enableSplitShade(/* enabled= */ true); @@ -820,6 +866,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testNoNotifications_switchesToLargeClockWhenExitingSplitShade() { mStatusBarStateController.setState(KEYGUARD); enableSplitShade(/* enabled= */ true); @@ -833,6 +880,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void clockSize_mediaShowing_inSplitShade_onAod_isLarge() { when(mDozeParameters.getAlwaysOn()).thenReturn(true); mStatusBarStateController.setState(KEYGUARD); @@ -848,6 +896,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void clockSize_mediaShowing_inSplitShade_screenOff_notAod_isSmall() { when(mDozeParameters.getAlwaysOn()).thenReturn(false); mStatusBarStateController.setState(KEYGUARD); @@ -863,6 +912,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onQsSetExpansionHeightCalled_qsFullyExpandedOnKeyguard_showNSSL() { // GIVEN mStatusBarStateController.setState(KEYGUARD); @@ -883,6 +933,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onQsSetExpansionHeightCalled_qsFullyExpandedOnKeyguard_hideNSSL() { // GIVEN mStatusBarStateController.setState(KEYGUARD); @@ -904,6 +955,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testSwitchesToBigClockInSplitShadeOnAodAnimateDisabled() { when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(false); mStatusBarStateController.setState(KEYGUARD); @@ -919,6 +971,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void switchesToBigClockInSplitShadeOn_landFlagOn_ForceSmallClock() { when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(false); mStatusBarStateController.setState(KEYGUARD); @@ -938,6 +991,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void switchesToBigClockInSplitShadeOn_landFlagOff_DontForceSmallClock() { when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(false); mStatusBarStateController.setState(KEYGUARD); @@ -957,6 +1011,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testDisplaysSmallClockOnLockscreenInSplitShadeWhenMediaIsPlaying() { mStatusBarStateController.setState(KEYGUARD); enableSplitShade(/* enabled= */ true); @@ -978,6 +1033,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testFoldToAodAnimationCleansupInAnimationEnd() { ArgumentCaptor<Animator.AnimatorListener> animCaptor = ArgumentCaptor.forClass(Animator.AnimatorListener.class); @@ -997,6 +1053,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testExpandWithQsMethodIsUsingLockscreenTransitionController() { enableSplitShade(/* enabled= */ true); mStatusBarStateController.setState(KEYGUARD); @@ -1008,6 +1065,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void setKeyguardStatusBarAlpha_setsAlphaOnKeyguardStatusBarController() { float statusBarAlpha = 0.5f; @@ -1017,6 +1075,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testQsToBeImmediatelyExpandedWhenOpeningPanelInSplitShade() { enableSplitShade(/* enabled= */ true); mShadeExpansionStateManager.updateState(STATE_OPEN); @@ -1030,6 +1089,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testQsNotToBeImmediatelyExpandedWhenGoingFromUnlockedToLocked() { enableSplitShade(/* enabled= */ true); mShadeExpansionStateManager.updateState(STATE_CLOSED); @@ -1042,6 +1102,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testQsImmediateResetsWhenPanelOpensOrCloses() { mShadeExpansionStateManager.updateState(STATE_OPEN); mShadeExpansionStateManager.updateState(STATE_CLOSED); @@ -1049,6 +1110,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testQsExpansionChangedToDefaultWhenRotatingFromOrToSplitShade() { when(mCommandQueue.panelsEnabled()).thenReturn(true); @@ -1065,6 +1127,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testPanelClosedWhenClosingQsInSplitShade() { mShadeExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1, /* expanded= */ true, /* tracking= */ false); @@ -1078,6 +1141,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void getMaxPanelTransitionDistance_expanding_inSplitShade_returnsSplitShadeFullTransitionDistance() { enableSplitShade(true); mNotificationPanelViewController.expandToQs(); @@ -1088,6 +1152,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void isExpandingOrCollapsing_returnsTrue_whenQsLockscreenDragInProgress() { when(mQsController.getLockscreenShadeDragProgress()).thenReturn(0.5f); assertThat(mNotificationPanelViewController.isExpandingOrCollapsing()).isTrue(); @@ -1095,6 +1160,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void getMaxPanelTransitionDistance_inSplitShade_withHeadsUp_returnsBiggerValue() { enableSplitShade(true); mNotificationPanelViewController.expandToQs(); @@ -1111,6 +1177,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void getMaxPanelTransitionDistance_expandingSplitShade_keyguard_returnsNonSplitShadeValue() { mStatusBarStateController.setState(KEYGUARD); enableSplitShade(true); @@ -1122,6 +1189,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void getMaxPanelTransitionDistance_expanding_notSplitShade_returnsNonSplitShadeValue() { enableSplitShade(false); mNotificationPanelViewController.expandToQs(); @@ -1132,6 +1200,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onLayoutChange_fullWidth_updatesQSWithFullWithTrue() { setIsFullWidth(true); @@ -1139,6 +1208,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onLayoutChange_notFullWidth_updatesQSWithFullWithFalse() { setIsFullWidth(false); @@ -1146,6 +1216,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onLayoutChange_qsNotSet_doesNotCrash() { mQuickSettingsController.setQs(null); @@ -1153,6 +1224,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onEmptySpaceClicked_notDozingAndOnKeyguard_requestsFaceAuth() { StatusBarStateController.StateListener statusBarStateListener = mNotificationPanelViewController.getStatusBarStateListener(); @@ -1167,8 +1239,8 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @EnableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void nsslFlagEnabled_allowOnlyExternalTouches() { - mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); // This sets the dozing state that is read when onMiddleClicked is eventually invoked. mTouchHandler.onTouch(mock(View.class), mDownMotionEvent); @@ -1179,6 +1251,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onSplitShadeChanged_duringShadeExpansion_resetsOverScrollState() { // There was a bug where there was left-over overscroll state after going from split shade // to single shade. @@ -1200,6 +1273,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onSplitShadeChanged_alwaysResetsOverScrollState() { enableSplitShade(true); enableSplitShade(false); @@ -1217,6 +1291,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo * to ensure scrollY can be correctly set to be 0 */ @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onShadeFlingClosingEnd_mAmbientStateSetClose_thenOnExpansionStopped() { // Given: Shade is expanded mNotificationPanelViewController.notifyExpandingFinished(); @@ -1237,6 +1312,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onShadeFlingEnd_mExpandImmediateShouldBeReset() { mNotificationPanelViewController.onFlingEnd(false); @@ -1244,6 +1320,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void inUnlockedSplitShade_transitioningMaxTransitionDistance_makesShadeFullyExpanded() { mStatusBarStateController.setState(SHADE); enableSplitShade(true); @@ -1253,6 +1330,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void shadeFullyExpanded_inShadeState() { mStatusBarStateController.setState(SHADE); @@ -1265,6 +1343,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void shadeFullyExpanded_onKeyguard() { mStatusBarStateController.setState(KEYGUARD); @@ -1274,12 +1353,14 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void shadeFullyExpanded_onShadeLocked() { mStatusBarStateController.setState(SHADE_LOCKED); assertThat(mNotificationPanelViewController.isShadeFullyExpanded()).isTrue(); } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void shadeExpanded_whenHasHeight() { int transitionDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance(); mNotificationPanelViewController.setExpandedHeight(transitionDistance); @@ -1287,6 +1368,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void shadeExpanded_whenInstantExpanding() { mNotificationPanelViewController.expand(true); assertThat(mNotificationPanelViewController.isExpanded()).isTrue(); @@ -1300,12 +1382,14 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void shadeExpanded_whenUnlockedOffscreenAnimationRunning() { when(mUnlockedScreenOffAnimationController.isAnimationPlaying()).thenReturn(true); assertThat(mNotificationPanelViewController.isExpanded()).isTrue(); } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void shadeExpanded_whenInputFocusTransferStarted() { when(mCommandQueue.panelsEnabled()).thenReturn(true); @@ -1315,6 +1399,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void shadeNotExpanded_whenInputFocusTransferStartedButPanelsDisabled() { when(mCommandQueue.panelsEnabled()).thenReturn(false); @@ -1324,6 +1409,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void cancelInputFocusTransfer_shadeCollapsed() { when(mCommandQueue.panelsEnabled()).thenReturn(true); mNotificationPanelViewController.startInputFocusTransfer(); @@ -1334,6 +1420,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void finishInputFocusTransfer_shadeFlingingOpen() { when(mCommandQueue.panelsEnabled()).thenReturn(true); mNotificationPanelViewController.startInputFocusTransfer(); @@ -1344,6 +1431,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void getFalsingThreshold_deviceNotInteractive_isQsThreshold() { PowerInteractor.Companion.setAsleepForTest( mPowerInteractor, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON); @@ -1353,6 +1441,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void getFalsingThreshold_lastWakeNotDueToTouch_isQsThreshold() { PowerInteractor.Companion.setAwakeForTest( mPowerInteractor, PowerManager.WAKE_REASON_POWER_BUTTON); @@ -1362,6 +1451,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void getFalsingThreshold_lastWakeDueToTouch_greaterThanQsThreshold() { PowerInteractor.Companion.setAwakeForTest(mPowerInteractor, PowerManager.WAKE_REASON_TAP); when(mQsController.getFalsingThreshold()).thenReturn(14); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt index e1d92e780c2a..52af907c7b7d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.shade +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.testing.TestableLooper import android.view.HapticFeedbackConstants @@ -27,6 +28,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.util.CollectionUtils import com.android.keyguard.KeyguardClockSwitch.LARGE +import com.android.systemui.Flags import com.android.systemui.coroutines.collectLastValue import com.android.systemui.res.R import com.android.systemui.statusbar.StatusBarState.KEYGUARD @@ -58,6 +60,7 @@ import org.mockito.Mockito.verify @RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest +@DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) class NotificationPanelViewControllerWithCoroutinesTest : NotificationPanelViewControllerBaseTest() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index 74a299910b18..6f2302a22d7b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -17,6 +17,8 @@ package com.android.systemui.shade import android.content.Context +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.platform.test.annotations.RequiresFlagsDisabled import android.platform.test.flag.junit.FlagsParameterization import android.testing.TestableLooper @@ -31,6 +33,7 @@ import com.android.keyguard.KeyguardSecurityContainerController import com.android.keyguard.LegacyLockIconViewController import com.android.keyguard.dagger.KeyguardBouncerComponent import com.android.systemui.Flags +import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor @@ -398,8 +401,8 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : @Test @DisableSceneContainer + @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun handleDispatchTouchEvent_nsslMigrationOff_userActivity_not_called() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) underTest.setStatusBarViewController(phoneStatusBarViewController) interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) @@ -408,8 +411,8 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : } @Test + @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun handleDispatchTouchEvent_nsslMigrationOn_userActivity() { - enableMigrateClocksFlag() underTest.setStatusBarViewController(phoneStatusBarViewController) interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) @@ -440,6 +443,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : } @Test + @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun shouldInterceptTouchEvent_dozing_touchInLockIconArea_touchNotIntercepted() { // GIVEN dozing whenever(sysuiStatusBarStateController.isDozing).thenReturn(true) @@ -452,13 +456,12 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : // AND the lock icon wants the touch whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(true) - enableMigrateClocksFlag() - // THEN touch should NOT be intercepted by NotificationShade assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isFalse() } @Test + @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun shouldInterceptTouchEvent_dozing_touchNotInLockIconArea_touchIntercepted() { // GIVEN dozing whenever(sysuiStatusBarStateController.isDozing).thenReturn(true) @@ -471,13 +474,12 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any())) .thenReturn(false) - enableMigrateClocksFlag() - // THEN touch should be intercepted by NotificationShade assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue() } @Test + @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun shouldInterceptTouchEvent_dozing_touchInStatusBar_touchIntercepted() { // GIVEN dozing whenever(sysuiStatusBarStateController.isDozing).thenReturn(true) @@ -490,13 +492,12 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any())) .thenReturn(true) - enableMigrateClocksFlag() - // THEN touch should be intercepted by NotificationShade assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue() } @Test + @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun shouldInterceptTouchEvent_dozingAndPulsing_touchIntercepted() { // GIVEN dozing whenever(sysuiStatusBarStateController.isDozing).thenReturn(true) @@ -517,8 +518,6 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : whenever(shadeViewController.handleExternalInterceptTouch(DOWN_EVENT)) .thenReturn(true) - enableMigrateClocksFlag() - // THEN touch should be intercepted by NotificationShade assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue() } @@ -652,19 +651,13 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : } @Test + @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun cancelCurrentTouch_callsDragDownHelper() { - enableMigrateClocksFlag() underTest.cancelCurrentTouch() verify(dragDownHelper).stopDragging() } - private fun enableMigrateClocksFlag() { - if (!Flags.migrateClocksToBlueprint()) { - mSetFlagsRule.enableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - } - } - companion object { private val DOWN_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) private val MOVE_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index fec7424ac006..ca29dd98a637 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.shade import android.os.SystemClock +import android.platform.test.annotations.DisableFlags import android.testing.TestableLooper.RunWithLooper import android.view.MotionEvent import android.widget.FrameLayout @@ -208,9 +209,9 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { } @Test + @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun testDragDownHelperCalledWhenDraggingDown() = testScope.runTest { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) whenever(dragDownHelper.isDraggingDown).thenReturn(true) val now = SystemClock.elapsedRealtime() val ev = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, 0f, 0f, 0 /* meta */) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt index c9a7c82d6b3f..02764f8a15fd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt @@ -16,9 +16,13 @@ package com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel +import android.content.DialogInterface import android.view.View import androidx.test.filters.SmallTest +import com.android.internal.jank.Cuj import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.DialogCuj +import com.android.systemui.animation.mockDialogTransitionAnimator import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue @@ -37,6 +41,8 @@ import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.Me import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel +import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer +import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory import com.android.systemui.statusbar.policy.CastDevice @@ -45,7 +51,10 @@ import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlinx.coroutines.test.runTest import org.junit.Before +import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @@ -60,6 +69,16 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { private val mockScreenCastDialog = mock<SystemUIDialog>() private val mockGenericCastDialog = mock<SystemUIDialog>() + private val chipBackgroundView = mock<ChipBackgroundContainer>() + private val chipView = + mock<View>().apply { + whenever( + this.requireViewById<ChipBackgroundContainer>( + R.id.ongoing_activity_chip_background + ) + ) + .thenReturn(chipBackgroundView) + } private val underTest = kosmos.castToOtherDeviceChipViewModel @@ -193,6 +212,63 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { } @Test + fun chip_projectionStoppedFromDialog_chipImmediatelyHidden() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE) + + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) + + // WHEN the stop action on the dialog is clicked + val dialogStopAction = + getStopActionFromDialog(latest, chipView, mockScreenCastDialog, kosmos) + dialogStopAction.onClick(mock<DialogInterface>(), 0) + + // THEN the chip is immediately hidden... + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) + // ...even though the repo still says it's projecting + assertThat(mediaProjectionRepo.mediaProjectionState.value) + .isInstanceOf(MediaProjectionState.Projecting::class.java) + + // AND we specify no animation + assertThat((latest as OngoingActivityChipModel.Hidden).shouldAnimate).isFalse() + } + + @Test + fun chip_routeStoppedFromDialog_chipImmediatelyHidden() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + mediaRouterRepo.castDevices.value = + listOf( + CastDevice( + state = CastDevice.CastState.Connected, + id = "id", + name = "name", + description = "desc", + origin = CastDevice.CastOrigin.MediaRouter, + ) + ) + + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) + + // WHEN the stop action on the dialog is clicked + val dialogStopAction = + getStopActionFromDialog(latest, chipView, mockGenericCastDialog, kosmos) + dialogStopAction.onClick(mock<DialogInterface>(), 0) + + // THEN the chip is immediately hidden... + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) + // ...even though the repo still says it's projecting + assertThat(mediaRouterRepo.castDevices.value).isNotEmpty() + + // AND we specify no animation + assertThat((latest as OngoingActivityChipModel.Hidden).shouldAnimate).isFalse() + } + + @Test fun chip_colorsAreRed() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -297,8 +373,14 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) assertThat(clickListener).isNotNull() - clickListener!!.onClick(mock<View>()) - verify(mockScreenCastDialog).show() + clickListener!!.onClick(chipView) + verify(kosmos.mockDialogTransitionAnimator) + .showFromView( + eq(mockScreenCastDialog), + eq(chipBackgroundView), + any(), + anyBoolean(), + ) } @Test @@ -316,8 +398,14 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) assertThat(clickListener).isNotNull() - clickListener!!.onClick(mock<View>()) - verify(mockScreenCastDialog).show() + clickListener!!.onClick(chipView) + verify(kosmos.mockDialogTransitionAnimator) + .showFromView( + eq(mockScreenCastDialog), + eq(chipBackgroundView), + any(), + anyBoolean(), + ) } @Test @@ -339,7 +427,70 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) assertThat(clickListener).isNotNull() - clickListener!!.onClick(mock<View>()) - verify(mockGenericCastDialog).show() + clickListener!!.onClick(chipView) + verify(kosmos.mockDialogTransitionAnimator) + .showFromView( + eq(mockGenericCastDialog), + eq(chipBackgroundView), + any(), + anyBoolean(), + ) + } + + @Test + fun chip_projectionStateCasting_clickListenerHasCuj() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE) + + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + clickListener!!.onClick(chipView) + + val cujCaptor = argumentCaptor<DialogCuj>() + verify(kosmos.mockDialogTransitionAnimator) + .showFromView( + any(), + any(), + cujCaptor.capture(), + anyBoolean(), + ) + + assertThat(cujCaptor.firstValue.cujType) + .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP) + assertThat(cujCaptor.firstValue.tag).contains("Cast") + } + + @Test + fun chip_routerStateCasting_clickListenerHasCuj() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + mediaRouterRepo.castDevices.value = + listOf( + CastDevice( + state = CastDevice.CastState.Connected, + id = "id", + name = "name", + description = "desc", + origin = CastDevice.CastOrigin.MediaRouter, + ) + ) + + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + clickListener!!.onClick(chipView) + + val cujCaptor = argumentCaptor<DialogCuj>() + verify(kosmos.mockDialogTransitionAnimator) + .showFromView( + any(), + any(), + cujCaptor.capture(), + anyBoolean(), + ) + + assertThat(cujCaptor.firstValue.cujType) + .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP) + assertThat(cujCaptor.firstValue.tag).contains("Cast") } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt index 4728c649b9a7..b4a37ee1a55e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt @@ -16,9 +16,13 @@ package com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel +import android.content.DialogInterface import android.view.View import androidx.test.filters.SmallTest +import com.android.internal.jank.Cuj import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.DialogCuj +import com.android.systemui.animation.mockDialogTransitionAnimator import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.Kosmos @@ -30,9 +34,13 @@ import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager import com.android.systemui.res.R import com.android.systemui.screenrecord.data.model.ScreenRecordModel import com.android.systemui.screenrecord.data.repository.screenRecordRepository +import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection import com.android.systemui.statusbar.chips.screenrecord.ui.view.EndScreenRecordingDialogDelegate +import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.shareToAppChipViewModel import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel +import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer +import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory import com.android.systemui.util.time.fakeSystemClock @@ -40,7 +48,10 @@ import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlinx.coroutines.test.runTest import org.junit.Before +import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @@ -53,11 +64,22 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { private val mediaProjectionRepo = kosmos.fakeMediaProjectionRepository private val systemClock = kosmos.fakeSystemClock private val mockSystemUIDialog = mock<SystemUIDialog>() + private val chipBackgroundView = mock<ChipBackgroundContainer>() + private val chipView = + mock<View>().apply { + whenever( + this.requireViewById<ChipBackgroundContainer>( + R.id.ongoing_activity_chip_background + ) + ) + .thenReturn(chipBackgroundView) + } private val underTest = kosmos.screenRecordChipViewModel @Before fun setUp() { + setUpPackageManagerForMediaProjection(kosmos) whenever(kosmos.mockSystemUIDialogFactory.create(any<EndScreenRecordingDialogDelegate>())) .thenReturn(mockSystemUIDialog) } @@ -132,6 +154,40 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { } @Test + fun chip_recordingStoppedFromDialog_screenRecordAndShareToAppChipImmediatelyHidden() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + val latestShareToApp by collectLastValue(kosmos.shareToAppChipViewModel.chip) + + // On real devices, when screen recording is active then share-to-app is also active + // because screen record is just a special case of share-to-app where the app receiving + // the share is SysUI + screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.EntireScreen("fake.package") + + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) + assertThat(latestShareToApp).isInstanceOf(OngoingActivityChipModel.Shown::class.java) + + // WHEN the stop action on the dialog is clicked + val dialogStopAction = + getStopActionFromDialog(latest, chipView, mockSystemUIDialog, kosmos) + dialogStopAction.onClick(mock<DialogInterface>(), 0) + + // THEN both the screen record chip and the share-to-app chip are immediately hidden... + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) + assertThat(latestShareToApp).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) + // ...even though the repos still say it's recording + assertThat(screenRecordRepo.screenRecordState.value) + .isEqualTo(ScreenRecordModel.Recording) + assertThat(mediaProjectionRepo.mediaProjectionState.value) + .isInstanceOf(MediaProjectionState.Projecting::class.java) + + // AND we specify no animation + assertThat((latest as OngoingActivityChipModel.Hidden).shouldAnimate).isFalse() + } + + @Test fun chip_startingState_colorsAreRed() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -182,9 +238,15 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) assertThat(clickListener).isNotNull() - clickListener!!.onClick(mock<View>()) + clickListener!!.onClick(chipView) // EndScreenRecordingDialogDelegate will test that the dialog has the right message - verify(mockSystemUIDialog).show() + verify(kosmos.mockDialogTransitionAnimator) + .showFromView( + eq(mockSystemUIDialog), + eq(chipBackgroundView), + any(), + anyBoolean(), + ) } @Test @@ -198,9 +260,15 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) assertThat(clickListener).isNotNull() - clickListener!!.onClick(mock<View>()) + clickListener!!.onClick(chipView) // EndScreenRecordingDialogDelegate will test that the dialog has the right message - verify(mockSystemUIDialog).show() + verify(kosmos.mockDialogTransitionAnimator) + .showFromView( + eq(mockSystemUIDialog), + eq(chipBackgroundView), + any(), + anyBoolean(), + ) } @Test @@ -218,8 +286,39 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) assertThat(clickListener).isNotNull() - clickListener!!.onClick(mock<View>()) + clickListener!!.onClick(chipView) // EndScreenRecordingDialogDelegate will test that the dialog has the right message - verify(mockSystemUIDialog).show() + verify(kosmos.mockDialogTransitionAnimator) + .showFromView( + eq(mockSystemUIDialog), + eq(chipBackgroundView), + any(), + anyBoolean(), + ) + } + + @Test + fun chip_clickListenerHasCuj() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.EntireScreen("host.package") + + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + clickListener!!.onClick(chipView) + + val cujCaptor = argumentCaptor<DialogCuj>() + verify(kosmos.mockDialogTransitionAnimator) + .showFromView( + any(), + any(), + cujCaptor.capture(), + anyBoolean(), + ) + + assertThat(cujCaptor.firstValue.cujType) + .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP) + assertThat(cujCaptor.firstValue.tag).contains("Screen record") } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt index f87b17dc92d1..2658679dee08 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt @@ -16,9 +16,13 @@ package com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel +import android.content.DialogInterface import android.view.View import androidx.test.filters.SmallTest +import com.android.internal.jank.Cuj import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.DialogCuj +import com.android.systemui.animation.mockDialogTransitionAnimator import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.Kosmos @@ -34,6 +38,8 @@ import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.Me import com.android.systemui.statusbar.chips.sharetoapp.ui.view.EndShareToAppDialogDelegate import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel +import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer +import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory import com.android.systemui.util.time.fakeSystemClock @@ -41,7 +47,10 @@ import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlinx.coroutines.test.runTest import org.junit.Before +import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @@ -54,6 +63,16 @@ class ShareToAppChipViewModelTest : SysuiTestCase() { private val systemClock = kosmos.fakeSystemClock private val mockShareDialog = mock<SystemUIDialog>() + private val chipBackgroundView = mock<ChipBackgroundContainer>() + private val chipView = + mock<View>().apply { + whenever( + this.requireViewById<ChipBackgroundContainer>( + R.id.ongoing_activity_chip_background + ) + ) + .thenReturn(chipBackgroundView) + } private val underTest = kosmos.shareToAppChipViewModel @@ -134,6 +153,31 @@ class ShareToAppChipViewModelTest : SysuiTestCase() { } @Test + fun chip_shareStoppedFromDialog_chipImmediatelyHidden() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE) + + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) + + // WHEN the stop action on the dialog is clicked + val dialogStopAction = + getStopActionFromDialog(latest, chipView, mockShareDialog, kosmos) + dialogStopAction.onClick(mock<DialogInterface>(), 0) + + // THEN the chip is immediately hidden... + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) + // ...even though the repo still says it's projecting + assertThat(mediaProjectionRepo.mediaProjectionState.value) + .isInstanceOf(MediaProjectionState.Projecting::class.java) + + // AND we specify no animation + assertThat((latest as OngoingActivityChipModel.Hidden).shouldAnimate).isFalse() + } + + @Test fun chip_colorsAreRed() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -181,8 +225,14 @@ class ShareToAppChipViewModelTest : SysuiTestCase() { val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) assertThat(clickListener).isNotNull() - clickListener!!.onClick(mock<View>()) - verify(mockShareDialog).show() + clickListener!!.onClick(chipView) + verify(kosmos.mockDialogTransitionAnimator) + .showFromView( + eq(mockShareDialog), + eq(chipBackgroundView), + any(), + anyBoolean(), + ) } @Test @@ -199,7 +249,41 @@ class ShareToAppChipViewModelTest : SysuiTestCase() { val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) assertThat(clickListener).isNotNull() - clickListener!!.onClick(mock<View>()) - verify(mockShareDialog).show() + clickListener!!.onClick(chipView) + verify(kosmos.mockDialogTransitionAnimator) + .showFromView( + eq(mockShareDialog), + eq(chipBackgroundView), + any(), + anyBoolean(), + ) + } + + @Test + fun chip_clickListenerHasCuj() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.SingleTask( + NORMAL_PACKAGE, + hostDeviceName = null, + createTask(taskId = 1), + ) + + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + clickListener!!.onClick(chipView) + + val cujCaptor = argumentCaptor<DialogCuj>() + verify(kosmos.mockDialogTransitionAnimator) + .showFromView( + any(), + any(), + cujCaptor.capture(), + anyBoolean(), + ) + + assertThat(cujCaptor.firstValue.cujType) + .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP) + assertThat(cujCaptor.firstValue.tag).contains("Share") } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt new file mode 100644 index 000000000000..b9049e8f76b6 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt @@ -0,0 +1,154 @@ +/* + * 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.chips.ui.viewmodel + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testScope +import com.android.systemui.res.R +import com.android.systemui.statusbar.chips.ui.model.ColorsModel +import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest + +@SmallTest +@OptIn(ExperimentalCoroutinesApi::class) +class ChipTransitionHelperTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val testScope = kosmos.testScope + + @Test + fun createChipFlow_typicallyFollowsInputFlow() = + testScope.runTest { + val underTest = ChipTransitionHelper(kosmos.applicationCoroutineScope) + val inputChipFlow = + MutableStateFlow<OngoingActivityChipModel>(OngoingActivityChipModel.Hidden()) + val latest by collectLastValue(underTest.createChipFlow(inputChipFlow)) + + val newChip = + OngoingActivityChipModel.Shown.Timer( + icon = Icon.Resource(R.drawable.ic_cake, contentDescription = null), + colors = ColorsModel.Themed, + startTimeMs = 100L, + onClickListener = null, + ) + + inputChipFlow.value = newChip + + assertThat(latest).isEqualTo(newChip) + + val newerChip = + OngoingActivityChipModel.Shown.IconOnly( + icon = Icon.Resource(R.drawable.ic_hotspot, contentDescription = null), + colors = ColorsModel.Themed, + onClickListener = null, + ) + + inputChipFlow.value = newerChip + + assertThat(latest).isEqualTo(newerChip) + } + + @Test + fun activityStopped_chipHiddenWithoutAnimationFor500ms() = + testScope.runTest { + val underTest = ChipTransitionHelper(kosmos.applicationCoroutineScope) + val inputChipFlow = + MutableStateFlow<OngoingActivityChipModel>(OngoingActivityChipModel.Hidden()) + val latest by collectLastValue(underTest.createChipFlow(inputChipFlow)) + + val shownChip = + OngoingActivityChipModel.Shown.Timer( + icon = Icon.Resource(R.drawable.ic_cake, contentDescription = null), + colors = ColorsModel.Themed, + startTimeMs = 100L, + onClickListener = null, + ) + + inputChipFlow.value = shownChip + + assertThat(latest).isEqualTo(shownChip) + + // WHEN #onActivityStopped is invoked + underTest.onActivityStoppedFromDialog() + runCurrent() + + // THEN the chip is hidden and has no animation + assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden(shouldAnimate = false)) + + // WHEN only 250ms have elapsed + advanceTimeBy(250) + + // THEN the chip is still hidden + assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden(shouldAnimate = false)) + + // WHEN over 500ms have elapsed + advanceTimeBy(251) + + // THEN the chip returns to the original input flow value + assertThat(latest).isEqualTo(shownChip) + } + + @Test + fun activityStopped_stoppedAgainBefore500ms_chipReshownAfterSecond500ms() = + testScope.runTest { + val underTest = ChipTransitionHelper(kosmos.applicationCoroutineScope) + val inputChipFlow = + MutableStateFlow<OngoingActivityChipModel>(OngoingActivityChipModel.Hidden()) + val latest by collectLastValue(underTest.createChipFlow(inputChipFlow)) + + val shownChip = + OngoingActivityChipModel.Shown.Timer( + icon = Icon.Resource(R.drawable.ic_cake, contentDescription = null), + colors = ColorsModel.Themed, + startTimeMs = 100L, + onClickListener = null, + ) + + inputChipFlow.value = shownChip + + assertThat(latest).isEqualTo(shownChip) + + // WHEN #onActivityStopped is invoked + underTest.onActivityStoppedFromDialog() + runCurrent() + + // THEN the chip is hidden and has no animation + assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden(shouldAnimate = false)) + + // WHEN 250ms have elapsed, get another stop event + advanceTimeBy(250) + underTest.onActivityStoppedFromDialog() + runCurrent() + + // THEN the chip is still hidden for another 500ms afterwards + assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden(shouldAnimate = false)) + advanceTimeBy(499) + assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden(shouldAnimate = false)) + advanceTimeBy(2) + assertThat(latest).isEqualTo(shownChip) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt index ca043f163854..6e4d8863fee2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt @@ -18,32 +18,58 @@ package com.android.systemui.statusbar.chips.ui.viewmodel import android.view.View import androidx.test.filters.SmallTest +import com.android.internal.jank.Cuj import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.DialogCuj +import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.log.logcatLogBuffer +import com.android.systemui.res.R +import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener import com.android.systemui.statusbar.phone.SystemUIDialog import kotlin.test.Test +import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever @SmallTest class OngoingActivityChipViewModelTest : SysuiTestCase() { private val mockSystemUIDialog = mock<SystemUIDialog>() private val dialogDelegate = SystemUIDialog.Delegate { mockSystemUIDialog } + private val dialogTransitionAnimator = mock<DialogTransitionAnimator>() + + private val chipBackgroundView = mock<ChipBackgroundContainer>() + private val chipView = + mock<View>().apply { + whenever( + this.requireViewById<ChipBackgroundContainer>( + R.id.ongoing_activity_chip_background + ) + ) + .thenReturn(chipBackgroundView) + } @Test fun createDialogLaunchOnClickListener_showsDialogOnClick() { + val cuj = DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Test") val clickListener = createDialogLaunchOnClickListener( dialogDelegate, + dialogTransitionAnimator, + cuj, logcatLogBuffer("OngoingActivityChipViewModelTest"), "tag", ) - // Dialogs must be created on the main thread - context.mainExecutor.execute { - clickListener.onClick(mock<View>()) - verify(mockSystemUIDialog).show() - } + clickListener.onClick(chipView) + verify(dialogTransitionAnimator) + .showFromView( + eq(mockSystemUIDialog), + eq(chipBackgroundView), + eq(cuj), + anyBoolean(), + ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt index b1a8d0beab34..ee249f0f8a2c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt @@ -16,6 +16,10 @@ package com.android.systemui.statusbar.chips.ui.viewmodel +import android.content.DialogInterface +import android.content.packageManager +import android.content.pm.PackageManager +import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -33,10 +37,14 @@ import com.android.systemui.screenrecord.data.repository.screenRecordRepository import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel +import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer +import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.util.time.fakeSystemClock import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.test.runCurrent @@ -44,9 +52,17 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) +@OptIn(ExperimentalCoroutinesApi::class) class OngoingActivityChipsViewModelTest : SysuiTestCase() { private val kosmos = Kosmos().also { it.testCase = this } private val testScope = kosmos.testScope @@ -56,6 +72,18 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { private val mediaProjectionState = kosmos.fakeMediaProjectionRepository.mediaProjectionState private val callRepo = kosmos.ongoingCallRepository + private val mockSystemUIDialog = mock<SystemUIDialog>() + private val chipBackgroundView = mock<ChipBackgroundContainer>() + private val chipView = + mock<View>().apply { + whenever( + this.requireViewById<ChipBackgroundContainer>( + R.id.ongoing_activity_chip_background + ) + ) + .thenReturn(chipBackgroundView) + } + private val underTest = kosmos.ongoingActivityChipsViewModel @Before @@ -72,7 +100,7 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { val latest by collectLastValue(underTest.chip) - assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) } @Test @@ -230,7 +258,81 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { job2.cancel() } + @Test + fun chip_screenRecordStoppedViaDialog_chipHiddenWithoutAnimation() = + testScope.runTest { + screenRecordState.value = ScreenRecordModel.Recording + mediaProjectionState.value = MediaProjectionState.NotProjecting + callRepo.setOngoingCallState(OngoingCallModel.NoCall) + + val latest by collectLastValue(underTest.chip) + + assertIsScreenRecordChip(latest) + + // WHEN screen record gets stopped via dialog + val dialogStopAction = + getStopActionFromDialog(latest, chipView, mockSystemUIDialog, kosmos) + dialogStopAction.onClick(mock<DialogInterface>(), 0) + + // THEN the chip is immediately hidden with no animation + assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden(shouldAnimate = false)) + } + + @Test + fun chip_projectionStoppedViaDialog_chipHiddenWithoutAnimation() = + testScope.runTest { + mediaProjectionState.value = + MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE) + screenRecordState.value = ScreenRecordModel.DoingNothing + callRepo.setOngoingCallState(OngoingCallModel.NoCall) + + val latest by collectLastValue(underTest.chip) + + assertIsShareToAppChip(latest) + + // WHEN media projection gets stopped via dialog + val dialogStopAction = + getStopActionFromDialog(latest, chipView, mockSystemUIDialog, kosmos) + dialogStopAction.onClick(mock<DialogInterface>(), 0) + + // THEN the chip is immediately hidden with no animation + assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden(shouldAnimate = false)) + } + companion object { + /** + * Assuming that the click listener in [latest] opens a dialog, this fetches the action + * associated with the positive button, which we assume is the "Stop sharing" action. + */ + fun getStopActionFromDialog( + latest: OngoingActivityChipModel?, + chipView: View, + dialog: SystemUIDialog, + kosmos: Kosmos + ): DialogInterface.OnClickListener { + // Capture the action that would get invoked when the user clicks "Stop" on the dialog + lateinit var dialogStopAction: DialogInterface.OnClickListener + Mockito.doAnswer { + val delegate = it.arguments[0] as SystemUIDialog.Delegate + delegate.beforeCreate(dialog, /* savedInstanceState= */ null) + + val stopActionCaptor = argumentCaptor<DialogInterface.OnClickListener>() + verify(dialog).setPositiveButton(any(), stopActionCaptor.capture()) + dialogStopAction = stopActionCaptor.firstValue + + return@doAnswer dialog + } + .whenever(kosmos.mockSystemUIDialogFactory) + .create(any<SystemUIDialog.Delegate>()) + whenever(kosmos.packageManager.getApplicationInfo(eq(NORMAL_PACKAGE), any<Int>())) + .thenThrow(PackageManager.NameNotFoundException()) + // Click the chip so that we open the dialog and we fill in [dialogStopAction] + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + clickListener!!.onClick(chipView) + + return dialogStopAction + } + fun assertIsScreenRecordChip(latest: OngoingActivityChipModel?) { assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) val icon = (latest as OngoingActivityChipModel.Shown).icon diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt index d87b3e23b471..4218be26c58e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt @@ -13,88 +13,57 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) package com.android.systemui.statusbar.notification.collection.coordinator -import android.app.Notification -import android.os.UserHandle -import android.platform.test.flag.junit.FlagsParameterization -import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.dump.DumpManager -import com.android.systemui.flags.andSceneContainer -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState -import com.android.systemui.keyguard.shared.model.TransitionStep -import com.android.systemui.kosmos.Kosmos -import com.android.systemui.log.logcatLogBuffer import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.scene.data.repository.Idle -import com.android.systemui.scene.data.repository.setTransition -import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.StatusBarState -import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.NotifPipeline -import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable -import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider -import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository -import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow -import com.android.systemui.statusbar.policy.HeadsUpManager -import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.withArgCaptor -import com.android.systemui.util.settings.FakeSettings -import com.google.common.truth.Truth.assertThat import java.util.function.Consumer -import kotlin.time.Duration.Companion.seconds -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestCoroutineScheduler -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.UnconfinedTestDispatcher -import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.same -import org.mockito.Mockito.anyString import org.mockito.Mockito.clearInvocations -import org.mockito.Mockito.never import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever -import platform.test.runner.parameterized.ParameterizedAndroidJunit4 -import platform.test.runner.parameterized.Parameters +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever @SmallTest -@RunWith(ParameterizedAndroidJunit4::class) -class KeyguardCoordinatorTest(flags: FlagsParameterization) : SysuiTestCase() { +@RunWith(AndroidJUnit4::class) +class KeyguardCoordinatorTest : SysuiTestCase() { - private val kosmos = Kosmos() - - private val headsUpManager: HeadsUpManager = mock() private val keyguardNotifVisibilityProvider: KeyguardNotificationVisibilityProvider = mock() - private val keyguardRepository = FakeKeyguardRepository() - private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository private val notifPipeline: NotifPipeline = mock() private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock() private val statusBarStateController: StatusBarStateController = mock() - init { - mSetFlagsRule.setFlagsParameterization(flags) + private lateinit var onStateChangeListener: Consumer<String> + + @Before + fun setup() { + val keyguardCoordinator = + KeyguardCoordinator( + keyguardNotifVisibilityProvider, + sectionHeaderVisibilityProvider, + statusBarStateController, + ) + keyguardCoordinator.attach(notifPipeline) + onStateChangeListener = + argumentCaptor { + verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture()) + } + .lastValue } @Test - fun testSetSectionHeadersVisibleInShade() = runKeyguardCoordinatorTest { + fun testSetSectionHeadersVisibleInShade() { clearInvocations(sectionHeaderVisibilityProvider) whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE) onStateChangeListener.accept("state change") @@ -102,617 +71,10 @@ class KeyguardCoordinatorTest(flags: FlagsParameterization) : SysuiTestCase() { } @Test - fun testSetSectionHeadersNotVisibleOnKeyguard() = runKeyguardCoordinatorTest { + fun testSetSectionHeadersNotVisibleOnKeyguard() { clearInvocations(sectionHeaderVisibilityProvider) whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) onStateChangeListener.accept("state change") verify(sectionHeaderVisibilityProvider).sectionHeadersVisible = eq(false) } - - @Test - fun unseenFilterSuppressesSeenNotifWhileKeyguardShowing() { - // GIVEN: Keyguard is not showing, shade is expanded, and a notification is present - keyguardRepository.setKeyguardShowing(false) - whenever(statusBarStateController.isExpanded).thenReturn(true) - runKeyguardCoordinatorTest { - val fakeEntry = NotificationEntryBuilder().build() - collectionListener.onEntryAdded(fakeEntry) - - // WHEN: The keyguard is now showing - keyguardRepository.setKeyguardShowing(true) - testScheduler.runCurrent() - - // THEN: The notification is recognized as "seen" and is filtered out. - assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue() - - // WHEN: The keyguard goes away - keyguardRepository.setKeyguardShowing(false) - testScheduler.runCurrent() - - // THEN: The notification is shown regardless - assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() - } - } - - @Test - fun unseenFilterStopsMarkingSeenNotifWhenTransitionToAod() { - // GIVEN: Keyguard is not showing, shade is not expanded, and a notification is present - keyguardRepository.setKeyguardShowing(false) - whenever(statusBarStateController.isExpanded).thenReturn(false) - runKeyguardCoordinatorTest { - val fakeEntry = NotificationEntryBuilder().build() - collectionListener.onEntryAdded(fakeEntry) - - // WHEN: The device transitions to AOD - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.GONE, - to = KeyguardState.AOD, - this.testScheduler, - ) - testScheduler.runCurrent() - - // THEN: We are no longer listening for shade expansions - verify(statusBarStateController, never()).addCallback(any()) - } - } - - @Test - fun unseenFilter_headsUpMarkedAsSeen() { - // GIVEN: Keyguard is not showing, shade is not expanded - keyguardRepository.setKeyguardShowing(false) - whenever(statusBarStateController.isExpanded).thenReturn(false) - runKeyguardCoordinatorTest { - kosmos.setTransition( - sceneTransition = Idle(Scenes.Gone), - stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE) - ) - - // WHEN: A notification is posted - val fakeEntry = NotificationEntryBuilder().build() - collectionListener.onEntryAdded(fakeEntry) - - // WHEN: That notification is heads up - onHeadsUpChangedListener.onHeadsUpStateChanged(fakeEntry, /* isHeadsUp= */ true) - testScheduler.runCurrent() - - // WHEN: The keyguard is now showing - keyguardRepository.setKeyguardShowing(true) - kosmos.setTransition( - sceneTransition = Idle(Scenes.Lockscreen), - stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD) - ) - - // THEN: The notification is recognized as "seen" and is filtered out. - assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue() - - // WHEN: The keyguard goes away - keyguardRepository.setKeyguardShowing(false) - kosmos.setTransition( - sceneTransition = Idle(Scenes.Gone), - stateTransition = TransitionStep(KeyguardState.AOD, KeyguardState.GONE) - ) - - // THEN: The notification is shown regardless - assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() - } - } - - @Test - fun unseenFilterDoesNotSuppressSeenOngoingNotifWhileKeyguardShowing() { - // GIVEN: Keyguard is not showing, shade is expanded, and an ongoing notification is present - keyguardRepository.setKeyguardShowing(false) - whenever(statusBarStateController.isExpanded).thenReturn(true) - runKeyguardCoordinatorTest { - val fakeEntry = - NotificationEntryBuilder() - .setNotification(Notification.Builder(mContext, "id").setOngoing(true).build()) - .build() - collectionListener.onEntryAdded(fakeEntry) - - // WHEN: The keyguard is now showing - keyguardRepository.setKeyguardShowing(true) - testScheduler.runCurrent() - - // THEN: The notification is recognized as "ongoing" and is not filtered out. - assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() - } - } - - @Test - fun unseenFilterDoesNotSuppressSeenMediaNotifWhileKeyguardShowing() { - // GIVEN: Keyguard is not showing, shade is expanded, and a media notification is present - keyguardRepository.setKeyguardShowing(false) - whenever(statusBarStateController.isExpanded).thenReturn(true) - runKeyguardCoordinatorTest { - val fakeEntry = - NotificationEntryBuilder().build().apply { - row = - mock<ExpandableNotificationRow>().apply { - whenever(isMediaRow).thenReturn(true) - } - } - collectionListener.onEntryAdded(fakeEntry) - - // WHEN: The keyguard is now showing - keyguardRepository.setKeyguardShowing(true) - testScheduler.runCurrent() - - // THEN: The notification is recognized as "media" and is not filtered out. - assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() - } - } - - @Test - fun unseenFilterUpdatesSeenProviderWhenSuppressing() { - // GIVEN: Keyguard is not showing, shade is expanded, and a notification is present - keyguardRepository.setKeyguardShowing(false) - whenever(statusBarStateController.isExpanded).thenReturn(true) - runKeyguardCoordinatorTest { - val fakeEntry = NotificationEntryBuilder().build() - collectionListener.onEntryAdded(fakeEntry) - - // WHEN: The keyguard is now showing - keyguardRepository.setKeyguardShowing(true) - testScheduler.runCurrent() - - // THEN: The notification is recognized as "seen" and is filtered out. - assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue() - - // WHEN: The filter is cleaned up - unseenFilter.onCleanup() - - // THEN: The SeenNotificationProvider has been updated to reflect the suppression - assertThat(seenNotificationsInteractor.hasFilteredOutSeenNotifications.value).isTrue() - } - } - - @Test - fun unseenFilterInvalidatesWhenSettingChanges() { - // GIVEN: Keyguard is not showing, and shade is expanded - keyguardRepository.setKeyguardShowing(false) - whenever(statusBarStateController.isExpanded).thenReturn(true) - runKeyguardCoordinatorTest { - // GIVEN: A notification is present - val fakeEntry = NotificationEntryBuilder().build() - collectionListener.onEntryAdded(fakeEntry) - - // GIVEN: The setting for filtering unseen notifications is disabled - showOnlyUnseenNotifsOnKeyguardSetting = false - - // GIVEN: The pipeline has registered the unseen filter for invalidation - val invalidationListener: Pluggable.PluggableListener<NotifFilter> = mock() - unseenFilter.setInvalidationListener(invalidationListener) - - // WHEN: The keyguard is now showing - keyguardRepository.setKeyguardShowing(true) - testScheduler.runCurrent() - - // THEN: The notification is not filtered out - assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() - - // WHEN: The secure setting is changed - showOnlyUnseenNotifsOnKeyguardSetting = true - - // THEN: The pipeline is invalidated - verify(invalidationListener).onPluggableInvalidated(same(unseenFilter), anyString()) - - // THEN: The notification is recognized as "seen" and is filtered out. - assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue() - } - } - - @Test - fun unseenFilterAllowsNewNotif() { - // GIVEN: Keyguard is showing, no notifications present - keyguardRepository.setKeyguardShowing(true) - runKeyguardCoordinatorTest { - // WHEN: A new notification is posted - val fakeEntry = NotificationEntryBuilder().build() - collectionListener.onEntryAdded(fakeEntry) - - // THEN: The notification is recognized as "unseen" and is not filtered out. - assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() - } - } - - @Test - fun unseenFilterSeenGroupSummaryWithUnseenChild() { - // GIVEN: Keyguard is not showing, shade is expanded, and a notification is present - keyguardRepository.setKeyguardShowing(false) - whenever(statusBarStateController.isExpanded).thenReturn(true) - runKeyguardCoordinatorTest { - // WHEN: A new notification is posted - val fakeSummary = NotificationEntryBuilder().build() - val fakeChild = - NotificationEntryBuilder() - .setGroup(context, "group") - .setGroupSummary(context, false) - .build() - GroupEntryBuilder().setSummary(fakeSummary).addChild(fakeChild).build() - - collectionListener.onEntryAdded(fakeSummary) - collectionListener.onEntryAdded(fakeChild) - - // WHEN: Keyguard is now showing, both notifications are marked as seen - keyguardRepository.setKeyguardShowing(true) - testScheduler.runCurrent() - - // WHEN: The child notification is now unseen - collectionListener.onEntryUpdated(fakeChild) - - // THEN: The summary is not filtered out, because the child is unseen - assertThat(unseenFilter.shouldFilterOut(fakeSummary, 0L)).isFalse() - } - } - - @Test - fun unseenNotificationIsMarkedAsSeenWhenKeyguardGoesAway() { - // GIVEN: Keyguard is showing, not dozing, unseen notification is present - keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setIsDozing(false) - runKeyguardCoordinatorTest { - val fakeEntry = NotificationEntryBuilder().build() - collectionListener.onEntryAdded(fakeEntry) - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.AOD, - to = KeyguardState.LOCKSCREEN, - this.testScheduler, - ) - testScheduler.runCurrent() - - // WHEN: five seconds have passed - testScheduler.advanceTimeBy(5.seconds) - testScheduler.runCurrent() - - // WHEN: Keyguard is no longer showing - keyguardRepository.setKeyguardShowing(false) - kosmos.setTransition( - sceneTransition = Idle(Scenes.Gone), - stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE) - ) - - // WHEN: Keyguard is shown again - keyguardRepository.setKeyguardShowing(true) - kosmos.setTransition( - sceneTransition = Idle(Scenes.Lockscreen), - stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD) - ) - - // THEN: The notification is now recognized as "seen" and is filtered out. - assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue() - } - } - - @Test - fun unseenNotificationIsNotMarkedAsSeenIfShadeNotExpanded() { - // GIVEN: Keyguard is showing, unseen notification is present - keyguardRepository.setKeyguardShowing(true) - runKeyguardCoordinatorTest { - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.GONE, - to = KeyguardState.LOCKSCREEN, - this.testScheduler, - ) - val fakeEntry = NotificationEntryBuilder().build() - collectionListener.onEntryAdded(fakeEntry) - - // WHEN: Keyguard is no longer showing - keyguardRepository.setKeyguardShowing(false) - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.GONE, - this.testScheduler, - ) - - // WHEN: Keyguard is shown again - keyguardRepository.setKeyguardShowing(true) - testScheduler.runCurrent() - - // THEN: The notification is not recognized as "seen" and is not filtered out. - assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() - } - } - - @Test - fun unseenNotificationIsNotMarkedAsSeenIfNotOnKeyguardLongEnough() { - // GIVEN: Keyguard is showing, not dozing, unseen notification is present - keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setIsDozing(false) - runKeyguardCoordinatorTest { - kosmos.setTransition( - sceneTransition = Idle(Scenes.Lockscreen), - stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.LOCKSCREEN) - ) - val firstEntry = NotificationEntryBuilder().setId(1).build() - collectionListener.onEntryAdded(firstEntry) - testScheduler.runCurrent() - - // WHEN: one second has passed - testScheduler.advanceTimeBy(1.seconds) - testScheduler.runCurrent() - - // WHEN: another unseen notification is posted - val secondEntry = NotificationEntryBuilder().setId(2).build() - collectionListener.onEntryAdded(secondEntry) - testScheduler.runCurrent() - - // WHEN: four more seconds have passed - testScheduler.advanceTimeBy(4.seconds) - testScheduler.runCurrent() - - // WHEN: the keyguard is no longer showing - keyguardRepository.setKeyguardShowing(false) - kosmos.setTransition( - sceneTransition = Idle(Scenes.Gone), - stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE) - ) - - // WHEN: Keyguard is shown again - keyguardRepository.setKeyguardShowing(true) - kosmos.setTransition( - sceneTransition = Idle(Scenes.Lockscreen), - stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.LOCKSCREEN) - ) - - // THEN: The first notification is considered seen and is filtered out. - assertThat(unseenFilter.shouldFilterOut(firstEntry, 0L)).isTrue() - - // THEN: The second notification is still considered unseen and is not filtered out - assertThat(unseenFilter.shouldFilterOut(secondEntry, 0L)).isFalse() - } - } - - @Test - fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedAfterThreshold() { - // GIVEN: Keyguard is showing, not dozing - keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setIsDozing(false) - runKeyguardCoordinatorTest { - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.GONE, - to = KeyguardState.LOCKSCREEN, - this.testScheduler, - ) - testScheduler.runCurrent() - - // WHEN: a new notification is posted - val entry = NotificationEntryBuilder().setId(1).build() - collectionListener.onEntryAdded(entry) - testScheduler.runCurrent() - - // WHEN: five more seconds have passed - testScheduler.advanceTimeBy(5.seconds) - testScheduler.runCurrent() - - // WHEN: the notification is removed - collectionListener.onEntryRemoved(entry, 0) - testScheduler.runCurrent() - - // WHEN: the notification is re-posted - collectionListener.onEntryAdded(entry) - testScheduler.runCurrent() - - // WHEN: one more second has passed - testScheduler.advanceTimeBy(1.seconds) - testScheduler.runCurrent() - - // WHEN: the keyguard is no longer showing - keyguardRepository.setKeyguardShowing(false) - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.GONE, - this.testScheduler, - ) - testScheduler.runCurrent() - - // WHEN: Keyguard is shown again - keyguardRepository.setKeyguardShowing(true) - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.GONE, - to = KeyguardState.LOCKSCREEN, - this.testScheduler, - ) - testScheduler.runCurrent() - - // THEN: The notification is considered unseen and is not filtered out. - assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse() - } - } - - @Test - fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedBeforeThreshold() { - // GIVEN: Keyguard is showing, not dozing - keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setIsDozing(false) - runKeyguardCoordinatorTest { - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.GONE, - to = KeyguardState.LOCKSCREEN, - this.testScheduler, - ) - testScheduler.runCurrent() - - // WHEN: a new notification is posted - val entry = NotificationEntryBuilder().setId(1).build() - collectionListener.onEntryAdded(entry) - testScheduler.runCurrent() - - // WHEN: one second has passed - testScheduler.advanceTimeBy(1.seconds) - testScheduler.runCurrent() - - // WHEN: the notification is removed - collectionListener.onEntryRemoved(entry, 0) - testScheduler.runCurrent() - - // WHEN: the notification is re-posted - collectionListener.onEntryAdded(entry) - testScheduler.runCurrent() - - // WHEN: one more second has passed - testScheduler.advanceTimeBy(1.seconds) - testScheduler.runCurrent() - - // WHEN: the keyguard is no longer showing - keyguardRepository.setKeyguardShowing(false) - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.GONE, - this.testScheduler, - ) - testScheduler.runCurrent() - - // WHEN: Keyguard is shown again - keyguardRepository.setKeyguardShowing(true) - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.GONE, - to = KeyguardState.LOCKSCREEN, - this.testScheduler, - ) - testScheduler.runCurrent() - - // THEN: The notification is considered unseen and is not filtered out. - assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse() - } - } - - @Test - fun unseenNotificationOnKeyguardNotMarkedAsSeenIfUpdatedBeforeThreshold() { - // GIVEN: Keyguard is showing, not dozing - keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setIsDozing(false) - runKeyguardCoordinatorTest { - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.GONE, - to = KeyguardState.LOCKSCREEN, - this.testScheduler, - ) - testScheduler.runCurrent() - - // WHEN: a new notification is posted - val entry = NotificationEntryBuilder().setId(1).build() - collectionListener.onEntryAdded(entry) - testScheduler.runCurrent() - - // WHEN: one second has passed - testScheduler.advanceTimeBy(1.seconds) - testScheduler.runCurrent() - - // WHEN: the notification is updated - collectionListener.onEntryUpdated(entry) - testScheduler.runCurrent() - - // WHEN: four more seconds have passed - testScheduler.advanceTimeBy(4.seconds) - testScheduler.runCurrent() - - // WHEN: the keyguard is no longer showing - keyguardRepository.setKeyguardShowing(false) - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.GONE, - this.testScheduler, - ) - testScheduler.runCurrent() - - // WHEN: Keyguard is shown again - keyguardRepository.setKeyguardShowing(true) - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.GONE, - to = KeyguardState.LOCKSCREEN, - this.testScheduler, - ) - testScheduler.runCurrent() - - // THEN: The notification is considered unseen and is not filtered out. - assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse() - } - } - - private fun runKeyguardCoordinatorTest( - testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit - ) { - val testDispatcher = UnconfinedTestDispatcher() - val testScope = TestScope(testDispatcher) - val fakeSettings = - FakeSettings().apply { - putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1) - } - val seenNotificationsInteractor = - SeenNotificationsInteractor(ActiveNotificationListRepository()) - val keyguardCoordinator = - KeyguardCoordinator( - testDispatcher, - mock<DumpManager>(), - headsUpManager, - keyguardNotifVisibilityProvider, - keyguardRepository, - kosmos.keyguardTransitionInteractor, - KeyguardCoordinatorLogger(logcatLogBuffer()), - testScope.backgroundScope, - sectionHeaderVisibilityProvider, - fakeSettings, - seenNotificationsInteractor, - statusBarStateController, - ) - keyguardCoordinator.attach(notifPipeline) - testScope.runTest(dispatchTimeoutMs = 1.seconds.inWholeMilliseconds) { - KeyguardCoordinatorTestScope( - keyguardCoordinator, - testScope, - seenNotificationsInteractor, - fakeSettings, - ) - .testBlock() - } - } - - private inner class KeyguardCoordinatorTestScope( - private val keyguardCoordinator: KeyguardCoordinator, - private val scope: TestScope, - val seenNotificationsInteractor: SeenNotificationsInteractor, - private val fakeSettings: FakeSettings, - ) : CoroutineScope by scope { - val testScheduler: TestCoroutineScheduler - get() = scope.testScheduler - - val onStateChangeListener: Consumer<String> = withArgCaptor { - verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture()) - } - - val unseenFilter: NotifFilter - get() = keyguardCoordinator.unseenNotifFilter - - val collectionListener: NotifCollectionListener = withArgCaptor { - verify(notifPipeline).addCollectionListener(capture()) - } - - val onHeadsUpChangedListener: OnHeadsUpChangedListener - get() = withArgCaptor { verify(headsUpManager).addListener(capture()) } - - val statusBarStateListener: StatusBarStateController.StateListener - get() = withArgCaptor { verify(statusBarStateController).addCallback(capture()) } - - var showOnlyUnseenNotifsOnKeyguardSetting: Boolean - get() = - fakeSettings.getIntForUser( - Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, - UserHandle.USER_CURRENT, - ) == 1 - set(value) { - fakeSettings.putIntForUser( - Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, - if (value) 1 else 2, - UserHandle.USER_CURRENT, - ) - } - } - - companion object { - @JvmStatic - @Parameters(name = "{0}") - fun getParams(): List<FlagsParameterization> { - return FlagsParameterization.allCombinationsOf().andSceneContainer() - } - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt index c2a7b52d9f19..b7ebebec7ca7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt @@ -126,6 +126,19 @@ class StackScrollAlgorithmTest : SysuiTestCase() { } @Test + @EnableSceneContainer + fun resetViewStates_defaultHun_yTranslationIsHeadsUpTop() { + val headsUpTop = 200f + ambientState.headsUpTop = headsUpTop + + whenever(notificationRow.isPinned).thenReturn(true) + whenever(notificationRow.isHeadsUp).thenReturn(true) + + resetViewStates_hunYTranslationIs(headsUpTop) + } + + @Test + @DisableSceneContainer fun resetViewStates_defaultHun_yTranslationIsInset() { whenever(notificationRow.isPinned).thenReturn(true) whenever(notificationRow.isHeadsUp).thenReturn(true) @@ -133,6 +146,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun resetViewStates_defaultHunWithStackMargin_changesHunYTranslation() { whenever(notificationRow.isPinned).thenReturn(true) whenever(notificationRow.isHeadsUp).thenReturn(true) @@ -140,7 +154,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { } @Test - @DisableSceneContainer // TODO(b/332574413) cover hun bounds integration with tests + @DisableSceneContainer fun resetViewStates_defaultHunWhenShadeIsOpening_yTranslationIsInset() { whenever(notificationRow.isPinned).thenReturn(true) whenever(notificationRow.isHeadsUp).thenReturn(true) @@ -153,6 +167,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { } @Test + @DisableSceneContainer @DisableFlags(NotificationsImprovedHunAnimation.FLAG_NAME) fun resetViewStates_hunAnimatingAway_yTranslationIsInset() { whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true) @@ -160,6 +175,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { } @Test + @DisableSceneContainer @DisableFlags(NotificationsImprovedHunAnimation.FLAG_NAME) fun resetViewStates_hunAnimatingAway_StackMarginChangesHunYTranslation() { whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true) @@ -167,6 +183,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { } @Test + @DisableSceneContainer @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME) fun resetViewStates_defaultHun_newHeadsUpAnim_yTranslationIsInset() { whenever(notificationRow.isPinned).thenReturn(true) @@ -175,6 +192,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { } @Test + @DisableSceneContainer @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME) fun resetViewStates_defaultHunWithStackMargin_newHeadsUpAnim_changesHunYTranslation() { whenever(notificationRow.isPinned).thenReturn(true) @@ -183,7 +201,98 @@ class StackScrollAlgorithmTest : SysuiTestCase() { } @Test - @DisableSceneContainer // TODO(b/332574413) cover hun bounds integration with tests + @EnableSceneContainer + fun resetViewStates_defaultHunInShade_stackTopEqualsHunTop_hunHasFullHeight() { + // Given: headsUpTop == stackTop -> haven't scrolled the stack yet + val headsUpTop = 150f + val collapsedHeight = 100 + val intrinsicHeight = 300 + fakeHunInShade( + headsUpTop = headsUpTop, + stackTop = headsUpTop, + collapsedHeight = collapsedHeight, + intrinsicHeight = intrinsicHeight, + ) + + // When + stackScrollAlgorithm.resetViewStates(ambientState, 0) + + // Then: HUN is at the headsUpTop + assertThat(notificationRow.viewState.yTranslation).isEqualTo(headsUpTop) + // And: HUN has its full height + assertThat(notificationRow.viewState.height).isEqualTo(intrinsicHeight) + } + + @Test + @EnableSceneContainer + fun resetViewStates_defaultHunInShade_stackTopGreaterThanHeadsUpTop_hunClampedToHeadsUpTop() { + // Given: headsUpTop < stackTop -> scrolled the stack a little bit + val stackTop = -25f + val headsUpTop = 150f + val collapsedHeight = 100 + val intrinsicHeight = 300 + fakeHunInShade( + headsUpTop = headsUpTop, + stackTop = stackTop, + collapsedHeight = collapsedHeight, + intrinsicHeight = intrinsicHeight + ) + + // When + stackScrollAlgorithm.resetViewStates(ambientState, 0) + + // Then: HUN is translated to the headsUpTop + assertThat(notificationRow.viewState.yTranslation).isEqualTo(headsUpTop) + // And: HUN is clipped to the available space + // newTranslation = max(150, -25) + // distToReal = 150 - (-25) + // height = max(300 - 175, 100) + assertThat(notificationRow.viewState.height).isEqualTo(125) + } + + @Test + @EnableSceneContainer + fun resetViewStates_defaultHunInShade_stackOverscrolledHun_hunClampedToHeadsUpTop() { + // Given: headsUpTop << stackTop -> stack has fully overscrolled the HUN + val stackTop = -500f + val headsUpTop = 150f + val collapsedHeight = 100 + val intrinsicHeight = 300 + fakeHunInShade( + headsUpTop = headsUpTop, + stackTop = stackTop, + collapsedHeight = collapsedHeight, + intrinsicHeight = intrinsicHeight + ) + + // When + stackScrollAlgorithm.resetViewStates(ambientState, 0) + + // Then: HUN is translated to the headsUpTop + assertThat(notificationRow.viewState.yTranslation).isEqualTo(headsUpTop) + // And: HUN is clipped to its collapsed height + assertThat(notificationRow.viewState.height).isEqualTo(collapsedHeight) + } + + @Test + @EnableSceneContainer + fun resetViewStates_defaultHun_showingQS_hunTranslatedToHeadsUpTop() { + // Given: the shade is open and scrolled to the bottom to show the QuickSettings + val headsUpTop = 2000f + fakeHunInShade( + headsUpTop = headsUpTop, + stackTop = 2600f, // stack scrolled below the screen + stackCutoff = 4000f, + collapsedHeight = 100, + intrinsicHeight = 300 + ) + whenever(notificationRow.isAboveShelf).thenReturn(true) + + resetViewStates_hunYTranslationIs(headsUpTop) + } + + @Test + @DisableSceneContainer @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME) fun resetViewStates_defaultHun_showingQS_newHeadsUpAnim_hunTranslatedToMax() { // Given: the shade is open and scrolled to the bottom to show the QuickSettings @@ -200,7 +309,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { } @Test - @DisableSceneContainer // TODO(b/332574413) cover hun bounds integration with tests + @DisableSceneContainer @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME) fun resetViewStates_hunAnimatingAway_showingQS_newHeadsUpAnim_hunTranslatedToBottomOfScreen() { // Given: the shade is open and scrolled to the bottom to show the QuickSettings @@ -245,6 +354,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { } @Test + @DisableSceneContainer @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME) fun resetViewStates_hunAnimatingAwayWhileDozing_yTranslationIsInset() { whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true) @@ -255,6 +365,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { } @Test + @DisableSceneContainer @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME) fun resetViewStates_hunAnimatingAwayWhileDozing_hasStackMargin_changesHunYTranslation() { whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true) @@ -744,8 +855,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { expandableViewState.yTranslation = 50f stackScrollAlgorithm.clampHunToTop( - /* quickQsOffsetHeight= */ 10f, - /* stackTranslation= */ 0f, + /* headsUpTop= */ 10f, /* collapsedHeight= */ 1f, expandableViewState ) @@ -760,8 +870,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { expandableViewState.yTranslation = -10f stackScrollAlgorithm.clampHunToTop( - /* quickQsOffsetHeight= */ 10f, - /* stackTranslation= */ 0f, + /* headsUpTop= */ 10f, /* collapsedHeight= */ 1f, expandableViewState ) @@ -777,8 +886,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { expandableViewState.yTranslation = -100f stackScrollAlgorithm.clampHunToTop( - /* quickQsOffsetHeight= */ 10f, - /* stackTranslation= */ 0f, + /* headsUpTop= */ 10f, /* collapsedHeight= */ 10f, expandableViewState ) @@ -796,8 +904,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { expandableViewState.yTranslation = 5f stackScrollAlgorithm.clampHunToTop( - /* quickQsOffsetHeight= */ 10f, - /* stackTranslation= */ 0f, + /* headsUpTop= */ 10f, /* collapsedHeight= */ 10f, expandableViewState ) @@ -1309,6 +1416,33 @@ class StackScrollAlgorithmTest : SysuiTestCase() { expect.that(notificationRow.viewState.alpha).isEqualTo(expectedAlpha) } + + /** fakes the notification row under test, to be a HUN in a fully opened shade */ + private fun fakeHunInShade( + headsUpTop: Float, + collapsedHeight: Int, + intrinsicHeight: Int, + stackTop: Float, + stackCutoff: Float = 2000f, + fullStackHeight: Float = 3000f + ) { + ambientState.headsUpTop = headsUpTop + ambientState.stackTop = stackTop + ambientState.stackCutoff = stackCutoff + + // shade is fully open + ambientState.expansionFraction = 1.0f + with(fullStackHeight) { + ambientState.stackHeight = this + ambientState.stackEndHeight = this + } + stackScrollAlgorithm.setIsExpanded(true) + + whenever(notificationRow.mustStayOnScreen()).thenReturn(true) + whenever(notificationRow.isHeadsUp).thenReturn(true) + whenever(notificationRow.collapsedHeight).thenReturn(collapsedHeight) + whenever(notificationRow.intrinsicHeight).thenReturn(intrinsicHeight) + } } private fun mockExpandableNotificationRow(): ExpandableNotificationRow { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java index fea0e72fe577..8dfbb37f8189 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java @@ -118,8 +118,8 @@ public class LegacyNotificationIconAreaControllerImplTest extends SysuiTestCase } @Test + @DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testAppearResetsTranslation() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); mController.setupAodIcons(mAodIcons); when(mDozeParameters.shouldControlScreenOff()).thenReturn(false); mController.appearAodIcons(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 0e4d892438ba..49e3f04cb44e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -116,6 +116,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Answers; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; @@ -176,6 +177,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { private ViewRootImpl mViewRootImpl; @Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private KeyguardTransitionInteractor mKeyguardTransitionInteractor; @Captor private ArgumentCaptor<OnBackInvokedCallback> mBackCallbackCaptor; @Captor @@ -223,7 +226,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mAlternateBouncerInteractor, mUdfpsOverlayInteractor, mActivityStarter, - mock(KeyguardTransitionInteractor.class), + mKeyguardTransitionInteractor, StandardTestDispatcher(null, null), () -> mock(WindowManagerLockscreenVisibilityInteractor.class), () -> mock(KeyguardDismissActionInteractor.class), @@ -1066,6 +1069,22 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Test @DisableSceneContainer + @EnableFlags(Flags.FLAG_SIM_PIN_RACE_CONDITION_ON_RESTART) + public void testShowBouncerOrKeyguard_showsKeyguardIfShowBouncerReturnsFalse() { + when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn( + KeyguardSecurityModel.SecurityMode.SimPin); + when(mPrimaryBouncerInteractor.show(true)).thenReturn(false); + when(mKeyguardTransitionInteractor.getTransitionState().getValue().getTo()) + .thenReturn(KeyguardState.LOCKSCREEN); + + reset(mCentralSurfaces); + mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false); + verify(mPrimaryBouncerInteractor).show(true); + verify(mCentralSurfaces).showKeyguard(); + } + + @Test + @DisableSceneContainer public void testShowBouncerOrKeyguard_needsFullScreen_bouncerAlreadyShowing() { when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn( KeyguardSecurityModel.SecurityMode.SimPin); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index 01540e7584a3..58ad83546e01 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -536,7 +536,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { // WHEN there's *no* ongoing activity via new callback mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( - /* hasOngoingActivity= */ false); + /* hasOngoingActivity= */ false, /* shouldAnimate= */ false); // THEN the old callback value is used, so the view is shown assertEquals(View.VISIBLE, @@ -548,7 +548,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { // WHEN there *is* an ongoing activity via new callback mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( - /* hasOngoingActivity= */ true); + /* hasOngoingActivity= */ true, /* shouldAnimate= */ false); // THEN the old callback value is used, so the view is hidden assertEquals(View.GONE, @@ -565,7 +565,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { // listener, but I'm unable to get the fragment to get attached so that the binder starts // listening to flows. mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( - /* hasOngoingActivity= */ false); + /* hasOngoingActivity= */ false, /* shouldAnimate= */ false); assertEquals(View.GONE, mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility()); @@ -577,7 +577,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { resumeAndGetFragment(); mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( - /* hasOngoingActivity= */ true); + /* hasOngoingActivity= */ true, /* shouldAnimate= */ false); assertEquals(View.VISIBLE, mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility()); @@ -590,7 +590,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( - /* hasOngoingActivity= */ true); + /* hasOngoingActivity= */ true, /* shouldAnimate= */ false); fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false); @@ -605,7 +605,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( - /* hasOngoingActivity= */ true); + /* hasOngoingActivity= */ true, /* shouldAnimate= */ false); when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); @@ -621,14 +621,14 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { // Ongoing activity started mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( - /* hasOngoingActivity= */ true); + /* hasOngoingActivity= */ true, /* shouldAnimate= */ false); assertEquals(View.VISIBLE, mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility()); // Ongoing activity ended mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( - /* hasOngoingActivity= */ false); + /* hasOngoingActivity= */ false, /* shouldAnimate= */ false); assertEquals(View.GONE, mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility()); @@ -643,7 +643,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { // Ongoing call started mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( - /* hasOngoingActivity= */ true); + /* hasOngoingActivity= */ true, /* shouldAnimate= */ false); // Notification area is hidden without delay assertEquals(0f, getNotificationAreaView().getAlpha(), 0.01); @@ -661,7 +661,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { // WHEN there's *no* ongoing activity via new callback mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( - /* hasOngoingActivity= */ false); + /* hasOngoingActivity= */ false, /* shouldAnimate= */ false); // THEN the new callback value is used, so the view is hidden assertEquals(View.GONE, @@ -673,7 +673,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { // WHEN there *is* an ongoing activity via new callback mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( - /* hasOngoingActivity= */ true); + /* hasOngoingActivity= */ true, /* shouldAnimate= */ false); // THEN the new callback value is used, so the view is shown assertEquals(View.VISIBLE, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt index 94159bcebf47..60750cf96e67 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt @@ -425,7 +425,7 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.DoingNothing - assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) kosmos.fakeMediaProjectionRepository.mediaProjectionState.value = MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt index d3f11253fc09..cefdf7e43fae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt @@ -29,7 +29,7 @@ class FakeCollapsedStatusBarViewModel : CollapsedStatusBarViewModel { override val transitionFromLockscreenToDreamStartedEvent = MutableSharedFlow<Unit>() override val ongoingActivityChip: MutableStateFlow<OngoingActivityChipModel> = - MutableStateFlow(OngoingActivityChipModel.Hidden) + MutableStateFlow(OngoingActivityChipModel.Hidden()) override val isHomeStatusBarAllowedByScene = MutableStateFlow(false) 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 ac42319c7b25..60b5b5d39b9b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -86,6 +86,7 @@ import android.service.dreams.IDreamManager; import android.service.notification.NotificationListenerService; import android.service.notification.ZenModeConfig; import android.testing.TestableLooper; +import android.util.Log; import android.util.Pair; import android.util.SparseArray; import android.view.Display; @@ -183,6 +184,7 @@ import com.android.wm.shell.common.bubbles.BubbleBarLocation; import com.android.wm.shell.common.bubbles.BubbleBarUpdate; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.onehanded.OneHandedController; +import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; @@ -192,7 +194,6 @@ import com.android.wm.shell.transition.Transitions; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -216,6 +217,9 @@ import platform.test.runner.parameterized.Parameters; @RunWith(ParameterizedAndroidJunit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) public class BubblesTest extends SysuiTestCase { + + private static final String TAG = "BubblesTest"; + @Mock private CommonNotifCollection mCommonNotifCollection; @Mock @@ -241,8 +245,6 @@ public class BubblesTest extends SysuiTestCase { @Mock private KeyguardBypassController mKeyguardBypassController; @Mock - private FloatingContentCoordinator mFloatingContentCoordinator; - @Mock private BubbleDataRepository mDataRepository; @Mock private NotificationShadeWindowView mNotificationShadeWindowView; @@ -372,6 +374,7 @@ public class BubblesTest extends SysuiTestCase { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + PhysicsAnimatorTestUtils.prepareForTest(); if (Transitions.ENABLE_SHELL_TRANSITIONS) { doReturn(true).when(mTransitions).isRegistered(); @@ -494,7 +497,7 @@ public class BubblesTest extends SysuiTestCase { mShellCommandHandler, mShellController, mBubbleData, - mFloatingContentCoordinator, + new FloatingContentCoordinator(), mDataRepository, mStatusBarService, mWindowManager, @@ -571,12 +574,32 @@ public class BubblesTest extends SysuiTestCase { } @After - public void tearDown() { + public void tearDown() throws Exception { ArrayList<Bubble> bubbles = new ArrayList<>(mBubbleData.getBubbles()); for (int i = 0; i < bubbles.size(); i++) { mBubbleController.removeBubble(bubbles.get(i).getKey(), Bubbles.DISMISS_NO_LONGER_BUBBLE); } + mTestableLooper.processAllMessages(); + + // check that no animations are running before finishing the test to make sure that the + // state gets cleaned up correctly between tests. + int retryCount = 0; + while (PhysicsAnimatorTestUtils.isAnyAnimationRunning() && retryCount <= 10) { + Log.d( + TAG, + String.format("waiting for animations to complete. attempt %d", retryCount)); + // post a message to the looper and wait for it to be processed + mTestableLooper.runWithLooper(() -> {}); + retryCount++; + } + mTestableLooper.processAllMessages(); + if (PhysicsAnimatorTestUtils.isAnyAnimationRunning()) { + Log.d(TAG, "finished waiting for animations to complete but animations are still " + + "running"); + } else { + Log.d(TAG, "no animations are running"); + } } @Test @@ -1853,7 +1876,6 @@ public class BubblesTest extends SysuiTestCase { any(Bubble.class), anyBoolean(), anyBoolean()); } - @Ignore("reason = b/351977103") @Test public void testShowStackEdu_isNotConversationBubble() { // Setup diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt index 3d85a4abbd68..c7dfd5cc93b9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt @@ -17,6 +17,8 @@ package com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel import android.content.applicationContext +import com.android.systemui.animation.dialogTransitionAnimator +import com.android.systemui.animation.mockDialogTransitionAnimator import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.statusbar.chips.casttootherdevice.domain.interactor.mediaRouterChipInteractor @@ -34,6 +36,7 @@ val Kosmos.castToOtherDeviceChipViewModel: CastToOtherDeviceChipViewModel by mediaRouterChipInteractor = mediaRouterChipInteractor, systemClock = fakeSystemClock, endMediaProjectionDialogHelper = endMediaProjectionDialogHelper, + dialogTransitionAnimator = mockDialogTransitionAnimator, logger = statusBarChipsLogger, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt index 1ed7a4702e2c..651a0f7639d8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.chips.mediaprojection.ui.view import android.content.packageManager +import com.android.systemui.animation.mockDialogTransitionAnimator import com.android.systemui.kosmos.Kosmos import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory @@ -24,6 +25,7 @@ val Kosmos.endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper by Kosmos.Fixture { EndMediaProjectionDialogHelper( dialogFactory = mockSystemUIDialogFactory, + dialogTransitionAnimator = mockDialogTransitionAnimator, packageManager = packageManager, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt index e4bb1665a432..c2a6f7d91eb0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt @@ -17,10 +17,12 @@ package com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel import android.content.applicationContext +import com.android.systemui.animation.mockDialogTransitionAnimator import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.statusbar.chips.mediaprojection.ui.view.endMediaProjectionDialogHelper import com.android.systemui.statusbar.chips.screenrecord.domain.interactor.screenRecordChipInteractor +import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.shareToAppChipViewModel import com.android.systemui.statusbar.chips.statusBarChipsLogger import com.android.systemui.util.time.fakeSystemClock @@ -30,7 +32,9 @@ val Kosmos.screenRecordChipViewModel: ScreenRecordChipViewModel by scope = applicationCoroutineScope, context = applicationContext, interactor = screenRecordChipInteractor, + shareToAppChipViewModel = shareToAppChipViewModel, endMediaProjectionDialogHelper = endMediaProjectionDialogHelper, + dialogTransitionAnimator = mockDialogTransitionAnimator, systemClock = fakeSystemClock, logger = statusBarChipsLogger, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt index 8ed7f9684d86..0770009f9998 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel import android.content.applicationContext +import com.android.systemui.animation.mockDialogTransitionAnimator import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.mediaProjectionChipInteractor @@ -32,6 +33,7 @@ val Kosmos.shareToAppChipViewModel: ShareToAppChipViewModel by mediaProjectionChipInteractor = mediaProjectionChipInteractor, systemClock = fakeSystemClock, endMediaProjectionDialogHelper = endMediaProjectionDialogHelper, + dialogTransitionAnimator = mockDialogTransitionAnimator, logger = statusBarChipsLogger, ) } diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java index 23c008eb7f67..fc00987dc38a 100644 --- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java +++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java @@ -74,6 +74,18 @@ public abstract class UsageStatsManagerInternal { public abstract void reportEvent(String packageName, @UserIdInt int userId, int eventType); /** + * Reports an event to the UsageStatsManager for all users. <br/> + * <em>Note: Starting from {@link android.os.Build.VERSION_CODES#R Android R}, if the user's + * device is not in an unlocked state (as defined by {@link UserManager#isUserUnlocked()}), + * then this event will be added to a queue and processed once the device is unlocked.</em> + * + * @param packageName The package for which this event occurred. + * @param eventType The event that occurred. Valid values can be found at + * {@link UsageEvents} + */ + public abstract void reportEventForAllUsers(String packageName, int eventType); + + /** * Reports a configuration change to the UsageStatsManager. <br/> * <em>Note: Starting from {@link android.os.Build.VERSION_CODES#R Android R}, if the user's * device is not in an unlocked state (as defined by {@link UserManager#isUserUnlocked()}), diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 67985efcd7bc..092ee16f3342 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -102,6 +102,7 @@ import android.util.StatsEvent; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.os.BinderCallsStats; import com.android.internal.os.Clock; @@ -1191,7 +1192,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub .setMinConsumedPowerThreshold(minConsumedPowerThreshold) .build(); bus = getBatteryUsageStats(List.of(query)).get(0); - return StatsPerUidLogger.logStats(bus, data); + return new StatsPerUidLogger(new FrameworkStatsLogger()).logStats(bus, data); } default: throw new UnsupportedOperationException("Unknown tagId=" + atomTag); @@ -1204,7 +1205,35 @@ public final class BatteryStatsService extends IBatteryStats.Stub } } - private static class StatsPerUidLogger { + public static class FrameworkStatsLogger { + /** + * Wrapper for the FrameworkStatsLog.buildStatsEvent method that makes it easier + * for mocking. + */ + @VisibleForTesting + public StatsEvent buildStatsEvent(long sessionStartTs, long sessionEndTs, + long sessionDuration, int sessionDischargePercentage, long sessionDischargeDuration, + int uid, @BatteryConsumer.ProcessState int processState, long timeInStateMillis, + String powerComponentName, float totalConsumedPowerMah, float powerComponentMah, + long powerComponentDurationMillis) { + return FrameworkStatsLog.buildStatsEvent( + FrameworkStatsLog.BATTERY_USAGE_STATS_PER_UID, + sessionStartTs, + sessionEndTs, + sessionDuration, + sessionDischargePercentage, + sessionDischargeDuration, + uid, + processState, + timeInStateMillis, + powerComponentName, + totalConsumedPowerMah, + powerComponentMah, + powerComponentDurationMillis); + } + } + + public static class StatsPerUidLogger { private static final int STATSD_METRIC_MAX_DIMENSIONS_COUNT = 3000; @@ -1224,7 +1253,18 @@ public final class BatteryStatsService extends IBatteryStats.Stub long dischargeDuration) {} ; - static int logStats(BatteryUsageStats bus, List<StatsEvent> data) { + private final FrameworkStatsLogger mFrameworkStatsLogger; + + public StatsPerUidLogger(FrameworkStatsLogger frameworkStatsLogger) { + mFrameworkStatsLogger = frameworkStatsLogger; + } + + /** + * Generates StatsEvents for the supplied battery usage stats and adds them to + * the supplied list. + */ + @VisibleForTesting + public int logStats(BatteryUsageStats bus, List<StatsEvent> data) { final SessionInfo sessionInfo = new SessionInfo( bus.getStatsStartTimestamp(), @@ -1340,7 +1380,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub return StatsManager.PULL_SUCCESS; } - private static boolean addStatsForPredefinedComponent( + private boolean addStatsForPredefinedComponent( List<StatsEvent> data, SessionInfo sessionInfo, int uid, @@ -1380,7 +1420,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub powerComponentDurationMillis); } - private static boolean addStatsForCustomComponent( + private boolean addStatsForCustomComponent( List<StatsEvent> data, SessionInfo sessionInfo, int uid, @@ -1422,7 +1462,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub * Returns true on success and false if reached max atoms capacity and no more atoms should * be added */ - private static boolean addStatsAtom( + private boolean addStatsAtom( List<StatsEvent> data, SessionInfo sessionInfo, int uid, @@ -1432,9 +1472,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub float totalConsumedPowerMah, float powerComponentMah, long powerComponentDurationMillis) { - data.add( - FrameworkStatsLog.buildStatsEvent( - FrameworkStatsLog.BATTERY_USAGE_STATS_PER_UID, + data.add(mFrameworkStatsLogger.buildStatsEvent( sessionInfo.startTs(), sessionInfo.endTs(), sessionInfo.duration(), diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index aeebae43d1ef..a7b2eb155558 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -2135,8 +2135,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue { final boolean targetedBroadcast = r.intent.getComponent() != null; final boolean targetedSelf = Objects.equals(r.callerPackage, receiverPackageName); if (targetedBroadcast && !targetedSelf) { - mService.mUsageStatsService.reportEvent(receiverPackageName, - r.userId, Event.APP_COMPONENT_USED); + if (r.userId == UserHandle.USER_ALL) { + mService.mUsageStatsService.reportEventForAllUsers( + receiverPackageName, Event.APP_COMPONENT_USED); + } else { + mService.mUsageStatsService.reportEvent(receiverPackageName, + r.userId, Event.APP_COMPONENT_USED); + } } mService.notifyPackageUse(receiverPackageName, diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 326ed69fa1dc..25b9228d3b37 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -1871,7 +1871,10 @@ public class AudioDeviceBroker { synchronized (mBluetoothAudioStateLock) { reapplyAudioHalBluetoothState(); } - mBtHelper.onAudioServerDiedRestoreA2dp(); + final int forceForMedia = getBluetoothA2dpEnabled() + ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP; + setForceUse_Async( + AudioSystem.FOR_MEDIA, forceForMedia, "MSG_RESTORE_DEVICES"); updateCommunicationRoute("MSG_RESTORE_DEVICES"); } } diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 02aa6f52ba8a..ca69f31adb35 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -1757,6 +1757,15 @@ public class AudioDeviceInventory { if (AudioService.DEBUG_DEVICES) { Slog.i(TAG, "deviceInfo:" + di + " is(already)Connected:" + isConnected); } + // Do not report an error in case of redundant connect or disconnect request + // as this can cause a state mismatch between BtHelper and AudioDeviceInventory + if (connect == isConnected) { + Log.i(TAG, "handleDeviceConnection() deviceInfo=" + di + " is already " + + (connect ? "" : "dis") + "connected"); + mmi.set(MediaMetrics.Property.STATE, connect + ? MediaMetrics.Value.CONNECT : MediaMetrics.Value.DISCONNECT).record(); + return true; + } if (connect && !isConnected) { final int res; if (isForTesting) { diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index 57bffa7e5b40..ce92dfbcc1c8 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -83,39 +83,53 @@ public class BtHelper { } // BluetoothHeadset API to control SCO connection + @GuardedBy("BtHelper.this") private @Nullable BluetoothHeadset mBluetoothHeadset; // Bluetooth headset device + @GuardedBy("mDeviceBroker.mDeviceStateLock") private @Nullable BluetoothDevice mBluetoothHeadsetDevice; + + @GuardedBy("mDeviceBroker.mDeviceStateLock") private final Map<BluetoothDevice, AudioDeviceAttributes> mResolvedScoAudioDevices = new HashMap<>(); + @GuardedBy("BtHelper.this") private @Nullable BluetoothHearingAid mHearingAid = null; + @GuardedBy("BtHelper.this") private @Nullable BluetoothLeAudio mLeAudio = null; + @GuardedBy("BtHelper.this") private @Nullable BluetoothLeAudioCodecConfig mLeAudioCodecConfig; // Reference to BluetoothA2dp to query for AbsoluteVolume. + @GuardedBy("BtHelper.this") private @Nullable BluetoothA2dp mA2dp = null; + @GuardedBy("BtHelper.this") private @Nullable BluetoothCodecConfig mA2dpCodecConfig; + @GuardedBy("BtHelper.this") private @AudioSystem.AudioFormatNativeEnumForBtCodec int mLeAudioBroadcastCodec = AudioSystem.AUDIO_FORMAT_DEFAULT; // If absolute volume is supported in AVRCP device + @GuardedBy("mDeviceBroker.mDeviceStateLock") private boolean mAvrcpAbsVolSupported = false; // Current connection state indicated by bluetooth headset + @GuardedBy("mDeviceBroker.mDeviceStateLock") private int mScoConnectionState; // Indicate if SCO audio connection is currently active and if the initiator is // audio service (internal) or bluetooth headset (external) + @GuardedBy("mDeviceBroker.mDeviceStateLock") private int mScoAudioState; // Indicates the mode used for SCO audio connection. The mode is virtual call if the request // originated from an app targeting an API version before JB MR2 and raw audio after that. + @GuardedBy("mDeviceBroker.mDeviceStateLock") private int mScoAudioMode; // SCO audio state is not active @@ -210,7 +224,7 @@ public class BtHelper { //---------------------------------------------------------------------- // Interface for AudioDeviceBroker - // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock + @GuardedBy("mDeviceBroker.mDeviceStateLock") /*package*/ synchronized void onSystemReady() { mScoConnectionState = android.media.AudioManager.SCO_AUDIO_STATE_ERROR; resetBluetoothSco(); @@ -238,17 +252,13 @@ public class BtHelper { } } - /*package*/ synchronized void onAudioServerDiedRestoreA2dp() { - final int forMed = mDeviceBroker.getBluetoothA2dpEnabled() - ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP; - mDeviceBroker.setForceUse_Async(AudioSystem.FOR_MEDIA, forMed, "onAudioServerDied()"); - } - - /*package*/ synchronized void setAvrcpAbsoluteVolumeSupported(boolean supported) { + @GuardedBy("mDeviceBroker.mDeviceStateLock") + /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) { mAvrcpAbsVolSupported = supported; Log.i(TAG, "setAvrcpAbsoluteVolumeSupported supported=" + supported); } + @GuardedBy("mDeviceBroker.mDeviceStateLock") /*package*/ synchronized void setAvrcpAbsoluteVolumeIndex(int index) { if (mA2dp == null) { if (AudioService.DEBUG_VOL) { @@ -371,7 +381,7 @@ public class BtHelper { return codecAndChanged; } - // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock + @GuardedBy("mDeviceBroker.mDeviceStateLock") /*package*/ synchronized void onReceiveBtEvent(Intent intent) { final String action = intent.getAction(); @@ -393,12 +403,12 @@ public class BtHelper { } /** - * Exclusively called from AudioDeviceBroker (with mSetModeLock held) + * Exclusively called from AudioDeviceBroker (with mDeviceStateLock held) * when handling MSG_L_RECEIVED_BT_EVENT in {@link #onReceiveBtEvent(Intent)} * as part of the serialization of the communication route selection */ - @GuardedBy("BtHelper.this") - private void onScoAudioStateChanged(int state) { + @GuardedBy("mDeviceBroker.mDeviceStateLock") + private synchronized void onScoAudioStateChanged(int state) { boolean broadcast = false; int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR; Log.i(TAG, "onScoAudioStateChanged state: " + state @@ -414,12 +424,14 @@ public class BtHelper { broadcast = true; } if (!mDeviceBroker.isScoManagedByAudio()) { - mDeviceBroker.setBluetoothScoOn(true, "BtHelper.onScoAudioStateChanged"); + mDeviceBroker.setBluetoothScoOn( + true, "BtHelper.onScoAudioStateChanged, state: " + state); } break; case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: if (!mDeviceBroker.isScoManagedByAudio()) { - mDeviceBroker.setBluetoothScoOn(false, "BtHelper.onScoAudioStateChanged"); + mDeviceBroker.setBluetoothScoOn( + false, "BtHelper.onScoAudioStateChanged, state: " + state); } scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED; // There are two cases where we want to immediately reconnect audio: @@ -466,6 +478,7 @@ public class BtHelper { * * @return false if SCO isn't connected */ + @GuardedBy("mDeviceBroker.mDeviceStateLock") /*package*/ synchronized boolean isBluetoothScoOn() { if (mBluetoothHeadset == null || mBluetoothHeadsetDevice == null) { return false; @@ -479,19 +492,20 @@ public class BtHelper { return false; } - /*package*/ synchronized boolean isBluetoothScoRequestedInternally() { + @GuardedBy("mDeviceBroker.mDeviceStateLock") + /*package*/ boolean isBluetoothScoRequestedInternally() { return mScoAudioState == SCO_STATE_ACTIVE_INTERNAL || mScoAudioState == SCO_STATE_ACTIVATE_REQ; } - // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock + @GuardedBy("mDeviceBroker.mDeviceStateLock") /*package*/ synchronized boolean startBluetoothSco(int scoAudioMode, @NonNull String eventSource) { AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(eventSource)); return requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode); } - // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock + @GuardedBy("mDeviceBroker.mDeviceStateLock") /*package*/ synchronized boolean stopBluetoothSco(@NonNull String eventSource) { AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(eventSource)); return requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, SCO_MODE_VIRTUAL_CALL); @@ -551,7 +565,8 @@ public class BtHelper { } } - /*package*/ synchronized void onBroadcastScoConnectionState(int state) { + @GuardedBy("mDeviceBroker.mDeviceStateLock") + /*package*/ void onBroadcastScoConnectionState(int state) { if (state == mScoConnectionState) { return; } @@ -563,8 +578,8 @@ public class BtHelper { mScoConnectionState = state; } - // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock - /*package*/ synchronized void resetBluetoothSco() { + @GuardedBy("mDeviceBroker.mDeviceStateLock") + /*package*/ void resetBluetoothSco() { mScoAudioState = SCO_STATE_INACTIVE; broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); mDeviceBroker.clearA2dpSuspended(false /* internalOnly */); @@ -572,7 +587,7 @@ public class BtHelper { mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco"); } - // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock + @GuardedBy("mDeviceBroker.mDeviceStateLock") /*package*/ synchronized void onBtProfileDisconnected(int profile) { AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( "BT profile " + BluetoothProfile.getProfileName(profile) @@ -634,9 +649,10 @@ public class BtHelper { } } + @GuardedBy("BtHelper.this") MyLeAudioCallback mLeAudioCallback = null; - // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock + @GuardedBy("mDeviceBroker.mDeviceStateLock") /*package*/ synchronized void onBtProfileConnected(int profile, BluetoothProfile proxy) { AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( "BT profile " + BluetoothProfile.getProfileName(profile) + " connected to proxy " @@ -773,8 +789,8 @@ public class BtHelper { } } - // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock - private void onHeadsetProfileConnected(@NonNull BluetoothHeadset headset) { + @GuardedBy("mDeviceBroker.mDeviceStateLock") + private synchronized void onHeadsetProfileConnected(@NonNull BluetoothHeadset headset) { // Discard timeout message mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService(); mBluetoothHeadset = headset; @@ -830,6 +846,7 @@ public class BtHelper { mDeviceBroker.postBroadcastScoConnectionState(state); } + @GuardedBy("mDeviceBroker.mDeviceStateLock") @Nullable AudioDeviceAttributes getHeadsetAudioDevice() { if (mBluetoothHeadsetDevice == null) { return null; @@ -837,6 +854,7 @@ public class BtHelper { return getHeadsetAudioDevice(mBluetoothHeadsetDevice); } + @GuardedBy("mDeviceBroker.mDeviceStateLock") private @NonNull AudioDeviceAttributes getHeadsetAudioDevice(BluetoothDevice btDevice) { AudioDeviceAttributes deviceAttr = mResolvedScoAudioDevices.get(btDevice); if (deviceAttr != null) { @@ -876,30 +894,44 @@ public class BtHelper { return new AudioDeviceAttributes(nativeType, address, name); } + @GuardedBy("mDeviceBroker.mDeviceStateLock") private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) { if (btDevice == null) { return true; } - int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET; - AudioDeviceAttributes audioDevice = btHeadsetDeviceToAudioDevice(btDevice); boolean result = false; + AudioDeviceAttributes audioDevice = null; // Only used if isActive is true + String address = btDevice.getAddress(); + String name = getName(btDevice); + // Handle output device if (isActive) { - result |= mDeviceBroker.handleDeviceConnection(audioDevice, isActive, btDevice); + audioDevice = btHeadsetDeviceToAudioDevice(btDevice); + result = mDeviceBroker.handleDeviceConnection( + audioDevice, true /*connect*/, btDevice); } else { - int[] outDeviceTypes = { + AudioDeviceAttributes ada = mResolvedScoAudioDevices.get(btDevice); + if (ada != null) { + result = mDeviceBroker.handleDeviceConnection( + ada, false /*connect*/, btDevice); + } else { + // Disconnect all possible audio device types if the disconnected device type is + // unknown + int[] outDeviceTypes = { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET, AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT - }; - for (int outDeviceType : outDeviceTypes) { - result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes( - outDeviceType, audioDevice.getAddress(), audioDevice.getName()), - isActive, btDevice); + }; + for (int outDeviceType : outDeviceTypes) { + result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes( + outDeviceType, address, name), false /*connect*/, btDevice); + } } } + // Handle input device + int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET; // handleDeviceConnection() && result to make sure the method get executed result = mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes( - inDevice, audioDevice.getAddress(), audioDevice.getName()), + inDevice, address, name), isActive, btDevice) && result; if (result) { if (isActive) { @@ -916,8 +948,8 @@ public class BtHelper { return btDevice == null ? "(null)" : btDevice.getAnonymizedAddress(); } - // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock - /*package */ synchronized void onSetBtScoActiveDevice(BluetoothDevice btDevice) { + @GuardedBy("mDeviceBroker.mDeviceStateLock") + /*package */ void onSetBtScoActiveDevice(BluetoothDevice btDevice) { Log.i(TAG, "onSetBtScoActiveDevice: " + getAnonymizedAddress(mBluetoothHeadsetDevice) + " -> " + getAnonymizedAddress(btDevice)); final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice; @@ -987,10 +1019,8 @@ public class BtHelper { //---------------------------------------------------------------------- - // @GuardedBy("mDeviceBroker.mSetModeLock") - // @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock") - @GuardedBy("BtHelper.this") - private boolean requestScoState(int state, int scoAudioMode) { + @GuardedBy("mDeviceBroker.mDeviceStateLock") + private synchronized boolean requestScoState(int state, int scoAudioMode) { checkScoAudioState(); if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) { // Make sure that the state transitions to CONNECTING even if we cannot initiate @@ -1154,13 +1184,14 @@ public class BtHelper { } } - private void checkScoAudioState() { + @GuardedBy("mDeviceBroker.mDeviceStateLock") + private synchronized void checkScoAudioState() { try { if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null && mScoAudioState == SCO_STATE_INACTIVE && mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice) - != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { + != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; } } catch (Exception e) { @@ -1184,7 +1215,7 @@ public class BtHelper { return result; } - /*package*/ int getLeAudioDeviceGroupId(BluetoothDevice device) { + /*package*/ synchronized int getLeAudioDeviceGroupId(BluetoothDevice device) { if (mLeAudio == null || device == null) { return BluetoothLeAudio.GROUP_ID_INVALID; } @@ -1197,7 +1228,7 @@ public class BtHelper { * @return A List of Pair(String main_address, String identity_address). Note that the * addresses returned by BluetoothDevice can be null. */ - /*package*/ List<Pair<String, String>> getLeAudioGroupAddresses(int groupId) { + /*package*/ synchronized List<Pair<String, String>> getLeAudioGroupAddresses(int groupId) { List<Pair<String, String>> addresses = new ArrayList<>(); BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); if (adapter == null || mLeAudio == null) { diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 5c1e783c0f52..6992580e4df8 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -129,9 +129,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private static final String SCREEN_ON_BLOCKED_TRACE_NAME = "Screen on blocked"; private static final String SCREEN_OFF_BLOCKED_TRACE_NAME = "Screen off blocked"; - private static final String TAG = "DisplayPowerController2"; + private static final String TAG = "DisplayPowerController"; // To enable these logs, run: - // 'adb shell setprop persist.log.tag.DisplayPowerController2 DEBUG && adb reboot' + // 'adb shell setprop persist.log.tag.DisplayPowerController DEBUG && adb reboot' private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); private static final String SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME = "Screen on blocked by displayoffload"; @@ -263,6 +263,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // The unique ID of the primary display device currently tied to this logical display private String mUniqueDisplayId; + private String mPhysicalDisplayName; // Tracker for brightness changes. @Nullable @@ -371,10 +372,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // If the last recorded screen state was dozing or not. private boolean mDozing; - private boolean mAppliedDimming; - - private boolean mAppliedThrottling; - // Reason for which the brightness was last changed. See {@link BrightnessReason} for more // information. // At the time of this writing, this value is changed within updatePowerState() only, which is @@ -483,7 +480,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // DPCs following the brightness of this DPC. This is used in concurrent displays mode - there // is one lead display, the additional displays follow the brightness value of the lead display. @GuardedBy("mLock") - private SparseArray<DisplayPowerControllerInterface> mDisplayBrightnessFollowers = + private final SparseArray<DisplayPowerControllerInterface> mDisplayBrightnessFollowers = new SparseArray(); private boolean mBootCompleted; @@ -525,8 +522,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mThermalBrightnessThrottlingDataId = logicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId; mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); - mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(); + mUniqueDisplayId = mDisplayDevice.getUniqueId(); mDisplayStatsId = mUniqueDisplayId.hashCode(); + mPhysicalDisplayName = mDisplayDevice.getNameLocked(); mLastBrightnessEvent = new BrightnessEvent(mDisplayId); mTempBrightnessEvent = new BrightnessEvent(mDisplayId); @@ -544,8 +542,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mBrightnessTracker = brightnessTracker; mOnBrightnessChangeRunnable = onBrightnessChangeRunnable; - PowerManager pm = context.getSystemService(PowerManager.class); - final Resources resources = context.getResources(); // DOZE AND DIM SETTINGS @@ -840,6 +836,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } final String uniqueId = device.getUniqueId(); + final String displayName = device.getNameLocked(); final DisplayDeviceConfig config = device.getDisplayDeviceConfig(); final IBinder token = device.getDisplayTokenLocked(); final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); @@ -866,6 +863,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call changed = true; mDisplayDevice = device; mUniqueDisplayId = uniqueId; + mPhysicalDisplayName = displayName; mDisplayStatsId = mUniqueDisplayId.hashCode(); mDisplayDeviceConfig = config; mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId; @@ -1552,10 +1550,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // unthrottled (unclamped/ideal) and throttled brightness levels for subsequent operations. // Note throttling effectively changes the allowed brightness range, so, similarly to HBM, // we broadcast this change through setting. - final float unthrottledBrightnessState = brightnessState; + final float unthrottledBrightnessState = rawBrightnessState; DisplayBrightnessState clampedState = mBrightnessClamperController.clamp(mPowerRequest, brightnessState, slowChange, /* displayState= */ state); - brightnessState = clampedState.getBrightness(); slowChange = clampedState.isSlowChange(); // faster rate wins, at this point customAnimationRate == -1, strategy does not control @@ -1744,11 +1741,23 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // brightness cap, RBC state, etc. mTempBrightnessEvent.setTime(System.currentTimeMillis()); mTempBrightnessEvent.setBrightness(brightnessState); + mTempBrightnessEvent.setNits( + mDisplayBrightnessController.convertToAdjustedNits(brightnessState)); + final float hbmMax = mBrightnessRangeController.getCurrentBrightnessMax(); + final float clampedMax = Math.min(clampedState.getMaxBrightness(), hbmMax); + final float brightnessOnAvailableScale = MathUtils.constrainedMap(0.0f, 1.0f, + clampedState.getMinBrightness(), clampedMax, + brightnessState); + mTempBrightnessEvent.setPercent(Math.round( + 1000.0f * com.android.internal.display.BrightnessUtils.convertLinearToGamma( + brightnessOnAvailableScale) / 10)); // rounded to one dp + mTempBrightnessEvent.setUnclampedBrightness(unthrottledBrightnessState); mTempBrightnessEvent.setPhysicalDisplayId(mUniqueDisplayId); + mTempBrightnessEvent.setPhysicalDisplayName(mPhysicalDisplayName); mTempBrightnessEvent.setDisplayState(state); mTempBrightnessEvent.setDisplayPolicy(mPowerRequest.policy); mTempBrightnessEvent.setReason(mBrightnessReason); - mTempBrightnessEvent.setHbmMax(mBrightnessRangeController.getCurrentBrightnessMax()); + mTempBrightnessEvent.setHbmMax(hbmMax); mTempBrightnessEvent.setHbmMode(mBrightnessRangeController.getHighBrightnessMode()); mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags() | (mIsRbcActive ? BrightnessEvent.FLAG_RBC : 0) @@ -2648,8 +2657,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call pw.println("Display Power Controller Thread State:"); pw.println(" mPowerRequest=" + mPowerRequest); pw.println(" mBrightnessReason=" + mBrightnessReason); - pw.println(" mAppliedDimming=" + mAppliedDimming); - pw.println(" mAppliedThrottling=" + mAppliedThrottling); pw.println(" mDozing=" + mDozing); pw.println(" mSkipRampState=" + skipRampStateToString(mSkipRampState)); pw.println(" mScreenOnBlockStartRealTime=" + mScreenOnBlockStartRealTime); diff --git a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java index 82b401a7cc83..5cc603c5018c 100644 --- a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java +++ b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java @@ -20,6 +20,8 @@ import static android.hardware.display.DisplayManagerInternal.DisplayPowerReques import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.policyToString; import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT; +import static com.android.server.display.BrightnessMappingStrategy.INVALID_LUX; +import static com.android.server.display.BrightnessMappingStrategy.INVALID_NITS; import static com.android.server.display.config.DisplayBrightnessMappingConfig.autoBrightnessModeToString; import android.hardware.display.BrightnessInfo; @@ -48,13 +50,17 @@ public final class BrightnessEvent { private BrightnessReason mReason = new BrightnessReason(); private int mDisplayId; private String mPhysicalDisplayId; + private String mPhysicalDisplayName; private int mDisplayState; private int mDisplayPolicy; private long mTime; private float mLux; + private float mNits; + private float mPercent; private float mPreThresholdLux; private float mInitialBrightness; private float mBrightness; + private float mUnclampedBrightness; private float mRecommendedBrightness; private float mPreThresholdBrightness; private int mHbmMode; @@ -88,15 +94,19 @@ public final class BrightnessEvent { mReason.set(that.getReason()); mDisplayId = that.getDisplayId(); mPhysicalDisplayId = that.getPhysicalDisplayId(); + mPhysicalDisplayName = that.getPhysicalDisplayName(); mDisplayState = that.mDisplayState; mDisplayPolicy = that.mDisplayPolicy; mTime = that.getTime(); // Lux values mLux = that.getLux(); mPreThresholdLux = that.getPreThresholdLux(); + mNits = that.getNits(); + mPercent = that.getPercent(); // Brightness values mInitialBrightness = that.getInitialBrightness(); mBrightness = that.getBrightness(); + mUnclampedBrightness = that.getUnclampedBrightness(); mRecommendedBrightness = that.getRecommendedBrightness(); mPreThresholdBrightness = that.getPreThresholdBrightness(); // Different brightness modulations @@ -121,14 +131,18 @@ public final class BrightnessEvent { mReason = new BrightnessReason(); mTime = SystemClock.uptimeMillis(); mPhysicalDisplayId = ""; + mPhysicalDisplayName = ""; mDisplayState = Display.STATE_UNKNOWN; mDisplayPolicy = POLICY_OFF; // Lux values - mLux = 0; + mLux = INVALID_LUX; mPreThresholdLux = 0; + mNits = INVALID_NITS; + mPercent = -1f; // Brightness values mInitialBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; mBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; + mUnclampedBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; mRecommendedBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; mPreThresholdBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; // Different brightness modulations @@ -160,13 +174,18 @@ public final class BrightnessEvent { return mReason.equals(that.mReason) && mDisplayId == that.mDisplayId && mPhysicalDisplayId.equals(that.mPhysicalDisplayId) + && mPhysicalDisplayName.equals(that.mPhysicalDisplayName) && mDisplayState == that.mDisplayState && mDisplayPolicy == that.mDisplayPolicy && Float.floatToRawIntBits(mLux) == Float.floatToRawIntBits(that.mLux) && Float.floatToRawIntBits(mPreThresholdLux) == Float.floatToRawIntBits(that.mPreThresholdLux) + && Float.floatToRawIntBits(mNits) == Float.floatToRawIntBits(that.mNits) + && Float.floatToRawIntBits(mPercent) == Float.floatToRawIntBits(that.mPercent) && Float.floatToRawIntBits(mBrightness) == Float.floatToRawIntBits(that.mBrightness) + && Float.floatToRawIntBits(mUnclampedBrightness) + == Float.floatToRawIntBits(that.mUnclampedBrightness) && Float.floatToRawIntBits(mRecommendedBrightness) == Float.floatToRawIntBits(that.mRecommendedBrightness) && Float.floatToRawIntBits(mPreThresholdBrightness) @@ -195,27 +214,34 @@ public final class BrightnessEvent { public String toString(boolean includeTime) { return (includeTime ? FORMAT.format(new Date(mTime)) + " - " : "") + "BrightnessEvent: " - + "disp=" + mDisplayId - + ", physDisp=" + mPhysicalDisplayId - + ", displayState=" + Display.stateToString(mDisplayState) - + ", displayPolicy=" + policyToString(mDisplayPolicy) - + ", brt=" + mBrightness + ((mFlags & FLAG_USER_SET) != 0 ? "(user_set)" : "") + + "brt=" + mBrightness + ((mFlags & FLAG_USER_SET) != 0 ? "(user_set)" : "") + " (" + + mPercent + "%)" + + ", nits= " + mNits + + ", lux=" + mLux + + ", reason=" + mReason.toString(mAdjustmentFlags) + + ", strat=" + mDisplayBrightnessStrategyName + + ", state=" + Display.stateToString(mDisplayState) + + ", policy=" + policyToString(mDisplayPolicy) + + ", flags=" + flagsToString() + // Autobrightness + ", initBrt=" + mInitialBrightness + ", rcmdBrt=" + mRecommendedBrightness + ", preBrt=" + mPreThresholdBrightness - + ", lux=" + mLux + ", preLux=" + mPreThresholdLux + + ", wasShortTermModelActive=" + mWasShortTermModelActive + + ", autoBrightness=" + mAutomaticBrightnessEnabled + " (" + + autoBrightnessModeToString(mAutoBrightnessMode) + ")" + // Throttling info + + ", unclampedBrt=" + mUnclampedBrightness + ", hbmMax=" + mHbmMax + ", hbmMode=" + BrightnessInfo.hbmToString(mHbmMode) - + ", rbcStrength=" + mRbcStrength + ", thrmMax=" + mThermalMax + // Modifiers + + ", rbcStrength=" + mRbcStrength + ", powerFactor=" + mPowerFactor - + ", wasShortTermModelActive=" + mWasShortTermModelActive - + ", flags=" + flagsToString() - + ", reason=" + mReason.toString(mAdjustmentFlags) - + ", autoBrightness=" + mAutomaticBrightnessEnabled - + ", strategy=" + mDisplayBrightnessStrategyName - + ", autoBrightnessMode=" + autoBrightnessModeToString(mAutoBrightnessMode); + // Meta + + ", physDisp=" + mPhysicalDisplayName + "(" + mPhysicalDisplayId + ")" + + ", logicalId=" + mDisplayId; } @Override @@ -255,6 +281,14 @@ public final class BrightnessEvent { this.mPhysicalDisplayId = mPhysicalDisplayId; } + public String getPhysicalDisplayName() { + return mPhysicalDisplayName; + } + + public void setPhysicalDisplayName(String mPhysicalDisplayName) { + this.mPhysicalDisplayName = mPhysicalDisplayName; + } + public void setDisplayState(int state) { mDisplayState = state; } @@ -295,6 +329,29 @@ public final class BrightnessEvent { this.mBrightness = brightness; } + public float getUnclampedBrightness() { + return mUnclampedBrightness; + } + + public void setUnclampedBrightness(float unclampedBrightness) { + this.mUnclampedBrightness = unclampedBrightness; + } + + public void setPercent(float percent) { + this.mPercent = percent; + } + public float getPercent() { + return mPercent; + } + + public void setNits(float nits) { + this.mNits = nits; + } + + public float getNits() { + return mNits; + } + public float getRecommendedBrightness() { return mRecommendedBrightness; } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index f61ca61c1e04..c82e5be7c643 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -16,6 +16,8 @@ package com.android.server.inputmethod; +import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID; + import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.NonNull; @@ -110,7 +112,7 @@ public abstract class InputMethodManagerInternal { InlineSuggestionsRequestInfo requestInfo, InlineSuggestionsRequestCallback cb); /** - * Force switch to the enabled input method by {@code imeId} for current user. If the input + * Force switch to the enabled input method by {@code imeId} for the current user. If the input * method with {@code imeId} is not enabled or not installed, do nothing. * * @param imeId the input method ID to be switched to @@ -119,7 +121,25 @@ public abstract class InputMethodManagerInternal { * method by {@code imeId}; {@code false} the input method with {@code imeId} is not available * to be switched. */ - public abstract boolean switchToInputMethod(String imeId, @UserIdInt int userId); + public boolean switchToInputMethod(@NonNull String imeId, @UserIdInt int userId) { + return switchToInputMethod(imeId, NOT_A_SUBTYPE_ID, userId); + } + + /** + * Force switch to the enabled input method by {@code imeId} for the current user. If the input + * method with {@code imeId} is not enabled or not installed, do nothing. If {@code subtypeId} + * is also supplied (not {@link InputMethodUtils#NOT_A_SUBTYPE_ID}) and valid, also switches to + * it, otherwise the system decides the most sensible default subtype to use. + * + * @param imeId the input method ID to be switched to + * @param subtypeId the input method subtype ID to be switched to + * @param userId the user ID to be queried + * @return {@code true} if the current input method was successfully switched to the input + * method by {@code imeId}; {@code false} the input method with {@code imeId} is not available + * to be switched. + */ + public abstract boolean switchToInputMethod(@NonNull String imeId, int subtypeId, + @UserIdInt int userId); /** * Force enable or disable the input method associated with {@code imeId} for given user. If @@ -211,6 +231,15 @@ public abstract class InputMethodManagerInternal { public abstract void updateImeWindowStatus(boolean disableImeIcon, int displayId); /** + * Updates and reports whether the IME switcher button should be shown, regardless whether + * SystemUI or the IME is responsible for drawing it and the corresponding navigation bar. + * + * @param displayId the display for which to update the IME switcher button visibility. + * @param userId the user for which to update the IME switcher button visibility. + */ + public abstract void updateShouldShowImeSwitcher(int displayId, @UserIdInt int userId); + + /** * Finish stylus handwriting by calling {@link InputMethodService#finishStylusHandwriting()} if * there is an ongoing handwriting session. */ @@ -290,7 +319,8 @@ public abstract class InputMethodManagerInternal { } @Override - public boolean switchToInputMethod(String imeId, @UserIdInt int userId) { + public boolean switchToInputMethod(@NonNull String imeId, int subtypeId, + @UserIdInt int userId) { return false; } @@ -335,6 +365,10 @@ public abstract class InputMethodManagerInternal { } @Override + public void updateShouldShowImeSwitcher(int displayId, @UserIdInt int userId) { + } + + @Override public void onSessionForAccessibilityCreated(int accessibilityConnectionId, IAccessibilityInputMethodSession session, @UserIdInt int userId) { } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 85af7ab8a10f..fbd9ac03dc27 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -181,6 +181,7 @@ import com.android.server.SystemService; import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import com.android.server.input.InputManagerInternal; import com.android.server.inputmethod.InputMethodManagerInternal.InputMethodListListener; +import com.android.server.inputmethod.InputMethodMenuControllerNew.MenuItem; import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem; import com.android.server.pm.UserManagerInternal; import com.android.server.statusbar.StatusBarManagerInternal; @@ -360,6 +361,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private final UserManagerInternal mUserManagerInternal; @MultiUserUnawareField private final InputMethodMenuController mMenuController; + private final InputMethodMenuControllerNew mMenuControllerNew; @GuardedBy("ImfLock.class") @MultiUserUnawareField @@ -566,7 +568,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } switch (key) { case Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD: { - mMenuController.updateKeyboardFromSettingsLocked(); + if (!Flags.imeSwitcherRevamp()) { + mMenuController.updateKeyboardFromSettingsLocked(); + } break; } case Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE: { @@ -631,7 +635,15 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } } } - mMenuController.hideInputMethodMenu(); + if (Flags.imeSwitcherRevamp()) { + synchronized (ImfLock.class) { + final var bindingController = getInputMethodBindingController(senderUserId); + mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), + senderUserId); + } + } else { + mMenuController.hideInputMethodMenu(); + } } else { Slog.w(TAG, "Unexpected intent " + intent); } @@ -1171,6 +1183,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. : bindingControllerFactory); mMenuController = new InputMethodMenuController(this); + mMenuControllerNew = Flags.imeSwitcherRevamp() + ? new InputMethodMenuControllerNew() : null; mVisibilityStateComputer = new ImeVisibilityStateComputer(this); mVisibilityApplier = new DefaultImeVisibilityApplier(this); @@ -1782,7 +1796,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. ImeTracker.PHASE_SERVER_WAIT_IME); userData.mCurStatsToken = null; // TODO: Make mMenuController multi-user aware - mMenuController.hideInputMethodMenuLocked(); + if (Flags.imeSwitcherRevamp()) { + mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId); + } else { + mMenuController.hideInputMethodMenuLocked(); + } } } @@ -2599,7 +2617,12 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (!mShowOngoingImeSwitcherForPhones) return false; // When the IME switcher dialog is shown, the IME switcher button should be hidden. // TODO(b/305849394): Make mMenuController multi-user aware. - if (mMenuController.getSwitchingDialogLocked() != null) return false; + final boolean switcherMenuShowing = Flags.imeSwitcherRevamp() + ? mMenuControllerNew.isShowing() + : mMenuController.getSwitchingDialogLocked() != null; + if (switcherMenuShowing) { + return false; + } // When we are switching IMEs, the IME switcher button should be hidden. final var bindingController = getInputMethodBindingController(userId); if (!Objects.equals(bindingController.getCurId(), @@ -2614,7 +2637,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. || (visibility & InputMethodService.IME_INVISIBLE) != 0) { return false; } - if (mWindowManagerInternal.isHardKeyboardAvailable()) { + if (mWindowManagerInternal.isHardKeyboardAvailable() && !Flags.imeSwitcherRevamp()) { // When physical keyboard is attached, we show the ime switcher (or notification if // NavBar is not available) because SHOW_IME_WITH_HARD_KEYBOARD settings currently // exists in the IME switcher dialog. Might be OK to remove this condition once @@ -2625,6 +2648,15 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); + if (Flags.imeSwitcherRevamp()) { + // The IME switcher button should be shown when the current IME specified a + // language settings activity. + final var curImi = settings.getMethodMap().get(settings.getSelectedInputMethod()); + if (curImi != null && curImi.createImeLanguageSettingsActivityIntent() != null) { + return true; + } + } + return hasMultipleSubtypesForSwitcher(false /* nonAuxOnly */, settings); } @@ -2794,7 +2826,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } final var curId = bindingController.getCurId(); // TODO(b/305849394): Make mMenuController multi-user aware. - if (mMenuController.getSwitchingDialogLocked() != null + final boolean switcherMenuShowing = Flags.imeSwitcherRevamp() + ? mMenuControllerNew.isShowing() + : mMenuController.getSwitchingDialogLocked() != null; + if (switcherMenuShowing || !Objects.equals(curId, bindingController.getSelectedMethodId())) { // When the IME switcher dialog is shown, or we are switching IMEs, // the back button should be in the default state (as if the IME is not shown). @@ -2813,7 +2848,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") void updateFromSettingsLocked(boolean enabledMayChange, @UserIdInt int userId) { updateInputMethodsFromSettingsLocked(enabledMayChange, userId); - mMenuController.updateKeyboardFromSettingsLocked(); + if (!Flags.imeSwitcherRevamp()) { + mMenuController.updateKeyboardFromSettingsLocked(); + } } /** @@ -3979,10 +4016,70 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD) public boolean isInputMethodPickerShownForTest() { synchronized (ImfLock.class) { - return mMenuController.isisInputMethodPickerShownForTestLocked(); + return Flags.imeSwitcherRevamp() + ? mMenuControllerNew.isShowing() + : mMenuController.isisInputMethodPickerShownForTestLocked(); } } + /** + * Gets the list of Input Method Switcher Menu items and the index of the selected item. + * + * @param items the list of input method and subtype items. + * @param selectedImeId the ID of the selected input method. + * @param selectedSubtypeId the ID of the selected input method subtype, + * or {@link #NOT_A_SUBTYPE_ID} if no subtype is selected. + * @param userId the ID of the user for which to get the menu items. + * @return the list of menu items, and the index of the selected item, + * or {@code -1} if no item is selected. + */ + @GuardedBy("ImfLock.class") + @NonNull + private Pair<List<MenuItem>, Integer> getInputMethodPickerItems( + @NonNull List<ImeSubtypeListItem> items, @Nullable String selectedImeId, + int selectedSubtypeId, @UserIdInt int userId) { + final var bindingController = getInputMethodBindingController(userId); + final var settings = InputMethodSettingsRepository.get(userId); + + if (selectedSubtypeId == NOT_A_SUBTYPE_ID) { + // TODO(b/351124299): Check if this fallback logic is still necessary. + final var curSubtype = bindingController.getCurrentInputMethodSubtype(); + if (curSubtype != null) { + final var curMethodId = bindingController.getSelectedMethodId(); + final var curImi = settings.getMethodMap().get(curMethodId); + selectedSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode( + curImi, curSubtype.hashCode()); + } + } + + // No item is selected by default. When we have a list of explicitly enabled + // subtypes, the implicit subtype is no longer listed. If the implicit one + // is still selected, no items will be shown as selected. + int selectedIndex = -1; + String prevImeId = null; + final var menuItems = new ArrayList<MenuItem>(); + for (int i = 0; i < items.size(); i++) { + final var item = items.get(i); + final var imeId = item.mImi.getId(); + if (imeId.equals(selectedImeId)) { + final int subtypeId = item.mSubtypeId; + // Check if this is the selected IME-subtype pair. + if ((subtypeId == 0 && selectedSubtypeId == NOT_A_SUBTYPE_ID) + || subtypeId == NOT_A_SUBTYPE_ID + || subtypeId == selectedSubtypeId) { + selectedIndex = i; + } + } + final boolean hasHeader = !imeId.equals(prevImeId); + final boolean hasDivider = hasHeader && prevImeId != null; + prevImeId = imeId; + menuItems.add(new MenuItem(item.mImeName, item.mSubtypeName, item.mImi, item.mSubtypeId, + hasHeader, hasDivider)); + } + + return new Pair<>(menuItems, selectedIndex); + } + @BinderThread private void onImeSwitchButtonClickFromClient(@NonNull IBinder token, int displayId, @NonNull UserData userData) { @@ -4625,7 +4722,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. proto.write(IS_INTERACTIVE, mIsInteractive); proto.write(BACK_DISPOSITION, bindingController.getBackDisposition()); proto.write(IME_WINDOW_VISIBILITY, bindingController.getImeWindowVis()); - proto.write(SHOW_IME_WITH_HARD_KEYBOARD, mMenuController.getShowImeWithHardKeyboard()); + if (!Flags.imeSwitcherRevamp()) { + proto.write(SHOW_IME_WITH_HARD_KEYBOARD, + mMenuController.getShowImeWithHardKeyboard()); + } proto.write(CONCURRENT_MULTI_USER_MODE_ENABLED, mConcurrentMultiUserModeEnabled); proto.end(token); } @@ -4931,8 +5031,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. synchronized (ImfLock.class) { final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + final int userId = settings.getUserId(); final boolean isScreenLocked = mWindowManagerInternal.isKeyguardLocked() - && mWindowManagerInternal.isKeyguardSecure(settings.getUserId()); + && mWindowManagerInternal.isKeyguardSecure(userId); final String lastInputMethodId = settings.getSelectedInputMethod(); int lastInputMethodSubtypeId = settings.getSelectedInputMethodSubtypeId(lastInputMethodId); @@ -4945,12 +5046,35 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. Slog.w(TAG, "Show switching menu failed, imList is empty," + " showAuxSubtypes: " + showAuxSubtypes + " isScreenLocked: " + isScreenLocked - + " userId: " + settings.getUserId()); + + " userId: " + userId); return false; } - mMenuController.showInputMethodMenuLocked(showAuxSubtypes, displayId, - lastInputMethodId, lastInputMethodSubtypeId, imList); + if (Flags.imeSwitcherRevamp()) { + if (DEBUG) { + Slog.v(TAG, "Show IME switcher menu," + + " showAuxSubtypes=" + showAuxSubtypes + + " displayId=" + displayId + + " preferredInputMethodId=" + lastInputMethodId + + " preferredInputMethodSubtypeId=" + lastInputMethodSubtypeId); + } + + final var itemsAndIndex = getInputMethodPickerItems(imList, + lastInputMethodId, lastInputMethodSubtypeId, userId); + final var menuItems = itemsAndIndex.first; + final int selectedIndex = itemsAndIndex.second; + + if (selectedIndex == -1) { + Slog.w(TAG, "Switching menu shown with no item selected" + + ", IME id: " + lastInputMethodId + + ", subtype index: " + lastInputMethodSubtypeId); + } + + mMenuControllerNew.show(menuItems, selectedIndex, displayId, userId); + } else { + mMenuController.showInputMethodMenuLocked(showAuxSubtypes, displayId, + lastInputMethodId, lastInputMethodSubtypeId, imList); + } } return true; @@ -5021,7 +5145,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // -------------------------------------------------------------- case MSG_HARD_KEYBOARD_SWITCH_CHANGED: - mMenuController.handleHardKeyboardStatusChange(msg.arg1 == 1); + if (!Flags.imeSwitcherRevamp()) { + mMenuController.handleHardKeyboardStatusChange(msg.arg1 == 1); + } synchronized (ImfLock.class) { sendOnNavButtonFlagsChangedToAllImesLocked(); } @@ -5591,7 +5717,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @GuardedBy("ImfLock.class") - private boolean switchToInputMethodLocked(String imeId, @UserIdInt int userId) { + private boolean switchToInputMethodLocked(@NonNull String imeId, int subtypeId, + @UserIdInt int userId) { final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); if (mConcurrentMultiUserModeEnabled || userId == mCurrentUserId) { if (!settings.getMethodMap().containsKey(imeId) @@ -5599,7 +5726,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. .contains(settings.getMethodMap().get(imeId))) { return false; // IME is not found or not enabled. } - setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID, userId); + setInputMethodLocked(imeId, subtypeId, userId); return true; } if (!settings.getMethodMap().containsKey(imeId) @@ -5608,6 +5735,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return false; // IME is not found or not enabled. } settings.putSelectedInputMethod(imeId); + // For non-current user, only reset subtypeId (instead of setting the given one). settings.putSelectedSubtype(NOT_A_SUBTYPE_ID); return true; } @@ -5753,9 +5881,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @Override - public boolean switchToInputMethod(String imeId, @UserIdInt int userId) { + public boolean switchToInputMethod(@NonNull String imeId, int subtypeId, + @UserIdInt int userId) { synchronized (ImfLock.class) { - return switchToInputMethodLocked(imeId, userId); + return switchToInputMethodLocked(imeId, subtypeId, userId); } } @@ -5852,7 +5981,12 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // input target changed, in case seeing the dialog dismiss flickering during // the next focused window starting the input connection. if (mLastImeTargetWindow != userData.mImeBindingState.mFocusedWindow) { - mMenuController.hideInputMethodMenuLocked(); + if (Flags.imeSwitcherRevamp()) { + final var bindingController = getInputMethodBindingController(userId); + mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId); + } else { + mMenuController.hideInputMethodMenuLocked(); + } } } } @@ -5871,6 +6005,15 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @Override + public void updateShouldShowImeSwitcher(int displayId, @UserIdInt int userId) { + synchronized (ImfLock.class) { + updateSystemUiLocked(userId); + final var userData = getUserData(userId); + sendOnNavButtonFlagsChangedLocked(userData); + } + } + + @Override public void onSessionForAccessibilityCreated(int accessibilityConnectionId, IAccessibilityInputMethodSession session, @UserIdInt int userId) { synchronized (ImfLock.class) { @@ -6192,6 +6335,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. }; mUserDataRepository.forAllUserData(userDataDump); + if (Flags.imeSwitcherRevamp()) { + p.println(" menuControllerNew:"); + mMenuControllerNew.dump(p, " "); + } p.println(" mCurToken=" + bindingController.getCurToken()); p.println(" mCurTokenDisplayId=" + bindingController.getCurTokenDisplayId()); p.println(" mCurHostInputToken=" + bindingController.getCurHostInputToken()); @@ -6638,7 +6785,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. continue; } boolean failedToSelectUnknownIme = !switchToInputMethodLocked(imeId, - userId); + NOT_A_SUBTYPE_ID, userId); if (failedToSelectUnknownIme) { error.print("Unknown input method "); error.print(imeId); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java new file mode 100644 index 000000000000..cbb1807a6c2e --- /dev/null +++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java @@ -0,0 +1,350 @@ +/* + * 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.inputmethod; + + +import static com.android.server.inputmethod.InputMethodManagerService.DEBUG; +import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.text.TextUtils; +import android.util.Printer; +import android.util.Slog; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodInfo; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.internal.widget.RecyclerView; + +import java.util.List; + +/** + * Controller for showing and hiding the Input Method Switcher Menu. + */ +final class InputMethodMenuControllerNew { + + private static final String TAG = InputMethodMenuControllerNew.class.getSimpleName(); + + /** + * The horizontal offset from the menu to the edge of the screen corresponding + * to {@link Gravity#END}. + */ + private static final int HORIZONTAL_OFFSET = 16; + + /** The title of the window, used for debugging. */ + private static final String WINDOW_TITLE = "IME Switcher Menu"; + + private final InputMethodDialogWindowContext mDialogWindowContext = + new InputMethodDialogWindowContext(); + + @Nullable + private AlertDialog mDialog; + + @Nullable + private List<MenuItem> mMenuItems; + + /** + * Shows the Input Method Switcher Menu, with a list of IMEs and their subtypes. + * + * @param items the list of menu items. + * @param selectedIndex the index of the menu item that is selected. + * If no other IMEs are enabled, this index will be out of reach. + * @param displayId the ID of the display where the menu was requested. + * @param userId the ID of the user that requested the menu. + */ + void show(@NonNull List<MenuItem> items, int selectedIndex, int displayId, + @UserIdInt int userId) { + // Hide the menu in case it was already showing. + hide(displayId, userId); + + final Context dialogWindowContext = mDialogWindowContext.get(displayId); + final var builder = new AlertDialog.Builder(dialogWindowContext, + com.android.internal.R.style.Theme_DeviceDefault_InputMethodSwitcherDialog); + final var inflater = LayoutInflater.from(builder.getContext()); + + // Create the content view. + final View contentView = inflater + .inflate(com.android.internal.R.layout.input_method_switch_dialog_new, null); + contentView.setAccessibilityPaneTitle( + dialogWindowContext.getText(com.android.internal.R.string.select_input_method)); + builder.setView(contentView); + + final DialogInterface.OnClickListener onClickListener = (dialog, which) -> { + if (which != selectedIndex) { + final var item = items.get(which); + InputMethodManagerInternal.get() + .switchToInputMethod(item.mImi.getId(), item.mSubtypeId, userId); + } + hide(displayId, userId); + }; + + final var selectedImi = selectedIndex >= 0 ? items.get(selectedIndex).mImi : null; + final var languageSettingsIntent = selectedImi != null + ? selectedImi.createImeLanguageSettingsActivityIntent() : null; + final boolean hasLanguageSettingsButton = languageSettingsIntent != null; + if (hasLanguageSettingsButton) { + final View buttonBar = contentView + .requireViewById(com.android.internal.R.id.button_bar); + buttonBar.setVisibility(View.VISIBLE); + + languageSettingsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + final Button languageSettingsButton = contentView + .requireViewById(com.android.internal.R.id.button1); + languageSettingsButton.setVisibility(View.VISIBLE); + languageSettingsButton.setOnClickListener(v -> { + v.getContext().startActivity(languageSettingsIntent); + hide(displayId, userId); + }); + } + + // Create the current IME subtypes list. + final RecyclerView recyclerView = contentView + .requireViewById(com.android.internal.R.id.list); + recyclerView.setAdapter(new Adapter(items, selectedIndex, inflater, onClickListener)); + // Scroll to the currently selected IME. + recyclerView.scrollToPosition(selectedIndex); + // Indicate that the list can be scrolled. + recyclerView.setScrollIndicators( + hasLanguageSettingsButton ? View.SCROLL_INDICATOR_BOTTOM : 0); + + builder.setOnCancelListener(dialog -> hide(displayId, userId)); + mMenuItems = items; + mDialog = builder.create(); + mDialog.setCanceledOnTouchOutside(true); + final Window w = mDialog.getWindow(); + w.setHideOverlayWindows(true); + final WindowManager.LayoutParams attrs = w.getAttributes(); + // Use an alternate token for the dialog for that window manager can group the token + // with other IME windows based on type vs. grouping based on whichever token happens + // to get selected by the system later on. + attrs.token = dialogWindowContext.getWindowContextToken(); + attrs.gravity = Gravity.getAbsoluteGravity(Gravity.BOTTOM | Gravity.END, + dialogWindowContext.getResources().getConfiguration().getLayoutDirection()); + attrs.x = HORIZONTAL_OFFSET; + attrs.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; + attrs.type = WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; + // Used for debugging only, not user visible. + attrs.setTitle(WINDOW_TITLE); + w.setAttributes(attrs); + + mDialog.show(); + InputMethodManagerInternal.get().updateShouldShowImeSwitcher(displayId, userId); + } + + /** + * Hides the Input Method Switcher Menu. + * + * @param displayId the ID of the display from where the menu should be hidden. + * @param userId the ID of the user for which the menu should be hidden. + */ + void hide(int displayId, @UserIdInt int userId) { + if (DEBUG) Slog.v(TAG, "Hide IME switcher menu."); + + mMenuItems = null; + // Cannot use dialog.isShowing() here, as the cancel listener flow already resets mShowing. + if (mDialog != null) { + mDialog.dismiss(); + mDialog = null; + + InputMethodManagerInternal.get().updateShouldShowImeSwitcher(displayId, userId); + } + } + + /** + * Returns whether the Input Method Switcher Menu is showing. + */ + boolean isShowing() { + return mDialog != null && mDialog.isShowing(); + } + + void dump(@NonNull Printer pw, @NonNull String prefix) { + final boolean showing = isShowing(); + pw.println(prefix + " isShowing: " + showing); + + if (showing) { + pw.println(prefix + " menuItems: " + mMenuItems); + } + } + + /** + * Item to be shown in the Input Method Switcher Menu, containing an input method and + * optionally an input method subtype. + */ + static class MenuItem { + + /** The name of the input method. */ + @NonNull + private final CharSequence mImeName; + + /** + * The name of the input method subtype, or {@code null} if this item doesn't have a + * subtype. + */ + @Nullable + private final CharSequence mSubtypeName; + + /** The info of the input method. */ + @NonNull + private final InputMethodInfo mImi; + + /** + * The index of the subtype in the input method's array of subtypes, + * or {@link InputMethodUtils#NOT_A_SUBTYPE_ID} if this item doesn't have a subtype. + */ + @IntRange(from = NOT_A_SUBTYPE_ID) + private final int mSubtypeId; + + /** Whether this item has a group header (only the first item of each input method). */ + private final boolean mHasHeader; + + /** + * Whether this item should has a group divider (same as {@link #mHasHeader}, + * excluding the first IME). + */ + private final boolean mHasDivider; + + MenuItem(@NonNull CharSequence imeName, @Nullable CharSequence subtypeName, + @NonNull InputMethodInfo imi, @IntRange(from = NOT_A_SUBTYPE_ID) int subtypeId, + boolean hasHeader, boolean hasDivider) { + mImeName = imeName; + mSubtypeName = subtypeName; + mImi = imi; + mSubtypeId = subtypeId; + mHasHeader = hasHeader; + mHasDivider = hasDivider; + } + + @Override + public String toString() { + return "MenuItem{" + + "mImeName=" + mImeName + + " mSubtypeName=" + mSubtypeName + + " mSubtypeId=" + mSubtypeId + + " mHasHeader=" + mHasHeader + + " mHasDivider=" + mHasDivider + + "}"; + } + } + + private static class Adapter extends RecyclerView.Adapter<Adapter.ViewHolder> { + + /** The list of items to show. */ + @NonNull + private final List<MenuItem> mItems; + /** The index of the selected item. */ + private final int mSelectedIndex; + @NonNull + private final LayoutInflater mInflater; + @NonNull + private final DialogInterface.OnClickListener mOnClickListener; + + Adapter(@NonNull List<MenuItem> items, int selectedIndex, + @NonNull LayoutInflater inflater, + @NonNull DialogInterface.OnClickListener onClickListener) { + mItems = items; + mSelectedIndex = selectedIndex; + mInflater = inflater; + mOnClickListener = onClickListener; + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + final View view = mInflater.inflate( + com.android.internal.R.layout.input_method_switch_item_new, parent, false); + + return new ViewHolder(view, mOnClickListener); + } + + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + holder.bind(mItems.get(position), position == mSelectedIndex /* isSelected */); + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + private static class ViewHolder extends RecyclerView.ViewHolder { + + /** The container of the item. */ + @NonNull + private final View mContainer; + /** The name of the item. */ + @NonNull + private final TextView mName; + /** Indicator for the selected status of the item. */ + @NonNull + private final ImageView mCheckmark; + /** The group header optionally drawn above the item. */ + @NonNull + private final TextView mHeader; + /** The group divider optionally drawn above the item. */ + @NonNull + private final View mDivider; + + private ViewHolder(@NonNull View itemView, + @NonNull DialogInterface.OnClickListener onClickListener) { + super(itemView); + + mContainer = itemView.requireViewById(com.android.internal.R.id.list_item); + mName = itemView.requireViewById(com.android.internal.R.id.text); + mCheckmark = itemView.requireViewById(com.android.internal.R.id.image); + mHeader = itemView.requireViewById(com.android.internal.R.id.header_text); + mDivider = itemView.requireViewById(com.android.internal.R.id.divider); + + mContainer.setOnClickListener((v) -> + onClickListener.onClick(null /* dialog */, getAdapterPosition())); + } + + /** + * Binds the given item to the current view. + * + * @param item the item to bind. + * @param isSelected whether this is selected. + */ + private void bind(@NonNull MenuItem item, boolean isSelected) { + // Use the IME name for subtypes with an empty subtype name. + final var name = TextUtils.isEmpty(item.mSubtypeName) + ? item.mImeName : item.mSubtypeName; + mContainer.setActivated(isSelected); + // Activated is the correct state, but we also set selected for accessibility info. + mContainer.setSelected(isSelected); + mName.setSelected(isSelected); + mName.setText(name); + mCheckmark.setVisibility(isSelected ? View.VISIBLE : View.GONE); + mHeader.setText(item.mImeName); + mHeader.setVisibility(item.mHasHeader ? View.VISIBLE : View.GONE); + mDivider.setVisibility(item.mHasDivider ? View.VISIBLE : View.GONE); + } + } + } +} diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index ba7d3b8c76d2..d9f36223c6dd 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -251,6 +251,10 @@ class MediaRouter2ServiceImpl { systemRoutes = providerInfo.getRoutes(); } else { systemRoutes = Collections.emptyList(); + Slog.e( + TAG, + "Returning empty system routes list because " + + "system provider has null providerInfo."); } } else { systemRoutes = new ArrayList<>(); diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java index 2d8aa3f06de3..09de01e88c9e 100644 --- a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java +++ b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java @@ -137,7 +137,7 @@ abstract class AbsAppSnapshotController<TYPE extends WindowContainer, } abstract ActivityRecord getTopActivity(TYPE source); - abstract ActivityRecord getTopFullscreenActivity(TYPE source); + abstract WindowState getTopFullscreenWindow(TYPE source); abstract ActivityManager.TaskDescription getTaskDescription(TYPE source); /** * Find the window for a given task to take a snapshot. Top child of the task is usually the one @@ -465,10 +465,7 @@ abstract class AbsAppSnapshotController<TYPE extends WindowContainer, */ @WindowInsetsController.Appearance private int getAppearance(TYPE source) { - final ActivityRecord topFullscreenActivity = getTopFullscreenActivity(source); - final WindowState topFullscreenWindow = topFullscreenActivity != null - ? topFullscreenActivity.findMainWindow() - : null; + final WindowState topFullscreenWindow = getTopFullscreenWindow(source); if (topFullscreenWindow != null) { return topFullscreenWindow.mAttrs.insetsFlags.appearance; } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index e7f4422b5958..fa6ac651a059 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -53,6 +53,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.WindowConfiguration.activityTypeToString; +import static android.app.WindowConfiguration.isFloating; import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION; import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE; import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON; @@ -335,6 +336,7 @@ import android.service.contentcapture.ActivityEvent; import android.service.dreams.DreamActivity; import android.service.voice.IVoiceInteractionSession; import android.util.ArraySet; +import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; import android.util.MergedConfiguration; @@ -8637,14 +8639,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds); } - applySizeOverrideIfNeeded( - mDisplayContent, - info.applicationInfo, - newParentConfiguration, - resolvedConfig, - mOptOutEdgeToEdge, - hasFixedRotationTransform(), - getCompatDisplayInsets() != null); + applySizeOverrideIfNeeded(newParentConfiguration, parentWindowingMode, resolvedConfig); mResolveConfigHint.resetTmpOverrides(); logAppCompatState(); @@ -8654,6 +8649,100 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return Rect.copyOrNull(mResolveConfigHint.mParentAppBoundsOverride); } + /** + * If necessary, override configuration fields related to app bounds. + * This will happen when the app is targeting SDK earlier than 35. + * The insets and configuration has decoupled since SDK level 35, to make the system + * compatible to existing apps, override the configuration with legacy metrics. In legacy + * metrics, fields such as appBounds will exclude some of the system bar areas. + * The override contains all potentially affected fields in Configuration, including + * screenWidthDp, screenHeightDp, smallestScreenWidthDp, and orientation. + * All overrides to those fields should be in this method. + * + * TODO: Consider integrate this with computeConfigByResolveHint() + */ + private void applySizeOverrideIfNeeded(Configuration newParentConfiguration, + int parentWindowingMode, Configuration inOutConfig) { + if (mDisplayContent == null) { + return; + } + final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds(); + int rotation = newParentConfiguration.windowConfiguration.getRotation(); + if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming()) { + rotation = mDisplayContent.getRotation(); + } + if (!mOptOutEdgeToEdge && (!mResolveConfigHint.mUseOverrideInsetsForConfig + || getCompatDisplayInsets() != null + || (isFloating(parentWindowingMode) + // Check the requested windowing mode of activity as well in case it is + // switching between PiP and fullscreen. + && (inOutConfig.windowConfiguration.getWindowingMode() + == WINDOWING_MODE_UNDEFINED + || isFloating(inOutConfig.windowConfiguration.getWindowingMode()))) + || rotation == ROTATION_UNDEFINED)) { + // If the insets configuration decoupled logic is not enabled for the app, or the app + // already has a compat override, or the context doesn't contain enough info to + // calculate the override, skip the override. + return; + } + // Make sure the orientation related fields will be updated by the override insets, because + // fixed rotation has assigned the fields from display's configuration. + if (hasFixedRotationTransform()) { + inOutConfig.windowConfiguration.setAppBounds(null); + inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED; + inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED; + inOutConfig.smallestScreenWidthDp = Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; + inOutConfig.orientation = ORIENTATION_UNDEFINED; + } + + // Override starts here. + final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); + final int dw = rotated ? mDisplayContent.mBaseDisplayHeight + : mDisplayContent.mBaseDisplayWidth; + final int dh = rotated ? mDisplayContent.mBaseDisplayWidth + : mDisplayContent.mBaseDisplayHeight; + final Rect nonDecorInsets = mDisplayContent.getDisplayPolicy() + .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets; + // This should be the only place override the configuration for ActivityRecord. Override + // the value if not calculated yet. + Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); + if (outAppBounds == null || outAppBounds.isEmpty()) { + inOutConfig.windowConfiguration.setAppBounds(parentBounds); + outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); + outAppBounds.inset(nonDecorInsets); + } + float density = inOutConfig.densityDpi; + if (density == Configuration.DENSITY_DPI_UNDEFINED) { + density = newParentConfiguration.densityDpi; + } + density *= DisplayMetrics.DENSITY_DEFAULT_SCALE; + if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) { + inOutConfig.screenWidthDp = (int) (outAppBounds.width() / density + 0.5f); + } + if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { + inOutConfig.screenHeightDp = (int) (outAppBounds.height() / density + 0.5f); + } + if (inOutConfig.smallestScreenWidthDp + == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED + && parentWindowingMode == WINDOWING_MODE_FULLSCREEN) { + // For the case of PIP transition and multi-window environment, the + // smallestScreenWidthDp is handled already. Override only if the app is in + // fullscreen. + final DisplayInfo info = new DisplayInfo(mDisplayContent.getDisplayInfo()); + mDisplayContent.computeSizeRanges(info, rotated, dw, dh, + mDisplayContent.getDisplayMetrics().density, + inOutConfig, true /* overrideConfig */); + } + + // It's possible that screen size will be considered in different orientation with or + // without considering the system bar insets. Override orientation as well. + if (inOutConfig.orientation == ORIENTATION_UNDEFINED) { + inOutConfig.orientation = + (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp) + ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; + } + } + private void computeConfigByResolveHint(@NonNull Configuration resolvedConfig, @NonNull Configuration parentConfig) { task.computeConfigResourceOverrides(resolvedConfig, parentConfig, mResolveConfigHint); @@ -8875,7 +8964,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * <p>Conditions that need to be met: * * <ul> - * <li>{@link LetterboxConfiguration#getIsEducationEnabled} is true. + * <li>{@link AppCompatConfiguration#getIsEducationEnabled} is true. * <li>The activity is eligible for fixed orientation letterbox. * <li>The activity is in fullscreen. * <li>The activity is portrait-only. @@ -8884,7 +8973,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * </ul> */ boolean isEligibleForLetterboxEducation() { - return mWmService.mLetterboxConfiguration.getIsEducationEnabled() + return mWmService.mAppCompatConfiguration.getIsEducationEnabled() && mIsEligibleForFixedOrientationLetterbox && getWindowingMode() == WINDOWING_MODE_FULLSCREEN && getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT diff --git a/services/core/java/com/android/server/wm/ActivityRefresher.java b/services/core/java/com/android/server/wm/ActivityRefresher.java index 0c32dfcf6d43..bc822718d45a 100644 --- a/services/core/java/com/android/server/wm/ActivityRefresher.java +++ b/services/core/java/com/android/server/wm/ActivityRefresher.java @@ -75,7 +75,7 @@ class ActivityRefresher { } final boolean cycleThroughStop = - mWmService.mLetterboxConfiguration + mWmService.mAppCompatConfiguration .isCameraCompatRefreshCycleThroughStopEnabled() && !activity.mAppCompatController.getAppCompatCameraOverrides() .shouldRefreshActivityViaPauseForCameraCompat(); @@ -114,7 +114,7 @@ class ActivityRefresher { private boolean shouldRefreshActivity(@NonNull ActivityRecord activity, @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) { - return mWmService.mLetterboxConfiguration.isCameraCompatRefreshEnabled() + return mWmService.mAppCompatConfiguration.isCameraCompatRefreshEnabled() && activity.mAppCompatController.getAppCompatOverrides() .getAppCompatCameraOverrides().shouldRefreshActivityForCameraCompat() && ArrayUtils.find(mEvaluators.toArray(), evaluator -> diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java index 62fb4bfc74d7..48bc813b0a38 100644 --- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java +++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java @@ -590,10 +590,8 @@ class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord return activity; } - @Override - ActivityRecord getTopFullscreenActivity(ActivityRecord activity) { - final WindowState win = activity.findMainWindow(); - return (win != null && win.mAttrs.isFullscreen()) ? activity : null; + WindowState getTopFullscreenWindow(ActivityRecord activity) { + return activity.findMainWindow(); } @Override diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java index cf008e73321e..d9f11b1635cc 100644 --- a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java +++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java @@ -34,8 +34,8 @@ import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_POSITION_MULTIPLIER_CENTER; -import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_POSITION_MULTIPLIER_CENTER; +import static com.android.server.wm.AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO; import android.annotation.NonNull; import android.content.pm.PackageManager; @@ -62,7 +62,7 @@ class AppCompatAspectRatioOverrides { @NonNull private final ActivityRecord mActivityRecord; @NonNull - private final LetterboxConfiguration mLetterboxConfiguration; + private final AppCompatConfiguration mAppCompatConfiguration; @NonNull private final UserAspectRatioState mUserAspectRatioState; @@ -80,12 +80,12 @@ class AppCompatAspectRatioOverrides { private final Function<Configuration, Float> mGetHorizontalPositionMultiplierProvider; AppCompatAspectRatioOverrides(@NonNull ActivityRecord activityRecord, - @NonNull LetterboxConfiguration letterboxConfiguration, + @NonNull AppCompatConfiguration appCompatConfiguration, @NonNull OptPropFactory optPropBuilder, @NonNull Function<Boolean, Boolean> isDisplayFullScreenAndInPostureProvider, @NonNull Function<Configuration, Float> getHorizontalPositionMultiplierProvider) { mActivityRecord = activityRecord; - mLetterboxConfiguration = letterboxConfiguration; + mAppCompatConfiguration = appCompatConfiguration; mUserAspectRatioState = new UserAspectRatioState(); mIsDisplayFullScreenAndInPostureProvider = isDisplayFullScreenAndInPostureProvider; mGetHorizontalPositionMultiplierProvider = getHorizontalPositionMultiplierProvider; @@ -93,10 +93,10 @@ class AppCompatAspectRatioOverrides { PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE); mAllowUserAspectRatioOverrideOptProp = optPropBuilder.create( PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, - mLetterboxConfiguration::isUserAppAspectRatioSettingsEnabled); + mAppCompatConfiguration::isUserAppAspectRatioSettingsEnabled); mAllowUserAspectRatioFullscreenOverrideOptProp = optPropBuilder.create( PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE, - mLetterboxConfiguration::isUserAppAspectRatioFullscreenEnabled); + mAppCompatConfiguration::isUserAppAspectRatioFullscreenEnabled); mAllowOrientationOverrideOptProp = optPropBuilder.create( PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE); } @@ -147,7 +147,7 @@ class AppCompatAspectRatioOverrides { boolean isUserFullscreenOverrideEnabled() { if (mAllowUserAspectRatioOverrideOptProp.isFalse() || mAllowUserAspectRatioFullscreenOverrideOptProp.isFalse() - || !mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled()) { + || !mAppCompatConfiguration.isUserAppAspectRatioFullscreenEnabled()) { return false; } return true; @@ -168,7 +168,7 @@ class AppCompatAspectRatioOverrides { // effect only if explicitly false. If mBooleanPropertyAllowUserAspectRatioOverride is null, // the current app doesn't opt-out so the first part of the predicate is true. return !mAllowUserAspectRatioOverrideOptProp.isFalse() - && mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled() + && mAppCompatConfiguration.isUserAppAspectRatioSettingsEnabled() && mActivityRecord.mDisplayContent != null && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest(); } @@ -267,11 +267,11 @@ class AppCompatAspectRatioOverrides { } private float getDefaultMinAspectRatioForUnresizableApps() { - if (!mLetterboxConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled() + if (!mAppCompatConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled() || mActivityRecord.getDisplayArea() == null) { - return mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps() + return mAppCompatConfiguration.getDefaultMinAspectRatioForUnresizableApps() > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO - ? mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps() + ? mAppCompatConfiguration.getDefaultMinAspectRatioForUnresizableApps() : getDefaultMinAspectRatio(); } @@ -280,9 +280,9 @@ class AppCompatAspectRatioOverrides { private float getDefaultMinAspectRatio() { if (mActivityRecord.getDisplayArea() == null - || !mLetterboxConfiguration + || !mAppCompatConfiguration .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox()) { - return mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio(); + return mAppCompatConfiguration.getFixedOrientationLetterboxAspectRatio(); } return getDisplaySizeMinAspectRatio(); } diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java index a7d2ecce4984..b23e75a0fbc2 100644 --- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java @@ -26,8 +26,8 @@ import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; -import static com.android.server.wm.LetterboxConfiguration.DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW; -import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO; +import static com.android.server.wm.AppCompatConfiguration.DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW; +import static com.android.server.wm.AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java index 0d108e1d9fd9..93a866380550 100644 --- a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java +++ b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java @@ -52,7 +52,7 @@ class AppCompatCameraOverrides { @NonNull private final AppCompatCameraOverridesState mAppCompatCameraOverridesState; @NonNull - private final LetterboxConfiguration mLetterboxConfiguration; + private final AppCompatConfiguration mAppCompatConfiguration; @NonNull private final OptPropFactory.OptProp mAllowMinAspectRatioOverrideOptProp; @NonNull @@ -63,15 +63,15 @@ class AppCompatCameraOverrides { private final OptPropFactory.OptProp mCameraCompatAllowForceRotationOptProp; AppCompatCameraOverrides(@NonNull ActivityRecord activityRecord, - @NonNull LetterboxConfiguration letterboxConfiguration, + @NonNull AppCompatConfiguration appCompatConfiguration, @NonNull OptPropFactory optPropBuilder) { mActivityRecord = activityRecord; - mLetterboxConfiguration = letterboxConfiguration; + mAppCompatConfiguration = appCompatConfiguration; mAppCompatCameraOverridesState = new AppCompatCameraOverridesState(); mAllowMinAspectRatioOverrideOptProp = optPropBuilder.create( PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE); final BooleanSupplier isCameraCompatTreatmentEnabled = AppCompatUtils.asLazy( - mLetterboxConfiguration::isCameraCompatTreatmentEnabled); + mAppCompatConfiguration::isCameraCompatTreatmentEnabled); mCameraCompatAllowRefreshOptProp = optPropBuilder.create( PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH, isCameraCompatTreatmentEnabled); @@ -214,7 +214,7 @@ class AppCompatCameraOverrides { * is active because the corresponding config is enabled and activity supports resizing. */ boolean isCameraCompatSplitScreenAspectRatioAllowed() { - return mLetterboxConfiguration.isCameraCompatSplitScreenAspectRatioEnabled() + return mAppCompatConfiguration.isCameraCompatSplitScreenAspectRatioEnabled() && !mActivityRecord.shouldCreateCompatDisplayInsets(); } diff --git a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java index c8955fdd7911..1562cf64ad96 100644 --- a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java @@ -45,7 +45,7 @@ class AppCompatCameraPolicy { // Not checking DeviceConfig value here to allow enabling via DeviceConfig // without the need to restart the device. final boolean needsDisplayRotationCompatPolicy = - wmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabledAtBuildTime(); + wmService.mAppCompatConfiguration.isCameraCompatTreatmentEnabledAtBuildTime(); final boolean needsCameraCompatFreeformPolicy = Flags.cameraCompatForFreeform() && DesktopModeHelper.canEnterDesktopMode(wmService.mContext); if (needsDisplayRotationCompatPolicy || needsCameraCompatFreeformPolicy) { diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/AppCompatConfiguration.java index 0161ae5b5473..ffa4251d0ded 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java +++ b/services/core/java/com/android/server/wm/AppCompatConfiguration.java @@ -34,10 +34,10 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.function.Function; -/** Reads letterbox configs from resources and controls their overrides at runtime. */ -final class LetterboxConfiguration { +/** Reads app compatibility configs from resources and controls their overrides at runtime. */ +final class AppCompatConfiguration { - private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxConfiguration" : TAG_ATM; + private static final String TAG = TAG_WITH_CLASS_NAME ? "AppCompatConfiguration" : TAG_ATM; // Whether camera compatibility treatment is enabled. // See DisplayRotationCompatPolicy for context. @@ -183,7 +183,7 @@ final class LetterboxConfiguration { // Responsible for the persistence of letterbox[Horizontal|Vertical]PositionMultiplier @NonNull - private final LetterboxConfigurationPersister mLetterboxConfigurationPersister; + private final AppCompatConfigurationPersister mAppCompatConfigurationPersister; // Aspect ratio of letterbox for fixed orientation, values <= // MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO will be ignored. @@ -307,8 +307,8 @@ final class LetterboxConfiguration { // Flags dynamically updated with {@link android.provider.DeviceConfig}. @NonNull private final SynchedDeviceConfig mDeviceConfig; - LetterboxConfiguration(@NonNull final Context systemUiContext) { - this(systemUiContext, new LetterboxConfigurationPersister( + AppCompatConfiguration(@NonNull final Context systemUiContext) { + this(systemUiContext, new AppCompatConfigurationPersister( () -> readLetterboxHorizontalReachabilityPositionFromConfig( systemUiContext, /* forBookMode */ false), () -> readLetterboxVerticalReachabilityPositionFromConfig( @@ -320,8 +320,8 @@ final class LetterboxConfiguration { } @VisibleForTesting - LetterboxConfiguration(@NonNull final Context systemUiContext, - @NonNull final LetterboxConfigurationPersister letterboxConfigurationPersister) { + AppCompatConfiguration(@NonNull final Context systemUiContext, + @NonNull final AppCompatConfigurationPersister appCompatConfigurationPersister) { mContext = systemUiContext; mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat( @@ -370,8 +370,8 @@ final class LetterboxConfiguration { mThinLetterboxHeightPxSupplier = new DimenPxIntSupplier(mContext, R.dimen.config_letterboxThinLetterboxHeightDp); - mLetterboxConfigurationPersister = letterboxConfigurationPersister; - mLetterboxConfigurationPersister.start(); + mAppCompatConfigurationPersister = appCompatConfigurationPersister; + mAppCompatConfigurationPersister.start(); mDeviceConfig = SynchedDeviceConfig.builder(DeviceConfig.NAMESPACE_WINDOW_MANAGER, systemUiContext.getMainExecutor()) @@ -605,7 +605,7 @@ final class LetterboxConfiguration { /** * Overrides alpha of a black scrim shown over wallpaper for {@link - * #LETTERBOX_BACKGROUND_WALLPAPER} option returned from {@link getLetterboxBackgroundType()}. + * #LETTERBOX_BACKGROUND_WALLPAPER} option returned from {@link #getLetterboxBackgroundType()}. * * <p>If given value is < 0 or >= 1, both it and a value of {@link * com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha} are ignored @@ -632,8 +632,8 @@ final class LetterboxConfiguration { } /** - * Overrides blur radius for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option from - * {@link getLetterboxBackgroundType()}. + * Overrides blur radius for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option from {@link + * #getLetterboxBackgroundType()}. * * <p> If given value <= 0, both it and a value of {@link * com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius} are ignored @@ -645,7 +645,7 @@ final class LetterboxConfiguration { /** * Resets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option returned by {@link - * getLetterboxBackgroundType()} to {@link + * #getLetterboxBackgroundType()} to {@link * com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius}. */ void resetLetterboxBackgroundWallpaperBlurRadiusPx() { @@ -654,14 +654,14 @@ final class LetterboxConfiguration { } /** - * Gets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option returned by {@link - * getLetterboxBackgroundType()}. + * Gets blur radius for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option returned by {@link + * #getLetterboxBackgroundType()}. */ int getLetterboxBackgroundWallpaperBlurRadiusPx() { return mLetterboxBackgroundWallpaperBlurRadiusPx; } - /* + /** * Gets horizontal position of a center of the letterboxed app window specified * in {@link com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier} * or via an ADB command. 0 corresponds to the left side of the screen and 1 to the @@ -672,7 +672,7 @@ final class LetterboxConfiguration { : mLetterboxHorizontalPositionMultiplier; } - /* + /** * Gets vertical position of a center of the letterboxed app window specified * in {@link com.android.internal.R.dimen.config_letterboxVerticalPositionMultiplier} * or via an ADB command. 0 corresponds to the top side of the screen and 1 to the @@ -743,7 +743,7 @@ final class LetterboxConfiguration { "mLetterboxBookModePositionMultiplier"); } - /* + /** * Whether horizontal reachability repositioning is allowed for letterboxed fullscreen apps in * landscape device orientation. */ @@ -751,7 +751,7 @@ final class LetterboxConfiguration { return mIsHorizontalReachabilityEnabled; } - /* + /** * Whether vertical reachability repositioning is allowed for letterboxed fullscreen apps in * portrait device orientation. */ @@ -759,7 +759,7 @@ final class LetterboxConfiguration { return mIsVerticalReachabilityEnabled; } - /* + /** * Whether automatic horizontal reachability repositioning in book mode is allowed for * letterboxed fullscreen apps in landscape device orientation. */ @@ -821,7 +821,7 @@ final class LetterboxConfiguration { R.bool.config_letterboxIsAutomaticReachabilityInBookModeEnabled); } - /* + /** * Gets default horizontal position of the letterboxed app window when horizontal reachability * is enabled. * @@ -833,7 +833,7 @@ final class LetterboxConfiguration { return mDefaultPositionForHorizontalReachability; } - /* + /** * Gets default vertical position of the letterboxed app window when vertical reachability is * enabled. * @@ -889,7 +889,7 @@ final class LetterboxConfiguration { */ void setPersistentLetterboxPositionForHorizontalReachability(boolean forBookMode, @LetterboxHorizontalReachabilityPosition int position) { - mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability( + mAppCompatConfigurationPersister.setLetterboxPositionForHorizontalReachability( forBookMode, position); } @@ -899,7 +899,7 @@ final class LetterboxConfiguration { */ void setPersistentLetterboxPositionForVerticalReachability(boolean forTabletopMode, @LetterboxVerticalReachabilityPosition int position) { - mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability( + mAppCompatConfigurationPersister.setLetterboxPositionForVerticalReachability( forTabletopMode, position); } @@ -909,11 +909,11 @@ final class LetterboxConfiguration { * is enabled to default position. */ void resetPersistentLetterboxPositionForHorizontalReachability() { - mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability( + mAppCompatConfigurationPersister.setLetterboxPositionForHorizontalReachability( false /* forBookMode */, readLetterboxHorizontalReachabilityPositionFromConfig(mContext, false /* forBookMode */)); - mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability( + mAppCompatConfigurationPersister.setLetterboxPositionForHorizontalReachability( true /* forBookMode */, readLetterboxHorizontalReachabilityPositionFromConfig(mContext, true /* forBookMode */)); @@ -925,11 +925,11 @@ final class LetterboxConfiguration { * enabled to default position. */ void resetPersistentLetterboxPositionForVerticalReachability() { - mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability( + mAppCompatConfigurationPersister.setLetterboxPositionForVerticalReachability( false /* forTabletopMode */, readLetterboxVerticalReachabilityPositionFromConfig(mContext, false /* forTabletopMode */)); - mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability( + mAppCompatConfigurationPersister.setLetterboxPositionForVerticalReachability( true /* forTabletopMode */, readLetterboxVerticalReachabilityPositionFromConfig(mContext, true /* forTabletopMode */)); @@ -961,7 +961,7 @@ final class LetterboxConfiguration { ? position : LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER; } - /* + /** * Gets horizontal position of a center of the letterboxed app window when reachability * is enabled specified. 0 corresponds to the left side of the screen and 1 to the right side. * @@ -969,7 +969,7 @@ final class LetterboxConfiguration { */ float getHorizontalMultiplierForReachability(boolean isDeviceInBookMode) { final int letterboxPositionForHorizontalReachability = - mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability( + mAppCompatConfigurationPersister.getLetterboxPositionForHorizontalReachability( isDeviceInBookMode); switch (letterboxPositionForHorizontalReachability) { case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT: @@ -985,7 +985,7 @@ final class LetterboxConfiguration { } } - /* + /** * Gets vertical position of a center of the letterboxed app window when reachability * is enabled specified. 0 corresponds to the top side of the screen and 1 to the bottom side. * @@ -993,7 +993,7 @@ final class LetterboxConfiguration { */ float getVerticalMultiplierForReachability(boolean isDeviceInTabletopMode) { final int letterboxPositionForVerticalReachability = - mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability( + mAppCompatConfigurationPersister.getLetterboxPositionForVerticalReachability( isDeviceInTabletopMode); switch (letterboxPositionForVerticalReachability) { case LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP: @@ -1009,23 +1009,23 @@ final class LetterboxConfiguration { } } - /* + /** * Gets the horizontal position of the letterboxed app window when horizontal reachability is * enabled. */ @LetterboxHorizontalReachabilityPosition int getLetterboxPositionForHorizontalReachability(boolean isInFullScreenBookMode) { - return mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability( + return mAppCompatConfigurationPersister.getLetterboxPositionForHorizontalReachability( isInFullScreenBookMode); } - /* + /** * Gets the vertical position of the letterboxed app window when vertical reachability is * enabled. */ @LetterboxVerticalReachabilityPosition int getLetterboxPositionForVerticalReachability(boolean isInFullScreenTabletopMode) { - return mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability( + return mAppCompatConfigurationPersister.getLetterboxPositionForVerticalReachability( isInFullScreenTabletopMode); } @@ -1197,6 +1197,10 @@ final class LetterboxConfiguration { || mDeviceConfig.getFlagValue(KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY); } + /** + * Sets whether we use the constraints override strategy for letterboxing when dealing + * with translucent activities. + */ void setTranslucentLetterboxingOverrideEnabled( boolean translucentLetterboxingOverrideEnabled) { mTranslucentLetterboxingOverrideEnabled = translucentLetterboxingOverrideEnabled; @@ -1204,8 +1208,8 @@ final class LetterboxConfiguration { /** * Resets whether we use the constraints override strategy for letterboxing when dealing - * with translucent activities - * {@link mDeviceConfig.getFlagValue(KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY)}. + * with translucent activities, which means mDeviceConfig.getFlagValue( + * KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY) will be used. */ void resetTranslucentLetterboxingEnabled() { setTranslucentLetterboxingOverrideEnabled(false); @@ -1215,11 +1219,11 @@ final class LetterboxConfiguration { private void updatePositionForHorizontalReachability(boolean isDeviceInBookMode, Function<Integer, Integer> newHorizonalPositionFun) { final int letterboxPositionForHorizontalReachability = - mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability( + mAppCompatConfigurationPersister.getLetterboxPositionForHorizontalReachability( isDeviceInBookMode); final int nextHorizontalPosition = newHorizonalPositionFun.apply( letterboxPositionForHorizontalReachability); - mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability( + mAppCompatConfigurationPersister.setLetterboxPositionForHorizontalReachability( isDeviceInBookMode, nextHorizontalPosition); } @@ -1227,11 +1231,11 @@ final class LetterboxConfiguration { private void updatePositionForVerticalReachability(boolean isDeviceInTabletopMode, Function<Integer, Integer> newVerticalPositionFun) { final int letterboxPositionForVerticalReachability = - mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability( + mAppCompatConfigurationPersister.getLetterboxPositionForVerticalReachability( isDeviceInTabletopMode); final int nextVerticalPosition = newVerticalPositionFun.apply( letterboxPositionForVerticalReachability); - mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability( + mAppCompatConfigurationPersister.setLetterboxPositionForVerticalReachability( isDeviceInTabletopMode, nextVerticalPosition); } diff --git a/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java b/services/core/java/com/android/server/wm/AppCompatConfigurationPersister.java index 38aa903e3954..852ce0401e2c 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java +++ b/services/core/java/com/android/server/wm/AppCompatConfigurationPersister.java @@ -30,8 +30,8 @@ import android.util.AtomicFile; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.wm.LetterboxConfiguration.LetterboxHorizontalReachabilityPosition; -import com.android.server.wm.LetterboxConfiguration.LetterboxVerticalReachabilityPosition; +import com.android.server.wm.AppCompatConfiguration.LetterboxHorizontalReachabilityPosition; +import com.android.server.wm.AppCompatConfiguration.LetterboxVerticalReachabilityPosition; import com.android.server.wm.nano.WindowManagerProtos; import java.io.ByteArrayOutputStream; @@ -45,12 +45,12 @@ import java.util.function.Supplier; /** * Persists the values of letterboxPositionForHorizontalReachability and - * letterboxPositionForVerticalReachability for {@link LetterboxConfiguration}. + * letterboxPositionForVerticalReachability for {@link AppCompatConfiguration}. */ -class LetterboxConfigurationPersister { +class AppCompatConfigurationPersister { private static final String TAG = - TAG_WITH_CLASS_NAME ? "LetterboxConfigurationPersister" : TAG_WM; + TAG_WITH_CLASS_NAME ? "AppCompatConfigurationPersister" : TAG_WM; private static final String LETTERBOX_CONFIGURATION_FILENAME = "letterbox_config"; @@ -94,7 +94,7 @@ class LetterboxConfigurationPersister { @NonNull private final PersisterQueue mPersisterQueue; - LetterboxConfigurationPersister( + AppCompatConfigurationPersister( @NonNull Supplier<Integer> defaultHorizontalReachabilitySupplier, @NonNull Supplier<Integer> defaultVerticalReachabilitySupplier, @NonNull Supplier<Integer> defaultBookModeReachabilitySupplier, @@ -106,7 +106,7 @@ class LetterboxConfigurationPersister { } @VisibleForTesting - LetterboxConfigurationPersister( + AppCompatConfigurationPersister( @NonNull Supplier<Integer> defaultHorizontalReachabilitySupplier, @NonNull Supplier<Integer> defaultVerticalReachabilitySupplier, @NonNull Supplier<Integer> defaultBookModeReachabilitySupplier, @@ -233,7 +233,7 @@ class LetterboxConfigurationPersister { letterboxData.letterboxPositionForTabletopModeReachability; } catch (IOException ioe) { Slog.e(TAG, - "Error reading from LetterboxConfigurationPersister. " + "Error reading from AppCompatConfigurationPersister. " + "Using default values!", ioe); useDefaultValue(); } finally { @@ -242,7 +242,7 @@ class LetterboxConfigurationPersister { fis.close(); } catch (IOException e) { useDefaultValue(); - Slog.e(TAG, "Error reading from LetterboxConfigurationPersister ", e); + Slog.e(TAG, "Error reading from AppCompatConfigurationPersister ", e); } } } @@ -332,7 +332,7 @@ class LetterboxConfigurationPersister { } catch (IOException ioe) { mFileToUpdate.failWrite(fos); Slog.e(TAG, - "Error writing to LetterboxConfigurationPersister. " + "Error writing to AppCompatConfigurationPersister. " + "Using default values!", ioe); } finally { if (mOnComplete != null) { diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java index 3eed96dd90c2..f9e2507aa1eb 100644 --- a/services/core/java/com/android/server/wm/AppCompatController.java +++ b/services/core/java/com/android/server/wm/AppCompatController.java @@ -44,9 +44,9 @@ class AppCompatController { final OptPropFactory optPropBuilder = new OptPropFactory(packageManager, activityRecord.packageName); mTransparentPolicy = new TransparentPolicy(activityRecord, - wmService.mLetterboxConfiguration); + wmService.mAppCompatConfiguration); mAppCompatOverrides = new AppCompatOverrides(activityRecord, - wmService.mLetterboxConfiguration, optPropBuilder); + wmService.mAppCompatConfiguration, optPropBuilder); mOrientationPolicy = new AppCompatOrientationPolicy(activityRecord, mAppCompatOverrides); mAppCompatAspectRatioPolicy = new AppCompatAspectRatioPolicy(activityRecord, mTransparentPolicy, mAppCompatOverrides); diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java b/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java index 9bf80119e431..0adf825b43ae 100644 --- a/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java +++ b/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java @@ -59,7 +59,7 @@ class AppCompatOrientationOverrides { final OrientationOverridesState mOrientationOverridesState; AppCompatOrientationOverrides(@NonNull ActivityRecord activityRecord, - @NonNull LetterboxConfiguration letterboxConfiguration, + @NonNull AppCompatConfiguration appCompatConfiguration, @NonNull OptPropFactory optPropBuilder, @NonNull AppCompatCameraOverrides appCompatCameraOverrides) { mActivityRecord = activityRecord; @@ -67,7 +67,7 @@ class AppCompatOrientationOverrides { mOrientationOverridesState = new OrientationOverridesState(mActivityRecord, System::currentTimeMillis); final BooleanSupplier isPolicyForIgnoringRequestedOrientationEnabled = asLazy( - letterboxConfiguration::isPolicyForIgnoringRequestedOrientationEnabled); + appCompatConfiguration::isPolicyForIgnoringRequestedOrientationEnabled); mIgnoreRequestedOrientationOptProp = optPropBuilder.create( PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION, isPolicyForIgnoringRequestedOrientationEnabled); diff --git a/services/core/java/com/android/server/wm/AppCompatOverrides.java b/services/core/java/com/android/server/wm/AppCompatOverrides.java index f6f93f9eda7c..b611ba9bb065 100644 --- a/services/core/java/com/android/server/wm/AppCompatOverrides.java +++ b/services/core/java/com/android/server/wm/AppCompatOverrides.java @@ -42,7 +42,7 @@ public class AppCompatOverrides { private static final String TAG = TAG_WITH_CLASS_NAME ? "AppCompatOverrides" : TAG_ATM; @NonNull - private final LetterboxConfiguration mLetterboxConfiguration; + private final AppCompatConfiguration mAppCompatConfiguration; @NonNull private final ActivityRecord mActivityRecord; @@ -63,23 +63,23 @@ public class AppCompatOverrides { private final AppCompatAspectRatioOverrides mAppCompatAspectRatioOverrides; AppCompatOverrides(@NonNull ActivityRecord activityRecord, - @NonNull LetterboxConfiguration letterboxConfiguration, + @NonNull AppCompatConfiguration appCompatConfiguration, @NonNull OptPropFactory optPropBuilder) { - mLetterboxConfiguration = letterboxConfiguration; + mAppCompatConfiguration = appCompatConfiguration; mActivityRecord = activityRecord; mAppCompatCameraOverrides = new AppCompatCameraOverrides(mActivityRecord, - mLetterboxConfiguration, optPropBuilder); + mAppCompatConfiguration, optPropBuilder); mAppCompatOrientationOverrides = new AppCompatOrientationOverrides(mActivityRecord, - mLetterboxConfiguration, optPropBuilder, mAppCompatCameraOverrides); + mAppCompatConfiguration, optPropBuilder, mAppCompatCameraOverrides); // TODO(b/341903757) Remove BooleanSuppliers after fixing dependency with reachability. mAppCompatAspectRatioOverrides = new AppCompatAspectRatioOverrides(activityRecord, - mLetterboxConfiguration, optPropBuilder, + mAppCompatConfiguration, optPropBuilder, activityRecord.mLetterboxUiController::isDisplayFullScreenAndInPosture, activityRecord.mLetterboxUiController::getHorizontalPositionMultiplier); mFakeFocusOptProp = optPropBuilder.create(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS, - mLetterboxConfiguration::isCompatFakeFocusEnabled); + mAppCompatConfiguration::isCompatFakeFocusEnabled); mAllowOrientationOverrideOptProp = optPropBuilder.create( diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index a38ac9ba4b75..efd52026cfec 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -22,23 +22,14 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; -import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.WindowConfiguration.activityTypeToString; -import static android.app.WindowConfiguration.isFloating; import static android.app.WindowConfiguration.windowingModeToString; import static android.app.WindowConfigurationProto.WINDOWING_MODE; import static android.content.ConfigurationProto.WINDOW_CONFIGURATION; -import static android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED; -import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION; -import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; -import static android.content.res.Configuration.ORIENTATION_PORTRAIT; -import static android.content.res.Configuration.ORIENTATION_UNDEFINED; -import static android.view.Surface.ROTATION_270; -import static android.view.Surface.ROTATION_90; import static com.android.server.wm.ConfigurationContainerProto.FULL_CONFIGURATION; import static com.android.server.wm.ConfigurationContainerProto.MERGED_OVERRIDE_CONFIGURATION; @@ -47,14 +38,11 @@ import static com.android.server.wm.ConfigurationContainerProto.OVERRIDE_CONFIGU import android.annotation.CallSuper; import android.annotation.NonNull; import android.app.WindowConfiguration; -import android.content.pm.ApplicationInfo; import android.content.res.Configuration; import android.graphics.Point; import android.graphics.Rect; import android.os.LocaleList; -import android.util.DisplayMetrics; import android.util.proto.ProtoOutputStream; -import android.view.DisplayInfo; import com.android.internal.annotations.VisibleForTesting; @@ -185,111 +173,6 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { mResolvedOverrideConfiguration.setTo(mRequestedOverrideConfiguration); } - /** - * If necessary, override configuration fields related to app bounds. - * This will happen when the app is targeting SDK earlier than 35. - * The insets and configuration has decoupled since SDK level 35, to make the system - * compatible to existing apps, override the configuration with legacy metrics. In legacy - * metrics, fields such as appBounds will exclude some of the system bar areas. - * The override contains all potentially affected fields in Configuration, including - * screenWidthDp, screenHeightDp, smallestScreenWidthDp, and orientation. - * All overrides to those fields should be in this method. - * - * TODO: Consider integrate this with computeConfigByResolveHint() - */ - static void applySizeOverrideIfNeeded(DisplayContent displayContent, ApplicationInfo appInfo, - Configuration newParentConfiguration, Configuration inOutConfig, - boolean optOutEdgeToEdge, boolean hasFixedRotationTransform, - boolean hasCompatDisplayInsets) { - if (displayContent == null) { - return; - } - final boolean useOverrideInsetsForConfig = - displayContent.mWmService.mFlags.mInsetsDecoupledConfiguration - ? !appInfo.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED) - && !appInfo.isChangeEnabled( - OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION) - : appInfo.isChangeEnabled(OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION); - final int parentWindowingMode = - newParentConfiguration.windowConfiguration.getWindowingMode(); - final boolean isFloating = isFloating(parentWindowingMode) - // Check the requested windowing mode of activity as well in case it is - // switching between PiP and fullscreen. - && (inOutConfig.windowConfiguration.getWindowingMode() == WINDOWING_MODE_UNDEFINED - || isFloating(inOutConfig.windowConfiguration.getWindowingMode())); - final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds(); - int rotation = newParentConfiguration.windowConfiguration.getRotation(); - if (rotation == ROTATION_UNDEFINED && !hasFixedRotationTransform) { - rotation = displayContent.getRotation(); - } - if (!optOutEdgeToEdge && (!useOverrideInsetsForConfig - || hasCompatDisplayInsets - || isFloating - || rotation == ROTATION_UNDEFINED)) { - // If the insets configuration decoupled logic is not enabled for the app, or the app - // already has a compat override, or the context doesn't contain enough info to - // calculate the override, skip the override. - return; - } - // Make sure the orientation related fields will be updated by the override insets, because - // fixed rotation has assigned the fields from display's configuration. - if (hasFixedRotationTransform) { - inOutConfig.windowConfiguration.setAppBounds(null); - inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED; - inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED; - inOutConfig.smallestScreenWidthDp = Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; - inOutConfig.orientation = ORIENTATION_UNDEFINED; - } - - // Override starts here. - final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); - final int dw = rotated - ? displayContent.mBaseDisplayHeight - : displayContent.mBaseDisplayWidth; - final int dh = rotated - ? displayContent.mBaseDisplayWidth - : displayContent.mBaseDisplayHeight; - final Rect nonDecorFrame = displayContent.getDisplayPolicy() - .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorFrame; - // This should be the only place override the configuration for ActivityRecord. Override - // the value if not calculated yet. - Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); - if (outAppBounds == null || outAppBounds.isEmpty()) { - inOutConfig.windowConfiguration.setAppBounds(parentBounds); - outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); - outAppBounds.intersect(nonDecorFrame); - } - float density = inOutConfig.densityDpi; - if (density == Configuration.DENSITY_DPI_UNDEFINED) { - density = newParentConfiguration.densityDpi; - } - density *= DisplayMetrics.DENSITY_DEFAULT_SCALE; - if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) { - inOutConfig.screenWidthDp = (int) (outAppBounds.width() / density + 0.5f); - } - if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { - inOutConfig.screenHeightDp = (int) (outAppBounds.height() / density + 0.5f); - } - if (inOutConfig.smallestScreenWidthDp == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED - && parentWindowingMode == WINDOWING_MODE_FULLSCREEN) { - // For the case of PIP transition and multi-window environment, the - // smallestScreenWidthDp is handled already. Override only if the app is in - // fullscreen. - final DisplayInfo info = new DisplayInfo(displayContent.getDisplayInfo()); - displayContent.computeSizeRanges(info, rotated, dw, dh, - displayContent.getDisplayMetrics().density, - inOutConfig, true /* overrideConfig */); - } - - // It's possible that screen size will be considered in different orientation with or - // without considering the system bar insets. Override orientation as well. - if (inOutConfig.orientation == ORIENTATION_UNDEFINED) { - inOutConfig.orientation = (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp) - ? ORIENTATION_PORTRAIT - : ORIENTATION_LANDSCAPE; - } - } - /** Returns {@code true} if requested override override configuration is not empty. */ boolean hasRequestedOverrideConfiguration() { return mHasOverrideConfiguration; diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index d2976b01acbb..8272e1609e0d 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -326,7 +326,7 @@ public class DisplayRotation { DisplayRotationImmersiveAppCompatPolicy initImmersiveAppCompatPolicy( WindowManagerService service, DisplayContent displayContent) { return DisplayRotationImmersiveAppCompatPolicy.createIfNeeded( - service.mLetterboxConfiguration, this, displayContent); + service.mAppCompatConfiguration, this, displayContent); } // Change the default value to the value specified in the sysprop diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java index 1a0124a1d4de..63fe94c33061 100644 --- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java @@ -247,7 +247,7 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp * </ul> */ private boolean isTreatmentEnabledForDisplay() { - return mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabled() + return mWmService.mAppCompatConfiguration.isCameraCompatTreatmentEnabled() && mDisplayContent.getIgnoreOrientationRequest() // TODO(b/225928882): Support camera compat rotation for external displays && mDisplayContent.getDisplay().getType() == TYPE_INTERNAL; diff --git a/services/core/java/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicy.java index de70c4df7985..094434d07cfe 100644 --- a/services/core/java/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicy.java @@ -40,28 +40,28 @@ final class DisplayRotationImmersiveAppCompatPolicy { @Nullable static DisplayRotationImmersiveAppCompatPolicy createIfNeeded( - @NonNull final LetterboxConfiguration letterboxConfiguration, + @NonNull final AppCompatConfiguration appCompatConfiguration, @NonNull final DisplayRotation displayRotation, @NonNull final DisplayContent displayContent) { - if (!letterboxConfiguration + if (!appCompatConfiguration .isDisplayRotationImmersiveAppCompatPolicyEnabledAtBuildTime()) { return null; } return new DisplayRotationImmersiveAppCompatPolicy( - letterboxConfiguration, displayRotation, displayContent); + appCompatConfiguration, displayRotation, displayContent); } private final DisplayRotation mDisplayRotation; - private final LetterboxConfiguration mLetterboxConfiguration; + private final AppCompatConfiguration mAppCompatConfiguration; private final DisplayContent mDisplayContent; private DisplayRotationImmersiveAppCompatPolicy( - @NonNull final LetterboxConfiguration letterboxConfiguration, + @NonNull final AppCompatConfiguration appCompatConfiguration, @NonNull final DisplayRotation displayRotation, @NonNull final DisplayContent displayContent) { mDisplayRotation = displayRotation; - mLetterboxConfiguration = letterboxConfiguration; + mAppCompatConfiguration = appCompatConfiguration; mDisplayContent = displayContent; } @@ -80,14 +80,14 @@ final class DisplayRotationImmersiveAppCompatPolicy { * <li>Rotation will lead to letterboxing due to fixed orientation. * <li>{@link DisplayContent#getIgnoreOrientationRequest} is {@code true} * <li>This policy is enabled on the device, for details see - * {@link LetterboxConfiguration#isDisplayRotationImmersiveAppCompatPolicyEnabled} + * {@link AppCompatConfiguration#isDisplayRotationImmersiveAppCompatPolicyEnabled} * </ul> * * @param proposedRotation new proposed {@link Surface.Rotation} for the screen. * @return {@code true}, if there is a need to lock screen rotation, {@code false} otherwise. */ boolean isRotationLockEnforced(@Surface.Rotation final int proposedRotation) { - if (!mLetterboxConfiguration.isDisplayRotationImmersiveAppCompatPolicyEnabled()) { + if (!mAppCompatConfiguration.isDisplayRotationImmersiveAppCompatPolicyEnabled()) { return false; } synchronized (mDisplayContent.mWmService.mGlobalLock) { diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 235d6cd09d35..be8e806a5752 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -37,17 +37,17 @@ import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHA import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__TOP_TO_CENTER; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP; -import static com.android.server.wm.LetterboxConfiguration.letterboxBackgroundTypeToString; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_WALLPAPER; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP; +import static com.android.server.wm.AppCompatConfiguration.letterboxBackgroundTypeToString; import android.annotation.NonNull; import android.annotation.Nullable; @@ -67,7 +67,7 @@ import android.view.WindowManager; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.LetterboxDetails; -import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType; +import com.android.server.wm.AppCompatConfiguration.LetterboxBackgroundType; import com.android.window.flags.Flags; import java.io.PrintWriter; @@ -81,7 +81,7 @@ final class LetterboxUiController { private final Point mTmpPoint = new Point(); - private final LetterboxConfiguration mLetterboxConfiguration; + private final AppCompatConfiguration mAppCompatConfiguration; private final ActivityRecord mActivityRecord; @@ -95,7 +95,7 @@ final class LetterboxUiController { private boolean mDoubleTapEvent; LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord) { - mLetterboxConfiguration = wmService.mLetterboxConfiguration; + mAppCompatConfiguration = wmService.mAppCompatConfiguration; // Given activityRecord may not be fully constructed since LetterboxUiController // is created in its constructor. It shouldn't be used in this constructor but it's safe // to use it after since controller is only used in ActivityRecord. @@ -345,7 +345,7 @@ final class LetterboxUiController { private boolean shouldLetterboxHaveRoundedCorners() { // TODO(b/214030873): remove once background is drawn for transparent activities // Letterbox shouldn't have rounded corners if the activity is transparent - return mLetterboxConfiguration.isLetterboxActivityCornersRounded() + return mAppCompatConfiguration.isLetterboxActivityCornersRounded() && mActivityRecord.fillsParent(); } @@ -382,13 +382,13 @@ final class LetterboxUiController { return isHorizontalReachabilityEnabled(parentConfiguration) // Using the last global dynamic position to avoid "jumps" when moving // between apps or activities. - ? mLetterboxConfiguration.getHorizontalMultiplierForReachability(bookModeEnabled) - : mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier(bookModeEnabled); + ? mAppCompatConfiguration.getHorizontalMultiplierForReachability(bookModeEnabled) + : mAppCompatConfiguration.getLetterboxHorizontalPositionMultiplier(bookModeEnabled); } private boolean isFullScreenAndBookModeEnabled() { return isDisplayFullScreenAndInPosture(/* isTabletop */ false) - && mLetterboxConfiguration.getIsAutomaticReachabilityInBookModeEnabled(); + && mAppCompatConfiguration.getIsAutomaticReachabilityInBookModeEnabled(); } float getVerticalPositionMultiplier(Configuration parentConfiguration) { @@ -398,8 +398,8 @@ final class LetterboxUiController { return isVerticalReachabilityEnabled(parentConfiguration) // Using the last global dynamic position to avoid "jumps" when moving // between apps or activities. - ? mLetterboxConfiguration.getVerticalMultiplierForReachability(tabletopMode) - : mLetterboxConfiguration.getLetterboxVerticalPositionMultiplier(tabletopMode); + ? mAppCompatConfiguration.getVerticalMultiplierForReachability(tabletopMode) + : mAppCompatConfiguration.getLetterboxVerticalPositionMultiplier(tabletopMode); } float getFixedOrientationLetterboxAspectRatio(@NonNull Configuration parentConfiguration) { @@ -408,14 +408,14 @@ final class LetterboxUiController { } boolean isLetterboxEducationEnabled() { - return mLetterboxConfiguration.getIsEducationEnabled(); + return mAppCompatConfiguration.getIsEducationEnabled(); } /** * @return {@value true} if the resulting app is letterboxed in a way defined as thin. */ boolean isVerticalThinLetterboxed() { - final int thinHeight = mLetterboxConfiguration.getThinLetterboxHeightPx(); + final int thinHeight = mAppCompatConfiguration.getThinLetterboxHeightPx(); if (thinHeight < 0) { return false; } @@ -432,7 +432,7 @@ final class LetterboxUiController { * @return {@value true} if the resulting app is pillarboxed in a way defined as thin. */ boolean isHorizontalThinLetterboxed() { - final int thinWidth = mLetterboxConfiguration.getThinLetterboxWidthPx(); + final int thinWidth = mAppCompatConfiguration.getThinLetterboxWidthPx(); if (thinWidth < 0) { return false; } @@ -477,17 +477,17 @@ final class LetterboxUiController { .shouldOverrideMinAspectRatio(); } - @LetterboxConfiguration.LetterboxVerticalReachabilityPosition + @AppCompatConfiguration.LetterboxVerticalReachabilityPosition int getLetterboxPositionForVerticalReachability() { final boolean isInFullScreenTabletopMode = isDisplayFullScreenAndSeparatingHinge(); - return mLetterboxConfiguration.getLetterboxPositionForVerticalReachability( + return mAppCompatConfiguration.getLetterboxPositionForVerticalReachability( isInFullScreenTabletopMode); } - @LetterboxConfiguration.LetterboxHorizontalReachabilityPosition + @AppCompatConfiguration.LetterboxHorizontalReachabilityPosition int getLetterboxPositionForHorizontalReachability() { final boolean isInFullScreenBookMode = isFullScreenAndBookModeEnabled(); - return mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability( + return mAppCompatConfiguration.getLetterboxPositionForHorizontalReachability( isInFullScreenBookMode); } @@ -503,12 +503,12 @@ final class LetterboxUiController { } boolean isInFullScreenBookMode = isDisplayFullScreenAndSeparatingHinge() - && mLetterboxConfiguration.getIsAutomaticReachabilityInBookModeEnabled(); - int letterboxPositionForHorizontalReachability = mLetterboxConfiguration + && mAppCompatConfiguration.getIsAutomaticReachabilityInBookModeEnabled(); + int letterboxPositionForHorizontalReachability = mAppCompatConfiguration .getLetterboxPositionForHorizontalReachability(isInFullScreenBookMode); if (mLetterbox.getInnerFrame().left > x) { // Moving to the next stop on the left side of the app window: right > center > left. - mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextLeftStop( + mAppCompatConfiguration.movePositionForHorizontalReachabilityToNextLeftStop( isInFullScreenBookMode); int changeToLog = letterboxPositionForHorizontalReachability @@ -519,7 +519,7 @@ final class LetterboxUiController { mDoubleTapEvent = true; } else if (mLetterbox.getInnerFrame().right < x) { // Moving to the next stop on the right side of the app window: left > center > right. - mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextRightStop( + mAppCompatConfiguration.movePositionForHorizontalReachabilityToNextRightStop( isInFullScreenBookMode); int changeToLog = letterboxPositionForHorizontalReachability @@ -544,11 +544,11 @@ final class LetterboxUiController { return; } boolean isInFullScreenTabletopMode = isDisplayFullScreenAndSeparatingHinge(); - int letterboxPositionForVerticalReachability = mLetterboxConfiguration + int letterboxPositionForVerticalReachability = mAppCompatConfiguration .getLetterboxPositionForVerticalReachability(isInFullScreenTabletopMode); if (mLetterbox.getInnerFrame().top > y) { // Moving to the next stop on the top side of the app window: bottom > center > top. - mLetterboxConfiguration.movePositionForVerticalReachabilityToNextTopStop( + mAppCompatConfiguration.movePositionForVerticalReachabilityToNextTopStop( isInFullScreenTabletopMode); int changeToLog = letterboxPositionForVerticalReachability @@ -559,7 +559,7 @@ final class LetterboxUiController { mDoubleTapEvent = true; } else if (mLetterbox.getInnerFrame().bottom < y) { // Moving to the next stop on the bottom side of the app window: top > center > bottom. - mLetterboxConfiguration.movePositionForVerticalReachabilityToNextBottomStop( + mAppCompatConfiguration.movePositionForVerticalReachabilityToNextBottomStop( isInFullScreenTabletopMode); int changeToLog = letterboxPositionForVerticalReachability @@ -596,7 +596,7 @@ final class LetterboxUiController { .getTransparentPolicy().getFirstOpaqueActivity() .map(ActivityRecord::getScreenResolvedBounds) .orElse(mActivityRecord.getScreenResolvedBounds()); - return mLetterboxConfiguration.getIsHorizontalReachabilityEnabled() + return mAppCompatConfiguration.getIsHorizontalReachabilityEnabled() && parentConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FULLSCREEN // Check whether the activity fills the parent vertically. @@ -641,7 +641,7 @@ final class LetterboxUiController { .getTransparentPolicy().getFirstOpaqueActivity() .map(ActivityRecord::getScreenResolvedBounds) .orElse(mActivityRecord.getScreenResolvedBounds()); - return mLetterboxConfiguration.getIsVerticalReachabilityEnabled() + return mAppCompatConfiguration.getIsVerticalReachabilityEnabled() && parentConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FULLSCREEN // Check whether the activity fills the parent horizontally. @@ -681,7 +681,7 @@ final class LetterboxUiController { return Color.valueOf(Color.BLACK); } @LetterboxBackgroundType int letterboxBackgroundType = - mLetterboxConfiguration.getLetterboxBackgroundType(); + mAppCompatConfiguration.getLetterboxBackgroundType(); TaskDescription taskDescription = mActivityRecord.taskDescription; switch (letterboxBackgroundType) { case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING: @@ -697,7 +697,7 @@ final class LetterboxUiController { case LETTERBOX_BACKGROUND_WALLPAPER: if (hasWallpaperBackgroundForLetterbox()) { // Color is used for translucent scrim that dims wallpaper. - return mLetterboxConfiguration.getLetterboxBackgroundColor(); + return mAppCompatConfiguration.getLetterboxBackgroundColor(); } Slog.w(TAG, "Wallpaper option is selected for letterbox background but " + "blur is not supported by a device or not supported in the current " @@ -705,14 +705,14 @@ final class LetterboxUiController { + "provided so using solid color background"); break; case LETTERBOX_BACKGROUND_SOLID_COLOR: - return mLetterboxConfiguration.getLetterboxBackgroundColor(); + return mAppCompatConfiguration.getLetterboxBackgroundColor(); default: throw new AssertionError( "Unexpected letterbox background type: " + letterboxBackgroundType); } // If picked option configured incorrectly or not supported then default to a solid color // background. - return mLetterboxConfiguration.getLetterboxBackgroundColor(); + return mAppCompatConfiguration.getLetterboxBackgroundColor(); } private void updateRoundedCornersIfNeeded(final WindowState mainWindow) { @@ -771,7 +771,7 @@ final class LetterboxUiController { private boolean requiresRoundedCorners(final WindowState mainWindow) { return isLetterboxedNotForDisplayCutout(mainWindow) - && mLetterboxConfiguration.isLetterboxActivityCornersRounded(); + && mAppCompatConfiguration.isLetterboxActivityCornersRounded(); } // Returns rounded corners radius the letterboxed activity should have based on override in @@ -785,8 +785,8 @@ final class LetterboxUiController { } final int radius; - if (mLetterboxConfiguration.getLetterboxActivityCornersRadius() >= 0) { - radius = mLetterboxConfiguration.getLetterboxActivityCornersRadius(); + if (mAppCompatConfiguration.getLetterboxActivityCornersRadius() >= 0) { + radius = mAppCompatConfiguration.getLetterboxActivityCornersRadius(); } else { final InsetsState insetsState = mainWindow.getInsetsState(); radius = Math.min( @@ -850,7 +850,7 @@ final class LetterboxUiController { private void updateWallpaperForLetterbox(WindowState mainWindow) { @LetterboxBackgroundType int letterboxBackgroundType = - mLetterboxConfiguration.getLetterboxBackgroundType(); + mAppCompatConfiguration.getLetterboxBackgroundType(); boolean wallpaperShouldBeShown = letterboxBackgroundType == LETTERBOX_BACKGROUND_WALLPAPER // Don't use wallpaper as a background if letterboxed for display cutout. @@ -868,18 +868,18 @@ final class LetterboxUiController { } private int getLetterboxWallpaperBlurRadiusPx() { - int blurRadius = mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadiusPx(); + int blurRadius = mAppCompatConfiguration.getLetterboxBackgroundWallpaperBlurRadiusPx(); return Math.max(blurRadius, 0); } private float getLetterboxWallpaperDarkScrimAlpha() { - float alpha = mLetterboxConfiguration.getLetterboxBackgroundWallpaperDarkScrimAlpha(); + float alpha = mAppCompatConfiguration.getLetterboxBackgroundWallpaperDarkScrimAlpha(); // No scrim by default. return (alpha < 0 || alpha >= 1) ? 0.0f : alpha; } private boolean isLetterboxWallpaperBlurSupported() { - return mLetterboxConfiguration.mContext.getSystemService(WindowManager.class) + return mAppCompatConfiguration.mContext.getSystemService(WindowManager.class) .isCrossWindowBlurEnabled(); } @@ -911,10 +911,10 @@ final class LetterboxUiController { getLetterboxBackgroundColor().toArgb())); pw.println(prefix + " letterboxBackgroundType=" + letterboxBackgroundTypeToString( - mLetterboxConfiguration.getLetterboxBackgroundType())); + mAppCompatConfiguration.getLetterboxBackgroundType())); pw.println(prefix + " letterboxCornerRadius=" + getRoundedCornersRadius(mainWin)); - if (mLetterboxConfiguration.getLetterboxBackgroundType() + if (mAppCompatConfiguration.getLetterboxBackgroundType() == LETTERBOX_BACKGROUND_WALLPAPER) { pw.println(prefix + " isLetterboxWallpaperBlurSupported=" + isLetterboxWallpaperBlurSupported()); @@ -932,19 +932,19 @@ final class LetterboxUiController { pw.println(prefix + " letterboxVerticalPositionMultiplier=" + getVerticalPositionMultiplier(mActivityRecord.getParent().getConfiguration())); pw.println(prefix + " letterboxPositionForHorizontalReachability=" - + LetterboxConfiguration.letterboxHorizontalReachabilityPositionToString( - mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability(false))); + + AppCompatConfiguration.letterboxHorizontalReachabilityPositionToString( + mAppCompatConfiguration.getLetterboxPositionForHorizontalReachability(false))); pw.println(prefix + " letterboxPositionForVerticalReachability=" - + LetterboxConfiguration.letterboxVerticalReachabilityPositionToString( - mLetterboxConfiguration.getLetterboxPositionForVerticalReachability(false))); + + AppCompatConfiguration.letterboxVerticalReachabilityPositionToString( + mAppCompatConfiguration.getLetterboxPositionForVerticalReachability(false))); pw.println(prefix + " fixedOrientationLetterboxAspectRatio=" - + mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio()); + + mAppCompatConfiguration.getFixedOrientationLetterboxAspectRatio()); pw.println(prefix + " defaultMinAspectRatioForUnresizableApps=" - + mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps()); + + mAppCompatConfiguration.getDefaultMinAspectRatioForUnresizableApps()); pw.println(prefix + " isSplitScreenAspectRatioForUnresizableAppsEnabled=" - + mLetterboxConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled()); + + mAppCompatConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled()); pw.println(prefix + " isDisplayAspectRatioEnabledForFixedOrientationLetterbox=" - + mLetterboxConfiguration + + mAppCompatConfiguration .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox()); } @@ -971,7 +971,7 @@ final class LetterboxUiController { } private int letterboxHorizontalReachabilityPositionToLetterboxPosition( - @LetterboxConfiguration.LetterboxHorizontalReachabilityPosition int position) { + @AppCompatConfiguration.LetterboxHorizontalReachabilityPosition int position) { switch (position) { case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT: return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__LEFT; @@ -987,7 +987,7 @@ final class LetterboxUiController { } private int letterboxVerticalReachabilityPositionToLetterboxPosition( - @LetterboxConfiguration.LetterboxVerticalReachabilityPosition int position) { + @AppCompatConfiguration.LetterboxVerticalReachabilityPosition int position) { switch (position) { case LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP: return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP; @@ -1005,13 +1005,13 @@ final class LetterboxUiController { int getLetterboxPositionForLogging() { int positionToLog = APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION; if (isHorizontalReachabilityEnabled()) { - int letterboxPositionForHorizontalReachability = mLetterboxConfiguration + int letterboxPositionForHorizontalReachability = mAppCompatConfiguration .getLetterboxPositionForHorizontalReachability( isDisplayFullScreenAndInPosture(/* isTabletop */ false)); positionToLog = letterboxHorizontalReachabilityPositionToLetterboxPosition( letterboxPositionForHorizontalReachability); } else if (isVerticalReachabilityEnabled()) { - int letterboxPositionForVerticalReachability = mLetterboxConfiguration + int letterboxPositionForVerticalReachability = mAppCompatConfiguration .getLetterboxPositionForVerticalReachability( isDisplayFullScreenAndInPosture(/* isTabletop */ true)); positionToLog = letterboxVerticalReachabilityPositionToLetterboxPosition( diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java index cff40c768381..a1e6701f75fd 100644 --- a/services/core/java/com/android/server/wm/StartingSurfaceController.java +++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java @@ -146,7 +146,10 @@ public class StartingSurfaceController { + activity); return null; } - final WindowState mainWindow = activity.findMainWindow(false); + // For snapshot surface, the top activity could be trampoline activity, so here should + // search for top fullscreen activity in the task. + final WindowState mainWindow = task + .getTopFullscreenMainWindow(false /* includeStartingApp */); if (mainWindow == null) { Slog.w(TAG, "TaskSnapshotSurface.create: no main window in " + activity); return null; diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 74f8a0683811..8f83a7c2c853 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3007,11 +3007,17 @@ class Task extends TaskFragment { return r.getTask().mTaskId != taskId && r.token != notTop && r.canBeTopRunning(); } - ActivityRecord getTopFullscreenActivity() { - return getActivity((r) -> { - final WindowState win = r.findMainWindow(); - return (win != null && win.mAttrs.isFullscreen()); + WindowState getTopFullscreenMainWindow(boolean includeStartingApp) { + final WindowState[] candidate = new WindowState[1]; + getActivity((r) -> { + final WindowState win = r.findMainWindow(includeStartingApp); + if (win != null && win.mAttrs.isFullscreen()) { + candidate[0] = win; + return true; + } + return false; }); + return candidate[0]; } /** @@ -3377,7 +3383,7 @@ class Task extends TaskFragment { // Whether the direct top activity is in size compat mode appCompatTaskInfo.topActivityInSizeCompat = isTopActivityVisible && top.inSizeCompatMode(); if (appCompatTaskInfo.topActivityInSizeCompat - && mWmService.mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) { + && mWmService.mAppCompatConfiguration.isTranslucentLetterboxingEnabled()) { // We hide the restart button in case of transparent activities. appCompatTaskInfo.topActivityInSizeCompat = top.fillsParent(); } @@ -3579,14 +3585,12 @@ class Task extends TaskFragment { // starting window because persisted configuration does not effect to Task. info.taskInfo.configuration.setTo(activity.getConfiguration()); if (!Flags.drawSnapshotAspectRatioMatch()) { - final ActivityRecord topFullscreenActivity = getTopFullscreenActivity(); - if (topFullscreenActivity != null) { - final WindowState mainWindow = topFullscreenActivity.findMainWindow(false); - if (mainWindow != null) { - info.topOpaqueWindowInsetsState = - mainWindow.getInsetsStateWithVisibilityOverride(); - info.topOpaqueWindowLayoutParams = mainWindow.getAttrs(); - } + final WindowState mainWindow = + getTopFullscreenMainWindow(false /* includeStartingApp */); + if (mainWindow != null) { + info.topOpaqueWindowInsetsState = + mainWindow.getInsetsStateWithVisibilityOverride(); + info.topOpaqueWindowLayoutParams = mainWindow.getAttrs(); } } return info; diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index 4218f8f88a07..57c7753d5c4b 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -261,8 +261,8 @@ class TaskSnapshotController extends AbsAppSnapshotController<Task, TaskSnapshot } @Override - ActivityRecord getTopFullscreenActivity(Task source) { - return source.getTopFullscreenActivity(); + WindowState getTopFullscreenWindow(Task source) { + return source.getTopFullscreenMainWindow(true /* includeStartingApp */); } @Override diff --git a/services/core/java/com/android/server/wm/TransparentPolicy.java b/services/core/java/com/android/server/wm/TransparentPolicy.java index cdb14ab1dc0a..2f46103fdf17 100644 --- a/services/core/java/com/android/server/wm/TransparentPolicy.java +++ b/services/core/java/com/android/server/wm/TransparentPolicy.java @@ -73,10 +73,10 @@ class TransparentPolicy { private final TransparentPolicyState mTransparentPolicyState; TransparentPolicy(@NonNull ActivityRecord activityRecord, - @NonNull LetterboxConfiguration letterboxConfiguration) { + @NonNull AppCompatConfiguration appCompatConfiguration) { mActivityRecord = activityRecord; mIsTranslucentLetterboxingEnabledSupplier = - letterboxConfiguration::isTranslucentLetterboxingEnabled; + appCompatConfiguration::isTranslucentLetterboxingEnabled; mTransparentPolicyState = new TransparentPolicyState(activityRecord); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index ebbf6e346e91..acd8b3f1dbc3 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -120,10 +120,10 @@ import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_W import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY; import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL; import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_WALLPAPER; import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS; import static com.android.server.wm.SensitiveContentPackages.PackageInfo; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL; @@ -1054,7 +1054,7 @@ public class WindowManagerService extends IWindowManager.Stub private boolean mAnimationsDisabled = false; boolean mPointerLocationEnabled = false; - final LetterboxConfiguration mLetterboxConfiguration; + final AppCompatConfiguration mAppCompatConfiguration; private boolean mIsIgnoreOrientationRequestDisabled; @@ -1294,7 +1294,7 @@ public class WindowManagerService extends IWindowManager.Stub | WindowInsets.Type.navigationBars(); } - mLetterboxConfiguration = new LetterboxConfiguration( + mAppCompatConfiguration = new AppCompatConfiguration( // Using SysUI context to have access to Material colors extracted from Wallpaper. ActivityThread.currentActivityThread().getSystemUiContext()); @@ -4441,7 +4441,7 @@ public class WindowManagerService extends IWindowManager.Stub */ boolean isIgnoreOrientationRequestDisabled() { return mIsIgnoreOrientationRequestDisabled - || !mLetterboxConfiguration.isIgnoreOrientationRequestAllowed(); + || !mAppCompatConfiguration.isIgnoreOrientationRequestAllowed(); } @Override @@ -9992,7 +9992,7 @@ public class WindowManagerService extends IWindowManager.Stub */ @Override public int getLetterboxBackgroundColorInArgb() { - return mLetterboxConfiguration.getLetterboxBackgroundColor().toArgb(); + return mAppCompatConfiguration.getLetterboxBackgroundColor().toArgb(); } /** @@ -10000,8 +10000,8 @@ public class WindowManagerService extends IWindowManager.Stub */ @Override public boolean isLetterboxBackgroundMultiColored() { - @LetterboxConfiguration.LetterboxBackgroundType int letterboxBackgroundType = - mLetterboxConfiguration.getLetterboxBackgroundType(); + @AppCompatConfiguration.LetterboxBackgroundType int letterboxBackgroundType = + mAppCompatConfiguration.getLetterboxBackgroundType(); switch (letterboxBackgroundType) { case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING: case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND: diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java index 6febe80166f4..51d5bc099bd6 100644 --- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java +++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java @@ -19,16 +19,16 @@ package com.android.server.wm; import static android.os.Build.IS_USER; import static android.view.CrossWindowBlurListeners.CROSS_WINDOW_BLUR_SUPPORTED; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_WALLPAPER; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP; import android.content.res.Resources.NotFoundException; import android.graphics.Color; @@ -50,12 +50,12 @@ import android.view.ViewDebug; import com.android.internal.os.ByteTransferPipe; import com.android.internal.protolog.LegacyProtoLogImpl; import com.android.internal.protolog.PerfettoProtoLogImpl; -import com.android.internal.protolog.common.IProtoLog; import com.android.internal.protolog.ProtoLog; +import com.android.internal.protolog.common.IProtoLog; import com.android.server.IoThread; -import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType; -import com.android.server.wm.LetterboxConfiguration.LetterboxHorizontalReachabilityPosition; -import com.android.server.wm.LetterboxConfiguration.LetterboxVerticalReachabilityPosition; +import com.android.server.wm.AppCompatConfiguration.LetterboxBackgroundType; +import com.android.server.wm.AppCompatConfiguration.LetterboxHorizontalReachabilityPosition; +import com.android.server.wm.AppCompatConfiguration.LetterboxVerticalReachabilityPosition; import java.io.IOException; import java.io.PrintWriter; @@ -78,12 +78,12 @@ public class WindowManagerShellCommand extends ShellCommand { // Internal service impl -- must perform security checks before touching. private final WindowManagerService mInternal; - private final LetterboxConfiguration mLetterboxConfiguration; + private final AppCompatConfiguration mAppCompatConfiguration; public WindowManagerShellCommand(WindowManagerService service) { mInterface = service; mInternal = service; - mLetterboxConfiguration = service.mLetterboxConfiguration; + mAppCompatConfiguration = service.mAppCompatConfiguration; } @Override @@ -678,7 +678,7 @@ public class WindowManagerShellCommand extends ShellCommand { return -1; } synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(aspectRatio); + mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(aspectRatio); } return 0; } @@ -698,7 +698,7 @@ public class WindowManagerShellCommand extends ShellCommand { return -1; } synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setDefaultMinAspectRatioForUnresizableApps(aspectRatio); + mAppCompatConfiguration.setDefaultMinAspectRatioForUnresizableApps(aspectRatio); } return 0; } @@ -717,7 +717,7 @@ public class WindowManagerShellCommand extends ShellCommand { return -1; } synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setLetterboxActivityCornersRadius(cornersRadius); + mAppCompatConfiguration.setLetterboxActivityCornersRadius(cornersRadius); } return 0; } @@ -752,7 +752,7 @@ public class WindowManagerShellCommand extends ShellCommand { return -1; } synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setLetterboxBackgroundTypeOverride(backgroundType); + mAppCompatConfiguration.setLetterboxBackgroundTypeOverride(backgroundType); } return 0; } @@ -770,7 +770,7 @@ public class WindowManagerShellCommand extends ShellCommand { return -1; } synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setLetterboxBackgroundColorResourceId(colorId); + mAppCompatConfiguration.setLetterboxBackgroundColorResourceId(colorId); } return 0; } @@ -787,7 +787,7 @@ public class WindowManagerShellCommand extends ShellCommand { return -1; } synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setLetterboxBackgroundColor(color); + mAppCompatConfiguration.setLetterboxBackgroundColor(color); } return 0; } @@ -809,7 +809,7 @@ public class WindowManagerShellCommand extends ShellCommand { synchronized (mInternal.mGlobalLock) { final int radiusPx = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, radiusDp, mInternal.mContext.getResources().getDisplayMetrics()); - mLetterboxConfiguration.setLetterboxBackgroundWallpaperBlurRadiusPx(radiusPx); + mAppCompatConfiguration.setLetterboxBackgroundWallpaperBlurRadiusPx(radiusPx); } return 0; } @@ -829,7 +829,7 @@ public class WindowManagerShellCommand extends ShellCommand { return -1; } synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setLetterboxBackgroundWallpaperDarkScrimAlpha(alpha); + mAppCompatConfiguration.setLetterboxBackgroundWallpaperDarkScrimAlpha(alpha); } return 0; } @@ -849,7 +849,7 @@ public class WindowManagerShellCommand extends ShellCommand { } synchronized (mInternal.mGlobalLock) { try { - mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(multiplier); + mAppCompatConfiguration.setLetterboxHorizontalPositionMultiplier(multiplier); } catch (IllegalArgumentException e) { getErrPrintWriter().println("Error: invalid multiplier value " + e); return -1; @@ -873,7 +873,7 @@ public class WindowManagerShellCommand extends ShellCommand { } synchronized (mInternal.mGlobalLock) { try { - mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(multiplier); + mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier(multiplier); } catch (IllegalArgumentException e) { getErrPrintWriter().println("Error: invalid multiplier value " + e); return -1; @@ -908,7 +908,7 @@ public class WindowManagerShellCommand extends ShellCommand { return -1; } synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setDefaultPositionForHorizontalReachability(position); + mAppCompatConfiguration.setDefaultPositionForHorizontalReachability(position); } return 0; } @@ -939,7 +939,7 @@ public class WindowManagerShellCommand extends ShellCommand { return -1; } synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setDefaultPositionForVerticalReachability(position); + mAppCompatConfiguration.setDefaultPositionForVerticalReachability(position); } return 0; } @@ -970,7 +970,7 @@ public class WindowManagerShellCommand extends ShellCommand { return -1; } synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setPersistentLetterboxPositionForHorizontalReachability( + mAppCompatConfiguration.setPersistentLetterboxPositionForHorizontalReachability( false /* IsInBookMode */, position); } return 0; @@ -1002,7 +1002,7 @@ public class WindowManagerShellCommand extends ShellCommand { return -1; } synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setPersistentLetterboxPositionForVerticalReachability( + mAppCompatConfiguration.setPersistentLetterboxPositionForVerticalReachability( false /* forTabletopMode */, position); } return 0; @@ -1074,15 +1074,15 @@ public class WindowManagerShellCommand extends ShellCommand { runSetLetterboxVerticalPositionMultiplier(pw); break; case "--isHorizontalReachabilityEnabled": - runSetBooleanFlag(pw, mLetterboxConfiguration + runSetBooleanFlag(pw, mAppCompatConfiguration ::setIsHorizontalReachabilityEnabled); break; case "--isVerticalReachabilityEnabled": - runSetBooleanFlag(pw, mLetterboxConfiguration + runSetBooleanFlag(pw, mAppCompatConfiguration ::setIsVerticalReachabilityEnabled); break; case "--isAutomaticReachabilityInBookModeEnabled": - runSetBooleanFlag(pw, mLetterboxConfiguration + runSetBooleanFlag(pw, mAppCompatConfiguration ::setIsAutomaticReachabilityInBookModeEnabled); break; case "--defaultPositionForHorizontalReachability": @@ -1098,34 +1098,34 @@ public class WindowManagerShellCommand extends ShellCommand { runSetPersistentLetterboxPositionForVerticalReachability(pw); break; case "--isEducationEnabled": - runSetBooleanFlag(pw, mLetterboxConfiguration::setIsEducationEnabled); + runSetBooleanFlag(pw, mAppCompatConfiguration::setIsEducationEnabled); break; case "--isSplitScreenAspectRatioForUnresizableAppsEnabled": - runSetBooleanFlag(pw, mLetterboxConfiguration + runSetBooleanFlag(pw, mAppCompatConfiguration ::setIsSplitScreenAspectRatioForUnresizableAppsEnabled); break; case "--isDisplayAspectRatioEnabledForFixedOrientationLetterbox": - runSetBooleanFlag(pw, mLetterboxConfiguration + runSetBooleanFlag(pw, mAppCompatConfiguration ::setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox); break; case "--isTranslucentLetterboxingEnabled": - runSetBooleanFlag(pw, mLetterboxConfiguration + runSetBooleanFlag(pw, mAppCompatConfiguration ::setTranslucentLetterboxingOverrideEnabled); break; case "--isUserAppAspectRatioSettingsEnabled": - runSetBooleanFlag(pw, mLetterboxConfiguration + runSetBooleanFlag(pw, mAppCompatConfiguration ::setUserAppAspectRatioSettingsOverrideEnabled); break; case "--isUserAppAspectRatioFullscreenEnabled": - runSetBooleanFlag(pw, mLetterboxConfiguration + runSetBooleanFlag(pw, mAppCompatConfiguration ::setUserAppAspectRatioFullscreenOverrideEnabled); break; case "--isCameraCompatRefreshEnabled": - runSetBooleanFlag(pw, mLetterboxConfiguration::setCameraCompatRefreshEnabled); + runSetBooleanFlag(pw, mAppCompatConfiguration::setCameraCompatRefreshEnabled); break; case "--isCameraCompatRefreshCycleThroughStopEnabled": runSetBooleanFlag(pw, - mLetterboxConfiguration::setCameraCompatRefreshCycleThroughStopEnabled); + mAppCompatConfiguration::setCameraCompatRefreshCycleThroughStopEnabled); break; default: getErrPrintWriter().println( @@ -1145,77 +1145,77 @@ public class WindowManagerShellCommand extends ShellCommand { String arg = getNextArg(); switch (arg) { case "aspectRatio": - mLetterboxConfiguration.resetFixedOrientationLetterboxAspectRatio(); + mAppCompatConfiguration.resetFixedOrientationLetterboxAspectRatio(); break; case "minAspectRatioForUnresizable": - mLetterboxConfiguration.resetDefaultMinAspectRatioForUnresizableApps(); + mAppCompatConfiguration.resetDefaultMinAspectRatioForUnresizableApps(); break; case "cornerRadius": - mLetterboxConfiguration.resetLetterboxActivityCornersRadius(); + mAppCompatConfiguration.resetLetterboxActivityCornersRadius(); break; case "backgroundType": - mLetterboxConfiguration.resetLetterboxBackgroundType(); + mAppCompatConfiguration.resetLetterboxBackgroundType(); break; case "backgroundColor": - mLetterboxConfiguration.resetLetterboxBackgroundColor(); + mAppCompatConfiguration.resetLetterboxBackgroundColor(); break; case "wallpaperBlurRadius": - mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadiusPx(); + mAppCompatConfiguration.resetLetterboxBackgroundWallpaperBlurRadiusPx(); break; case "wallpaperDarkScrimAlpha": - mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha(); + mAppCompatConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha(); break; case "horizontalPositionMultiplier": - mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier(); + mAppCompatConfiguration.resetLetterboxHorizontalPositionMultiplier(); break; case "verticalPositionMultiplier": - mLetterboxConfiguration.resetLetterboxVerticalPositionMultiplier(); + mAppCompatConfiguration.resetLetterboxVerticalPositionMultiplier(); break; case "isHorizontalReachabilityEnabled": - mLetterboxConfiguration.resetIsHorizontalReachabilityEnabled(); + mAppCompatConfiguration.resetIsHorizontalReachabilityEnabled(); break; case "isVerticalReachabilityEnabled": - mLetterboxConfiguration.resetIsVerticalReachabilityEnabled(); + mAppCompatConfiguration.resetIsVerticalReachabilityEnabled(); break; case "defaultPositionForHorizontalReachability": - mLetterboxConfiguration.resetDefaultPositionForHorizontalReachability(); + mAppCompatConfiguration.resetDefaultPositionForHorizontalReachability(); break; case "defaultPositionForVerticalReachability": - mLetterboxConfiguration.resetDefaultPositionForVerticalReachability(); + mAppCompatConfiguration.resetDefaultPositionForVerticalReachability(); break; case "persistentPositionForHorizontalReachability": - mLetterboxConfiguration + mAppCompatConfiguration .resetPersistentLetterboxPositionForHorizontalReachability(); break; case "persistentPositionForVerticalReachability": - mLetterboxConfiguration + mAppCompatConfiguration .resetPersistentLetterboxPositionForVerticalReachability(); break; case "isEducationEnabled": - mLetterboxConfiguration.resetIsEducationEnabled(); + mAppCompatConfiguration.resetIsEducationEnabled(); break; case "isSplitScreenAspectRatioForUnresizableAppsEnabled": - mLetterboxConfiguration + mAppCompatConfiguration .resetIsSplitScreenAspectRatioForUnresizableAppsEnabled(); break; case "IsDisplayAspectRatioEnabledForFixedOrientationLetterbox": - mLetterboxConfiguration + mAppCompatConfiguration .resetIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(); break; case "isTranslucentLetterboxingEnabled": - mLetterboxConfiguration.resetTranslucentLetterboxingEnabled(); + mAppCompatConfiguration.resetTranslucentLetterboxingEnabled(); break; case "isUserAppAspectRatioSettingsEnabled": - mLetterboxConfiguration.resetUserAppAspectRatioSettingsEnabled(); + mAppCompatConfiguration.resetUserAppAspectRatioSettingsEnabled(); break; case "isUserAppAspectRatioFullscreenEnabled": - mLetterboxConfiguration.resetUserAppAspectRatioFullscreenEnabled(); + mAppCompatConfiguration.resetUserAppAspectRatioFullscreenEnabled(); break; case "isCameraCompatRefreshEnabled": - mLetterboxConfiguration.resetCameraCompatRefreshEnabled(); + mAppCompatConfiguration.resetCameraCompatRefreshEnabled(); break; case "isCameraCompatRefreshCycleThroughStopEnabled": - mLetterboxConfiguration + mAppCompatConfiguration .resetCameraCompatRefreshCycleThroughStopEnabled(); break; default: @@ -1304,104 +1304,104 @@ public class WindowManagerShellCommand extends ShellCommand { private void resetLetterboxStyle() { synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.resetFixedOrientationLetterboxAspectRatio(); - mLetterboxConfiguration.resetDefaultMinAspectRatioForUnresizableApps(); - mLetterboxConfiguration.resetLetterboxActivityCornersRadius(); - mLetterboxConfiguration.resetLetterboxBackgroundType(); - mLetterboxConfiguration.resetLetterboxBackgroundColor(); - mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadiusPx(); - mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha(); - mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier(); - mLetterboxConfiguration.resetLetterboxVerticalPositionMultiplier(); - mLetterboxConfiguration.resetIsHorizontalReachabilityEnabled(); - mLetterboxConfiguration.resetIsVerticalReachabilityEnabled(); - mLetterboxConfiguration.resetEnabledAutomaticReachabilityInBookMode(); - mLetterboxConfiguration.resetDefaultPositionForHorizontalReachability(); - mLetterboxConfiguration.resetDefaultPositionForVerticalReachability(); - mLetterboxConfiguration.resetPersistentLetterboxPositionForHorizontalReachability(); - mLetterboxConfiguration.resetPersistentLetterboxPositionForVerticalReachability(); - mLetterboxConfiguration.resetIsEducationEnabled(); - mLetterboxConfiguration.resetIsSplitScreenAspectRatioForUnresizableAppsEnabled(); - mLetterboxConfiguration.resetIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(); - mLetterboxConfiguration.resetTranslucentLetterboxingEnabled(); - mLetterboxConfiguration.resetUserAppAspectRatioSettingsEnabled(); - mLetterboxConfiguration.resetUserAppAspectRatioFullscreenEnabled(); - mLetterboxConfiguration.resetCameraCompatRefreshEnabled(); - mLetterboxConfiguration.resetCameraCompatRefreshCycleThroughStopEnabled(); + mAppCompatConfiguration.resetFixedOrientationLetterboxAspectRatio(); + mAppCompatConfiguration.resetDefaultMinAspectRatioForUnresizableApps(); + mAppCompatConfiguration.resetLetterboxActivityCornersRadius(); + mAppCompatConfiguration.resetLetterboxBackgroundType(); + mAppCompatConfiguration.resetLetterboxBackgroundColor(); + mAppCompatConfiguration.resetLetterboxBackgroundWallpaperBlurRadiusPx(); + mAppCompatConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha(); + mAppCompatConfiguration.resetLetterboxHorizontalPositionMultiplier(); + mAppCompatConfiguration.resetLetterboxVerticalPositionMultiplier(); + mAppCompatConfiguration.resetIsHorizontalReachabilityEnabled(); + mAppCompatConfiguration.resetIsVerticalReachabilityEnabled(); + mAppCompatConfiguration.resetEnabledAutomaticReachabilityInBookMode(); + mAppCompatConfiguration.resetDefaultPositionForHorizontalReachability(); + mAppCompatConfiguration.resetDefaultPositionForVerticalReachability(); + mAppCompatConfiguration.resetPersistentLetterboxPositionForHorizontalReachability(); + mAppCompatConfiguration.resetPersistentLetterboxPositionForVerticalReachability(); + mAppCompatConfiguration.resetIsEducationEnabled(); + mAppCompatConfiguration.resetIsSplitScreenAspectRatioForUnresizableAppsEnabled(); + mAppCompatConfiguration.resetIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(); + mAppCompatConfiguration.resetTranslucentLetterboxingEnabled(); + mAppCompatConfiguration.resetUserAppAspectRatioSettingsEnabled(); + mAppCompatConfiguration.resetUserAppAspectRatioFullscreenEnabled(); + mAppCompatConfiguration.resetCameraCompatRefreshEnabled(); + mAppCompatConfiguration.resetCameraCompatRefreshCycleThroughStopEnabled(); } } private int runGetLetterboxStyle(PrintWriter pw) throws RemoteException { synchronized (mInternal.mGlobalLock) { pw.println("Corner radius: " - + mLetterboxConfiguration.getLetterboxActivityCornersRadius()); + + mAppCompatConfiguration.getLetterboxActivityCornersRadius()); pw.println("Horizontal position multiplier: " - + mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier( + + mAppCompatConfiguration.getLetterboxHorizontalPositionMultiplier( false /* isInBookMode */)); pw.println("Vertical position multiplier: " - + mLetterboxConfiguration.getLetterboxVerticalPositionMultiplier( + + mAppCompatConfiguration.getLetterboxVerticalPositionMultiplier( false /* isInTabletopMode */)); pw.println("Horizontal position multiplier (book mode): " - + mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier( + + mAppCompatConfiguration.getLetterboxHorizontalPositionMultiplier( true /* isInBookMode */)); pw.println("Vertical position multiplier (tabletop mode): " - + mLetterboxConfiguration.getLetterboxVerticalPositionMultiplier( + + mAppCompatConfiguration.getLetterboxVerticalPositionMultiplier( true /* isInTabletopMode */)); pw.println("Horizontal position multiplier for reachability: " - + mLetterboxConfiguration.getHorizontalMultiplierForReachability( + + mAppCompatConfiguration.getHorizontalMultiplierForReachability( false /* isInBookMode */)); pw.println("Vertical position multiplier for reachability: " - + mLetterboxConfiguration.getVerticalMultiplierForReachability( + + mAppCompatConfiguration.getVerticalMultiplierForReachability( false /* isInTabletopMode */)); pw.println("Aspect ratio: " - + mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio()); + + mAppCompatConfiguration.getFixedOrientationLetterboxAspectRatio()); pw.println("Default min aspect ratio for unresizable apps: " - + mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps()); + + mAppCompatConfiguration.getDefaultMinAspectRatioForUnresizableApps()); pw.println("Is horizontal reachability enabled: " - + mLetterboxConfiguration.getIsHorizontalReachabilityEnabled()); + + mAppCompatConfiguration.getIsHorizontalReachabilityEnabled()); pw.println("Is vertical reachability enabled: " - + mLetterboxConfiguration.getIsVerticalReachabilityEnabled()); + + mAppCompatConfiguration.getIsVerticalReachabilityEnabled()); pw.println("Is automatic reachability in book mode enabled: " - + mLetterboxConfiguration.getIsAutomaticReachabilityInBookModeEnabled()); + + mAppCompatConfiguration.getIsAutomaticReachabilityInBookModeEnabled()); pw.println("Default position for horizontal reachability: " - + LetterboxConfiguration.letterboxHorizontalReachabilityPositionToString( - mLetterboxConfiguration.getDefaultPositionForHorizontalReachability())); + + AppCompatConfiguration.letterboxHorizontalReachabilityPositionToString( + mAppCompatConfiguration.getDefaultPositionForHorizontalReachability())); pw.println("Default position for vertical reachability: " - + LetterboxConfiguration.letterboxVerticalReachabilityPositionToString( - mLetterboxConfiguration.getDefaultPositionForVerticalReachability())); + + AppCompatConfiguration.letterboxVerticalReachabilityPositionToString( + mAppCompatConfiguration.getDefaultPositionForVerticalReachability())); pw.println("Current position for horizontal reachability:" - + LetterboxConfiguration.letterboxHorizontalReachabilityPositionToString( - mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability(false))); + + AppCompatConfiguration.letterboxHorizontalReachabilityPositionToString( + mAppCompatConfiguration.getLetterboxPositionForHorizontalReachability(false))); pw.println("Current position for vertical reachability:" - + LetterboxConfiguration.letterboxVerticalReachabilityPositionToString( - mLetterboxConfiguration.getLetterboxPositionForVerticalReachability(false))); + + AppCompatConfiguration.letterboxVerticalReachabilityPositionToString( + mAppCompatConfiguration.getLetterboxPositionForVerticalReachability(false))); pw.println("Is education enabled: " - + mLetterboxConfiguration.getIsEducationEnabled()); + + mAppCompatConfiguration.getIsEducationEnabled()); pw.println("Is using split screen aspect ratio as aspect ratio for unresizable apps: " - + mLetterboxConfiguration + + mAppCompatConfiguration .getIsSplitScreenAspectRatioForUnresizableAppsEnabled()); pw.println("Is using display aspect ratio as aspect ratio for all letterboxed apps: " - + mLetterboxConfiguration + + mAppCompatConfiguration .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox()); pw.println(" Is activity \"refresh\" in camera compatibility treatment enabled: " - + mLetterboxConfiguration.isCameraCompatRefreshEnabled()); + + mAppCompatConfiguration.isCameraCompatRefreshEnabled()); pw.println(" Refresh using \"stopped -> resumed\" cycle: " - + mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()); + + mAppCompatConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()); pw.println("Background type: " - + LetterboxConfiguration.letterboxBackgroundTypeToString( - mLetterboxConfiguration.getLetterboxBackgroundType())); + + AppCompatConfiguration.letterboxBackgroundTypeToString( + mAppCompatConfiguration.getLetterboxBackgroundType())); pw.println(" Background color: " + Integer.toHexString( - mLetterboxConfiguration.getLetterboxBackgroundColor().toArgb())); + mAppCompatConfiguration.getLetterboxBackgroundColor().toArgb())); pw.println(" Wallpaper blur radius: " - + mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadiusPx()); + + mAppCompatConfiguration.getLetterboxBackgroundWallpaperBlurRadiusPx()); pw.println(" Wallpaper dark scrim alpha: " - + mLetterboxConfiguration.getLetterboxBackgroundWallpaperDarkScrimAlpha()); + + mAppCompatConfiguration.getLetterboxBackgroundWallpaperDarkScrimAlpha()); pw.println("Is letterboxing for translucent activities enabled: " - + mLetterboxConfiguration.isTranslucentLetterboxingEnabled()); + + mAppCompatConfiguration.isTranslucentLetterboxingEnabled()); pw.println("Is the user aspect ratio settings enabled: " - + mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()); + + mAppCompatConfiguration.isUserAppAspectRatioSettingsEnabled()); pw.println("Is the fullscreen option in user aspect ratio settings enabled: " - + mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled()); + + mAppCompatConfiguration.isUserAppAspectRatioFullscreenEnabled()); } return 0; } @@ -1539,13 +1539,13 @@ public class WindowManagerShellCommand extends ShellCommand { pw.println(" Sets letterbox style using the following options:"); pw.println(" --aspectRatio aspectRatio"); pw.println(" Aspect ratio of letterbox for fixed orientation. If aspectRatio <= " - + LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO); + + AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO); pw.println(" both it and R.dimen.config_fixedOrientationLetterboxAspectRatio will"); pw.println(" be ignored and framework implementation will determine aspect ratio."); pw.println(" --minAspectRatioForUnresizable aspectRatio"); pw.println(" Default min aspect ratio for unresizable apps which is used when an"); pw.println(" app is eligible for the size compat mode. If aspectRatio <= " - + LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO); + + AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO); pw.println(" both it and R.dimen.config_fixedOrientationLetterboxAspectRatio will"); pw.println(" be ignored and framework implementation will determine aspect ratio."); pw.println(" --cornerRadius radius"); diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index bbd9c0a820ef..60d3e787cac4 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -1655,22 +1655,6 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio // Otherwise if other places send wpc.getConfiguration() to client, the configuration may // be ignored due to the seq is older. resolvedConfig.seq = newParentConfig.seq; - - if (mConfigActivityRecord != null) { - // Let the activity decide whether to apply the size override. - return; - } - final DisplayContent displayContent = mAtm.mWindowManager != null - ? mAtm.mWindowManager.getDefaultDisplayContentLocked() - : null; - applySizeOverrideIfNeeded( - displayContent, - mInfo, - newParentConfig, - resolvedConfig, - false /* optOutEdgeToEdge */, - false /* hasFixedRotationTransform */, - false /* hasCompatDisplayInsets */); } void dispatchConfiguration(@NonNull Configuration config) { diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index 624c8971d36f..c6aea5a290e9 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -2137,6 +2137,14 @@ public final class DisplayPowerControllerTest { private void setUpDisplay(int displayId, String uniqueId, LogicalDisplay logicalDisplayMock, DisplayDevice displayDeviceMock, DisplayDeviceConfig displayDeviceConfigMock, boolean isEnabled) { + + setUpDisplay(displayId, uniqueId, logicalDisplayMock, displayDeviceMock, + displayDeviceConfigMock, isEnabled, "display_name"); + } + + private void setUpDisplay(int displayId, String uniqueId, LogicalDisplay logicalDisplayMock, + DisplayDevice displayDeviceMock, DisplayDeviceConfig displayDeviceConfigMock, + boolean isEnabled, String displayName) { DisplayInfo info = new DisplayInfo(); DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo(); deviceInfo.uniqueId = uniqueId; @@ -2148,6 +2156,7 @@ public final class DisplayPowerControllerTest { when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false); when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo); when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId); + when(displayDeviceMock.getNameLocked()).thenReturn(displayName); when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock); when(displayDeviceConfigMock.getProximitySensor()).thenReturn( new SensorData(Sensor.STRING_TYPE_PROXIMITY, null)); diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java index 397d77c52f68..26f6e91d29c8 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java @@ -43,10 +43,14 @@ public final class BrightnessEventTest { mBrightnessEvent = new BrightnessEvent(1); mBrightnessEvent.setReason( getReason(BrightnessReason.REASON_DOZE, BrightnessReason.MODIFIER_LOW_POWER)); - mBrightnessEvent.setPhysicalDisplayId("test"); + mBrightnessEvent.setPhysicalDisplayId("987654321"); + mBrightnessEvent.setPhysicalDisplayName("display_name"); mBrightnessEvent.setDisplayState(Display.STATE_ON); mBrightnessEvent.setDisplayPolicy(POLICY_BRIGHT); mBrightnessEvent.setLux(100.0f); + mBrightnessEvent.setPercent(46.5f); + mBrightnessEvent.setNits(893.8f); + mBrightnessEvent.setUnclampedBrightness(0.65f); mBrightnessEvent.setPreThresholdLux(150.0f); mBrightnessEvent.setTime(System.currentTimeMillis()); mBrightnessEvent.setInitialBrightness(25.0f); @@ -77,12 +81,13 @@ public final class BrightnessEventTest { public void testToStringWorksAsExpected() { String actualString = mBrightnessEvent.toString(false); String expectedString = - "BrightnessEvent: disp=1, physDisp=test, displayState=ON, displayPolicy=BRIGHT," - + " brt=0.6, initBrt=25.0, rcmdBrt=0.6, preBrt=NaN, lux=100.0, preLux=150.0," - + " hbmMax=0.62, hbmMode=off, rbcStrength=-1, thrmMax=0.65, powerFactor=0.2," - + " wasShortTermModelActive=true, flags=, reason=doze [ low_pwr ]," - + " autoBrightness=true, strategy=" + DISPLAY_BRIGHTNESS_STRATEGY_NAME - + ", autoBrightnessMode=idle"; + "BrightnessEvent: brt=0.6 (46.5%), nits= 893.8, lux=100.0, reason=doze [ " + + "low_pwr ], strat=strategy_name, state=ON, policy=BRIGHT, flags=, " + + "initBrt=25.0, rcmdBrt=0.6, preBrt=NaN, preLux=150.0, " + + "wasShortTermModelActive=true, autoBrightness=true (idle), " + + "unclampedBrt=0.65, hbmMax=0.62, hbmMode=off, thrmMax=0.65, " + + "rbcStrength=-1, powerFactor=0.2, physDisp=display_name(987654321), " + + "logicalId=1"; assertEquals(expectedString, actualString); } diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp index f2b4136c51ed..b2a5b02c49e1 100644 --- a/services/tests/powerstatstests/Android.bp +++ b/services/tests/powerstatstests/Android.bp @@ -59,6 +59,7 @@ android_ravenwood_test { name: "PowerStatsTestsRavenwood", static_libs: [ "services.core", + "platformprotosnano", "coretests-aidl", "ravenwood-junit", "truth", diff --git a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java index ac1f7d0e345f..62efbc3cfa35 100644 --- a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.internal.os; +package com.android.server.power.stats; import static android.os.BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE; @@ -23,39 +23,262 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import android.os.AggregateBatteryConsumer; import android.os.BatteryConsumer; import android.os.BatteryUsageStats; +import android.os.Process; import android.os.UidBatteryConsumer; import android.os.nano.BatteryUsageStatsAtomsProto; import android.os.nano.BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage; +import android.platform.test.ravenwood.RavenwoodRule; +import android.util.StatsEvent; import androidx.test.filters.SmallTest; +import com.android.server.am.BatteryStatsService; + import com.google.protobuf.nano.InvalidProtocolBufferNanoException; +import org.junit.Rule; import org.junit.Test; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; - @SmallTest -public class BatteryUsageStatsPulledTest { +public class BatteryUsageStatsAtomTest { + + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); private static final int UID_0 = 1000; private static final int UID_1 = 2000; private static final int UID_2 = 3000; private static final int UID_3 = 4000; - private static final int[] UID_USAGE_TIME_PROCESS_STATES = { - BatteryConsumer.PROCESS_STATE_FOREGROUND, - BatteryConsumer.PROCESS_STATE_BACKGROUND, - BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE - }; @Test - public void testGetStatsProto() { + public void testAtom_BatteryUsageStatsPerUid() { + final BatteryUsageStats bus = buildBatteryUsageStats(); + BatteryStatsService.FrameworkStatsLogger statsLogger = + mock(BatteryStatsService.FrameworkStatsLogger.class); + + List<StatsEvent> actual = new ArrayList<>(); + new BatteryStatsService.StatsPerUidLogger(statsLogger).logStats(bus, actual); + + // Device-wide totals + verify(statsLogger).buildStatsEvent( + 1000L, + 20000L, + 10000L, + 20, + 1234L, + Process.INVALID_UID, + BatteryConsumer.PROCESS_STATE_UNSPECIFIED, + 0L, + "cpu", + 30000.0f, + 20100.0f, + 20300L + ); + verify(statsLogger).buildStatsEvent( + 1000L, + 20000L, + 10000L, + 20, + 1234L, + Process.INVALID_UID, + BatteryConsumer.PROCESS_STATE_UNSPECIFIED, + 0L, + "camera", + 30000.0f, + 20150.0f, + 0L + ); + verify(statsLogger).buildStatsEvent( + 1000L, + 20000L, + 10000L, + 20, + 1234L, + Process.INVALID_UID, + BatteryConsumer.PROCESS_STATE_UNSPECIFIED, + 0L, + "CustomConsumer1", + 30000.0f, + 20200.0f, + 20400L + ); + + // Per-proc state estimates for UID_0 + verify(statsLogger).buildStatsEvent( + 1000L, + 20000L, + 10000L, + 20, + 1234L, + UID_0, + BatteryConsumer.PROCESS_STATE_UNSPECIFIED, + 0L, + "screen", + 1650.0f, + 300.0f, + 0L + ); + verify(statsLogger).buildStatsEvent( + 1000L, + 20000L, + 10000L, + 20, + 1234L, + UID_0, + BatteryConsumer.PROCESS_STATE_UNSPECIFIED, + 0L, + "cpu", + 1650.0f, + 400.0f, + 600L + ); + verify(statsLogger).buildStatsEvent( + 1000L, + 20000L, + 10000L, + 20, + 1234L, + UID_0, + BatteryConsumer.PROCESS_STATE_FOREGROUND, + 1000L, + "cpu", + 1650.0f, + 9100.0f, + 8100L + ); + verify(statsLogger).buildStatsEvent( + 1000L, + 20000L, + 10000L, + 20, + 1234L, + UID_0, + BatteryConsumer.PROCESS_STATE_BACKGROUND, + 2000L, + "cpu", + 1650.0f, + 9200.0f, + 8200L + ); + verify(statsLogger).buildStatsEvent( + 1000L, + 20000L, + 10000L, + 20, + 1234L, + UID_0, + BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE, + 0L, + "cpu", + 1650.0f, + 9300.0f, + 8400L + ); + verify(statsLogger).buildStatsEvent( + 1000L, + 20000L, + 10000L, + 20, + 1234L, + UID_0, + BatteryConsumer.PROCESS_STATE_CACHED, + 0L, + "cpu", + 1650.0f, + 9400.0f, + 0L + ); + verify(statsLogger).buildStatsEvent( + 1000L, + 20000L, + 10000L, + 20, + 1234L, + UID_0, + BatteryConsumer.PROCESS_STATE_FOREGROUND, + 1000L, + "CustomConsumer1", + 1650.0f, + 450.0f, + 0L + ); + verify(statsLogger).buildStatsEvent( + 1000L, + 20000L, + 10000L, + 20, + 1234L, + UID_0, + BatteryConsumer.PROCESS_STATE_BACKGROUND, + 2000L, + "CustomConsumer1", + 1650.0f, + 450.0f, + 0L + ); + verify(statsLogger).buildStatsEvent( + 1000L, + 20000L, + 10000L, + 20, + 1234L, + UID_0, + BatteryConsumer.PROCESS_STATE_FOREGROUND, + 1000L, + "CustomConsumer2", + 1650.0f, + 500.0f, + 800L + ); + verify(statsLogger).buildStatsEvent( + 1000L, + 20000L, + 10000L, + 20, + 1234L, + UID_0, + BatteryConsumer.PROCESS_STATE_BACKGROUND, + 2000L, + "CustomConsumer2", + 1650.0f, + 500.0f, + 800L + ); + + // Nothing for UID_1, because its power consumption is 0 + + // Only "screen" is populated for UID_2 + verify(statsLogger).buildStatsEvent( + 1000L, + 20000L, + 10000L, + 20, + 1234L, + UID_2, + BatteryConsumer.PROCESS_STATE_UNSPECIFIED, + 0L, + "screen", + 766.0f, + 766.0f, + 0L + ); + + verifyNoMoreInteractions(statsLogger); + } + + @Test + public void testAtom_BatteryUsageStatsAtomsProto() { final BatteryUsageStats bus = buildBatteryUsageStats(); final byte[] bytes = bus.getStatsProto(); BatteryUsageStatsAtomsProto proto; @@ -68,9 +291,7 @@ public class BatteryUsageStatsPulledTest { assertEquals(bus.getStatsStartTimestamp(), proto.sessionStartMillis); assertEquals(bus.getStatsEndTimestamp(), proto.sessionEndMillis); - assertEquals( - bus.getStatsEndTimestamp() - bus.getStatsStartTimestamp(), - proto.sessionDurationMillis); + assertEquals(10000, proto.sessionDurationMillis); assertEquals(bus.getDischargePercentage(), proto.sessionDischargePercentage); assertEquals(bus.getDischargeDurationMs(), proto.dischargeDurationMillis); @@ -90,8 +311,8 @@ public class BatteryUsageStatsPulledTest { final List<android.os.UidBatteryConsumer> uidConsumers = bus.getUidBatteryConsumers(); uidConsumers.sort((a, b) -> a.getUid() - b.getUid()); - final BatteryUsageStatsAtomsProto.UidBatteryConsumer[] uidConsumersProto - = proto.uidBatteryConsumers; + final BatteryUsageStatsAtomsProto.UidBatteryConsumer[] uidConsumersProto = + proto.uidBatteryConsumers; Arrays.sort(uidConsumersProto, (a, b) -> a.uid - b.uid); // UID_0 - After sorting, UID_0 should be in position 0 for both data structures @@ -186,6 +407,12 @@ public class BatteryUsageStatsPulledTest { } } + private static final int[] UID_USAGE_TIME_PROCESS_STATES = { + BatteryConsumer.PROCESS_STATE_FOREGROUND, + BatteryConsumer.PROCESS_STATE_BACKGROUND, + BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE + }; + private void assertSameUidBatteryConsumer( android.os.UidBatteryConsumer uidConsumer, BatteryUsageStatsAtomsProto.UidBatteryConsumer uidConsumerProto, @@ -195,10 +422,10 @@ public class BatteryUsageStatsPulledTest { assertEquals("Uid consumers had mismatched uids", uid, uidConsumer.getUid()); assertEquals("For uid " + uid, - uidConsumer.getTimeInStateMs(android.os.UidBatteryConsumer.STATE_FOREGROUND), + uidConsumer.getTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_FOREGROUND), uidConsumerProto.timeInForegroundMillis); assertEquals("For uid " + uid, - uidConsumer.getTimeInStateMs(android.os.UidBatteryConsumer.STATE_BACKGROUND), + uidConsumer.getTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_BACKGROUND), uidConsumerProto.timeInBackgroundMillis); for (int processState : UID_USAGE_TIME_PROCESS_STATES) { final long timeInStateMillis = uidConsumer.getTimeInProcessStateMs(processState); @@ -265,7 +492,9 @@ public class BatteryUsageStatsPulledTest { .setDischargePercentage(20) .setDischargedPowerRange(1000, 2000) .setDischargeDurationMs(1234) - .setStatsStartTimestamp(1000); + .setStatsStartTimestamp(1000) + .setStatsEndTimestamp(20000) + .setStatsDuration(10000); final UidBatteryConsumer.Builder uidBuilder = builder .getOrCreateUidBatteryConsumerBuilder(UID_0) .setPackageWithHighestDrain("myPackage0") diff --git a/services/tests/wmtests/OWNERS b/services/tests/wmtests/OWNERS index 78b867f9190b..51519fdc4b87 100644 --- a/services/tests/wmtests/OWNERS +++ b/services/tests/wmtests/OWNERS @@ -4,4 +4,7 @@ include /services/core/java/com/android/server/wm/OWNERS # Voice Interaction per-file *Assist* = file:/core/java/android/service/voice/OWNERS -natanieljr@google.com
\ No newline at end of file +# Keyboard shortcuts +per-file res/xml/bookmarks.xml = file:/services/core/java/com/android/server/input/OWNERS + +natanieljr@google.com diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java index a3252f87dc9d..6ad1044d2012 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java @@ -53,7 +53,7 @@ import org.junit.runner.RunWith; @RunWith(WindowTestRunner.class) public class ActivityRefresherTests extends WindowTestsBase { private Handler mMockHandler; - private LetterboxConfiguration mLetterboxConfiguration; + private AppCompatConfiguration mAppCompatConfiguration; private ActivityRecord mActivity; private ActivityRefresher mActivityRefresher; @@ -69,13 +69,13 @@ public class ActivityRefresherTests extends WindowTestsBase { @Before public void setUp() throws Exception { - mLetterboxConfiguration = mDisplayContent.mWmService.mLetterboxConfiguration; - spyOn(mLetterboxConfiguration); - when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled()) + mAppCompatConfiguration = mDisplayContent.mWmService.mAppCompatConfiguration; + spyOn(mAppCompatConfiguration); + when(mAppCompatConfiguration.isCameraCompatTreatmentEnabled()) .thenReturn(true); - when(mLetterboxConfiguration.isCameraCompatRefreshEnabled()) + when(mAppCompatConfiguration.isCameraCompatRefreshEnabled()) .thenReturn(true); - when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()) + when(mAppCompatConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()) .thenReturn(true); mMockHandler = mock(Handler.class); @@ -90,7 +90,7 @@ public class ActivityRefresherTests extends WindowTestsBase { @Test public void testShouldRefreshActivity_refreshDisabled() throws Exception { - when(mLetterboxConfiguration.isCameraCompatRefreshEnabled()) + when(mAppCompatConfiguration.isCameraCompatRefreshEnabled()) .thenReturn(false); configureActivityAndDisplay(); mActivityRefresher.addEvaluator(mEvaluatorTrue); @@ -146,7 +146,7 @@ public class ActivityRefresherTests extends WindowTestsBase { public void testOnActivityConfigurationChanging_cycleThroughStopDisabled() throws Exception { mActivityRefresher.addEvaluator(mEvaluatorTrue); - when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()) + when(mAppCompatConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()) .thenReturn(false); configureActivityAndDisplay(); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java index 2d94b34c52f1..d8c7fb3594c1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java @@ -290,7 +290,7 @@ public class AppCompatCameraOverridesTest extends WindowTestsBase { * Runs a test scenario providing a Robot. */ void runTestScenario(@NonNull Consumer<CameraOverridesRobotTest> consumer) { - spyOn(mWm.mLetterboxConfiguration); + spyOn(mWm.mAppCompatConfiguration); final CameraOverridesRobotTest robot = new CameraOverridesRobotTest(mWm, mAtm, mSupervisor); consumer.accept(robot); } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java index 974a8e8712d8..0b1bb0f75a09 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java @@ -112,7 +112,7 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase { * Runs a test scenario providing a Robot. */ void runTestScenario(@NonNull Consumer<DisplayRotationPolicyRobotTest> consumer) { - spyOn(mWm.mLetterboxConfiguration); + spyOn(mWm.mAppCompatConfiguration); final DisplayRotationPolicyRobotTest robot = new DisplayRotationPolicyRobotTest(mWm, mAtm, mSupervisor); consumer.accept(robot); diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationPersisterTest.java index 3fcec963593c..c952e2f682c3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationPersisterTest.java @@ -18,8 +18,8 @@ package com.android.server.wm; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP; import android.annotation.NonNull; import android.annotation.Nullable; @@ -44,14 +44,14 @@ import java.util.function.Consumer; import java.util.function.Supplier; /** - * Tests for the {@link LetterboxConfigurationPersister} class. + * Tests for the {@link AppCompatConfigurationPersister} class. * * Build/Install/Run: - * atest WmTests:LetterboxConfigurationPersisterTest + * atest WmTests:AppCompatConfigurationPersisterTest */ @SmallTest @Presubmit -public class LetterboxConfigurationPersisterTest { +public class AppCompatConfigurationPersisterTest { private static final long TIMEOUT = 2000L; // 2 secs @@ -61,7 +61,7 @@ public class LetterboxConfigurationPersisterTest { private static final String LETTERBOX_CONFIGURATION_TEST_FILENAME = "letterbox_config_test"; - private LetterboxConfigurationPersister mLetterboxConfigurationPersister; + private AppCompatConfigurationPersister mAppCompatConfigurationPersister; private Context mContext; private PersisterQueue mPersisterQueue; private QueueState mQueueState; @@ -74,7 +74,7 @@ public class LetterboxConfigurationPersisterTest { mConfigFolder = mContext.getFilesDir(); mPersisterQueue = new PersisterQueue(); mQueueState = new QueueState(); - mLetterboxConfigurationPersister = new LetterboxConfigurationPersister( + mAppCompatConfigurationPersister = new AppCompatConfigurationPersister( () -> mContext.getResources().getInteger( R.integer.config_letterboxDefaultPositionForHorizontalReachability), () -> mContext.getResources().getInteger( @@ -88,12 +88,12 @@ public class LetterboxConfigurationPersisterTest { LETTERBOX_CONFIGURATION_TEST_FILENAME); mQueueListener = queueEmpty -> mQueueState.onItemAdded(); mPersisterQueue.addListener(mQueueListener); - mLetterboxConfigurationPersister.start(); + mAppCompatConfigurationPersister.start(); } @After public void tearDown() throws InterruptedException { - deleteConfiguration(mLetterboxConfigurationPersister, mPersisterQueue); + deleteConfiguration(mAppCompatConfigurationPersister, mPersisterQueue); waitForCompletion(mPersisterQueue); mPersisterQueue.removeListener(mQueueListener); stopPersisterSafe(mPersisterQueue); @@ -102,7 +102,7 @@ public class LetterboxConfigurationPersisterTest { @Test public void test_whenStoreIsCreated_valuesAreDefaults() { final int positionForHorizontalReachability = - mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability( + mAppCompatConfigurationPersister.getLetterboxPositionForHorizontalReachability( false); final int defaultPositionForHorizontalReachability = mContext.getResources().getInteger( @@ -110,7 +110,7 @@ public class LetterboxConfigurationPersisterTest { Assert.assertEquals(defaultPositionForHorizontalReachability, positionForHorizontalReachability); final int positionForVerticalReachability = - mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(false); + mAppCompatConfigurationPersister.getLetterboxPositionForVerticalReachability(false); final int defaultPositionForVerticalReachability = mContext.getResources().getInteger( R.integer.config_letterboxDefaultPositionForVerticalReachability); @@ -120,16 +120,16 @@ public class LetterboxConfigurationPersisterTest { @Test public void test_whenUpdatedWithNewValues_valuesAreWritten() { - mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(false, + mAppCompatConfigurationPersister.setLetterboxPositionForHorizontalReachability(false, LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT); - mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(false, + mAppCompatConfigurationPersister.setLetterboxPositionForVerticalReachability(false, LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP); waitForCompletion(mPersisterQueue); final int newPositionForHorizontalReachability = - mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability( + mAppCompatConfigurationPersister.getLetterboxPositionForHorizontalReachability( false); final int newPositionForVerticalReachability = - mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(false); + mAppCompatConfigurationPersister.getLetterboxPositionForVerticalReachability(false); Assert.assertEquals(LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT, newPositionForHorizontalReachability); Assert.assertEquals(LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP, @@ -139,7 +139,7 @@ public class LetterboxConfigurationPersisterTest { @Test public void test_whenUpdatedWithNewValues_valuesAreReadAfterRestart() { final PersisterQueue firstPersisterQueue = new PersisterQueue(); - final LetterboxConfigurationPersister firstPersister = new LetterboxConfigurationPersister( + final AppCompatConfigurationPersister firstPersister = new AppCompatConfigurationPersister( DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST, mContext.getFilesDir(), firstPersisterQueue, mQueueState, @@ -152,7 +152,7 @@ public class LetterboxConfigurationPersisterTest { waitForCompletion(firstPersisterQueue); stopPersisterSafe(firstPersisterQueue); final PersisterQueue secondPersisterQueue = new PersisterQueue(); - final LetterboxConfigurationPersister secondPersister = new LetterboxConfigurationPersister( + final AppCompatConfigurationPersister secondPersister = new AppCompatConfigurationPersister( DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST, mContext.getFilesDir(), secondPersisterQueue, mQueueState, @@ -174,7 +174,7 @@ public class LetterboxConfigurationPersisterTest { @Test public void test_whenUpdatedWithNewValuesAndDeleted_valuesAreDefaults() { final PersisterQueue firstPersisterQueue = new PersisterQueue(); - final LetterboxConfigurationPersister firstPersister = new LetterboxConfigurationPersister( + final AppCompatConfigurationPersister firstPersister = new AppCompatConfigurationPersister( DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST, mContext.getFilesDir(), firstPersisterQueue, mQueueState, @@ -198,7 +198,7 @@ public class LetterboxConfigurationPersisterTest { stopPersisterSafe(firstPersisterQueue); final PersisterQueue secondPersisterQueue = new PersisterQueue(); - final LetterboxConfigurationPersister secondPersister = new LetterboxConfigurationPersister( + final AppCompatConfigurationPersister secondPersister = new AppCompatConfigurationPersister( DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST, mContext.getFilesDir(), secondPersisterQueue, mQueueState, @@ -245,7 +245,7 @@ public class LetterboxConfigurationPersisterTest { return mQueueState.isEmpty(); } - private void deleteConfiguration(LetterboxConfigurationPersister persister, + private void deleteConfiguration(AppCompatConfigurationPersister persister, PersisterQueue persisterQueue) { final AtomicFile fileToDelete = new AtomicFile( new File(mConfigFolder, LETTERBOX_CONFIGURATION_TEST_FILENAME)); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxConfigurationRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java index e1da913872d8..cb3cf6bd2a5c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxConfigurationRobot.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java @@ -24,46 +24,46 @@ import static org.mockito.Mockito.when; import androidx.annotation.NonNull; /** - * Robot implementation for {@link LetterboxConfiguration}. + * Robot implementation for {@link AppCompatConfiguration}. */ -class AppCompatLetterboxConfigurationRobot { +class AppCompatConfigurationRobot { @NonNull - private final LetterboxConfiguration mLetterboxConfiguration; + private final AppCompatConfiguration mAppCompatConfiguration; - AppCompatLetterboxConfigurationRobot(@NonNull LetterboxConfiguration letterboxConfiguration) { - mLetterboxConfiguration = letterboxConfiguration; - spyOn(mLetterboxConfiguration); + AppCompatConfigurationRobot(@NonNull AppCompatConfiguration appCompatConfiguration) { + mAppCompatConfiguration = appCompatConfiguration; + spyOn(mAppCompatConfiguration); } void enableTranslucentPolicy(boolean enabled) { - when(mLetterboxConfiguration.isTranslucentLetterboxingEnabled()).thenReturn(enabled); + when(mAppCompatConfiguration.isTranslucentLetterboxingEnabled()).thenReturn(enabled); } void enablePolicyForIgnoringRequestedOrientation(boolean enabled) { - doReturn(enabled).when(mLetterboxConfiguration) + doReturn(enabled).when(mAppCompatConfiguration) .isPolicyForIgnoringRequestedOrientationEnabled(); } void enableCameraCompatTreatment(boolean enabled) { - doReturn(enabled).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled(); + doReturn(enabled).when(mAppCompatConfiguration).isCameraCompatTreatmentEnabled(); } void enableCameraCompatTreatmentAtBuildTime(boolean enabled) { - doReturn(enabled).when(mLetterboxConfiguration) + doReturn(enabled).when(mAppCompatConfiguration) .isCameraCompatTreatmentEnabledAtBuildTime(); } void enableUserAppAspectRatioFullscreen(boolean enabled) { - doReturn(enabled).when(mLetterboxConfiguration).isUserAppAspectRatioFullscreenEnabled(); + doReturn(enabled).when(mAppCompatConfiguration).isUserAppAspectRatioFullscreenEnabled(); } void enableUserAppAspectRatioSettings(boolean enabled) { - doReturn(enabled).when(mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled(); + doReturn(enabled).when(mAppCompatConfiguration).isUserAppAspectRatioSettingsEnabled(); } void enableCameraCompatSplitScreenAspectRatio(boolean enabled) { - doReturn(enabled).when(mLetterboxConfiguration) + doReturn(enabled).when(mAppCompatConfiguration) .isCameraCompatSplitScreenAspectRatioEnabled(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationTest.java index 79e401c238bd..6efd7de80c55 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationTest.java @@ -19,12 +19,12 @@ package com.android.server.wm; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP; import static com.android.server.wm.testing.Assert.assertThrows; import static junit.framework.Assert.assertEquals; @@ -52,35 +52,35 @@ import java.util.Arrays; import java.util.function.BiConsumer; /** - * Tests for the {@link LetterboxConfiguration} class. + * Tests for the {@link AppCompatConfiguration} class. * * Build/Install/Run: - * atest WmTests:LetterboxConfigurationTest + * atest WmTests:AppCompatConfigurationTest */ @SmallTest @Presubmit -public class LetterboxConfigurationTest { +public class AppCompatConfigurationTest { private Context mContext; - private LetterboxConfiguration mLetterboxConfiguration; - private LetterboxConfigurationPersister mLetterboxConfigurationPersister; + private AppCompatConfiguration mAppCompatConfiguration; + private AppCompatConfigurationPersister mAppCompatConfigurationPersister; @Before public void setUp() throws Exception { mContext = getInstrumentation().getTargetContext(); - mLetterboxConfigurationPersister = mock(LetterboxConfigurationPersister.class); - mLetterboxConfiguration = new LetterboxConfiguration(mContext, - mLetterboxConfigurationPersister); + mAppCompatConfigurationPersister = mock(AppCompatConfigurationPersister.class); + mAppCompatConfiguration = new AppCompatConfiguration(mContext, + mAppCompatConfigurationPersister); } @Test public void test_whenReadingValues_storeIsInvoked() { for (boolean halfFoldPose : Arrays.asList(false, true)) { - mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability(halfFoldPose); - verify(mLetterboxConfigurationPersister).getLetterboxPositionForHorizontalReachability( + mAppCompatConfiguration.getLetterboxPositionForHorizontalReachability(halfFoldPose); + verify(mAppCompatConfigurationPersister).getLetterboxPositionForHorizontalReachability( halfFoldPose); - mLetterboxConfiguration.getLetterboxPositionForVerticalReachability(halfFoldPose); - verify(mLetterboxConfigurationPersister).getLetterboxPositionForVerticalReachability( + mAppCompatConfiguration.getLetterboxPositionForVerticalReachability(halfFoldPose); + verify(mAppCompatConfigurationPersister).getLetterboxPositionForVerticalReachability( halfFoldPose); } } @@ -88,13 +88,13 @@ public class LetterboxConfigurationTest { @Test public void test_whenSettingValues_updateConfigurationIsInvoked() { for (boolean halfFoldPose : Arrays.asList(false, true)) { - mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextRightStop( + mAppCompatConfiguration.movePositionForHorizontalReachabilityToNextRightStop( halfFoldPose); - verify(mLetterboxConfigurationPersister).setLetterboxPositionForHorizontalReachability( + verify(mAppCompatConfigurationPersister).setLetterboxPositionForHorizontalReachability( eq(halfFoldPose), anyInt()); - mLetterboxConfiguration.movePositionForVerticalReachabilityToNextBottomStop( + mAppCompatConfiguration.movePositionForVerticalReachabilityToNextBottomStop( halfFoldPose); - verify(mLetterboxConfigurationPersister).setLetterboxPositionForVerticalReachability( + verify(mAppCompatConfigurationPersister).setLetterboxPositionForVerticalReachability( eq(halfFoldPose), anyInt()); } } @@ -107,65 +107,65 @@ public class LetterboxConfigurationTest { /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT, /* expectedTime */ 1, /* halfFoldPose */ false, - LetterboxConfiguration::movePositionForHorizontalReachabilityToNextLeftStop); + AppCompatConfiguration::movePositionForHorizontalReachabilityToNextLeftStop); assertForHorizontalMove( /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER, /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT, /* expectedTime */ 1, /* halfFoldPose */ false, - LetterboxConfiguration::movePositionForHorizontalReachabilityToNextRightStop); + AppCompatConfiguration::movePositionForHorizontalReachabilityToNextRightStop); // Starting from left assertForHorizontalMove( /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT, /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT, /* expectedTime */ 2, /* halfFoldPose */ false, - LetterboxConfiguration::movePositionForHorizontalReachabilityToNextLeftStop); + AppCompatConfiguration::movePositionForHorizontalReachabilityToNextLeftStop); assertForHorizontalMove( /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT, /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER, /* expectedTime */ 1, /* halfFoldPose */ false, - LetterboxConfiguration::movePositionForHorizontalReachabilityToNextRightStop); + AppCompatConfiguration::movePositionForHorizontalReachabilityToNextRightStop); // Starting from right assertForHorizontalMove( /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT, /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT, /* expectedTime */ 2, /* halfFoldPose */ false, - LetterboxConfiguration::movePositionForHorizontalReachabilityToNextRightStop); + AppCompatConfiguration::movePositionForHorizontalReachabilityToNextRightStop); assertForHorizontalMove( /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT, /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER, /* expectedTime */ 2, /* halfFoldPose */ false, - LetterboxConfiguration::movePositionForHorizontalReachabilityToNextLeftStop); + AppCompatConfiguration::movePositionForHorizontalReachabilityToNextLeftStop); // Starting from left - book mode assertForHorizontalMove( /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT, /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT, /* expectedTime */ 1, /* halfFoldPose */ true, - LetterboxConfiguration::movePositionForHorizontalReachabilityToNextLeftStop); + AppCompatConfiguration::movePositionForHorizontalReachabilityToNextLeftStop); assertForHorizontalMove( /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT, /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT, /* expectedTime */ 1, /* halfFoldPose */ true, - LetterboxConfiguration::movePositionForHorizontalReachabilityToNextRightStop); + AppCompatConfiguration::movePositionForHorizontalReachabilityToNextRightStop); // Starting from right - book mode assertForHorizontalMove( /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT, /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT, /* expectedTime */ 2, /* halfFoldPose */ true, - LetterboxConfiguration::movePositionForHorizontalReachabilityToNextRightStop); + AppCompatConfiguration::movePositionForHorizontalReachabilityToNextRightStop); assertForHorizontalMove( /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT, /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT, /* expectedTime */ 2, /* halfFoldPose */ true, - LetterboxConfiguration::movePositionForHorizontalReachabilityToNextLeftStop); + AppCompatConfiguration::movePositionForHorizontalReachabilityToNextLeftStop); } @Test @@ -176,85 +176,85 @@ public class LetterboxConfigurationTest { /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM, /* expectedTime */ 1, /* halfFoldPose */ false, - LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop); + AppCompatConfiguration::movePositionForVerticalReachabilityToNextBottomStop); assertForVerticalMove( /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER, /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP, /* expectedTime */ 1, /* halfFoldPose */ false, - LetterboxConfiguration::movePositionForVerticalReachabilityToNextTopStop); + AppCompatConfiguration::movePositionForVerticalReachabilityToNextTopStop); // Starting from top assertForVerticalMove( /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP, /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER, /* expectedTime */ 1, /* halfFoldPose */ false, - LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop); + AppCompatConfiguration::movePositionForVerticalReachabilityToNextBottomStop); assertForVerticalMove( /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP, /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP, /* expectedTime */ 2, /* halfFoldPose */ false, - LetterboxConfiguration::movePositionForVerticalReachabilityToNextTopStop); + AppCompatConfiguration::movePositionForVerticalReachabilityToNextTopStop); // Starting from bottom assertForVerticalMove( /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM, /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER, /* expectedTime */ 2, /* halfFoldPose */ false, - LetterboxConfiguration::movePositionForVerticalReachabilityToNextTopStop); + AppCompatConfiguration::movePositionForVerticalReachabilityToNextTopStop); assertForVerticalMove( /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM, /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM, /* expectedTime */ 2, /* halfFoldPose */ false, - LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop); + AppCompatConfiguration::movePositionForVerticalReachabilityToNextBottomStop); // Starting from top - tabletop mode assertForVerticalMove( /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP, /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM, /* expectedTime */ 1, /* halfFoldPose */ true, - LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop); + AppCompatConfiguration::movePositionForVerticalReachabilityToNextBottomStop); assertForVerticalMove( /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP, /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP, /* expectedTime */ 1, /* halfFoldPose */ true, - LetterboxConfiguration::movePositionForVerticalReachabilityToNextTopStop); + AppCompatConfiguration::movePositionForVerticalReachabilityToNextTopStop); // Starting from bottom - tabletop mode assertForVerticalMove( /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM, /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP, /* expectedTime */ 2, /* halfFoldPose */ true, - LetterboxConfiguration::movePositionForVerticalReachabilityToNextTopStop); + AppCompatConfiguration::movePositionForVerticalReachabilityToNextTopStop); assertForVerticalMove( /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM, /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM, /* expectedTime */ 2, /* halfFoldPose */ true, - LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop); + AppCompatConfiguration::movePositionForVerticalReachabilityToNextBottomStop); } private void assertForHorizontalMove(int from, int expected, int expectedTime, - boolean halfFoldPose, BiConsumer<LetterboxConfiguration, Boolean> move) { + boolean halfFoldPose, BiConsumer<AppCompatConfiguration, Boolean> move) { // We are in the current position - when(mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability(halfFoldPose)) + when(mAppCompatConfiguration.getLetterboxPositionForHorizontalReachability(halfFoldPose)) .thenReturn(from); - move.accept(mLetterboxConfiguration, halfFoldPose); - verify(mLetterboxConfigurationPersister, + move.accept(mAppCompatConfiguration, halfFoldPose); + verify(mAppCompatConfigurationPersister, times(expectedTime)).setLetterboxPositionForHorizontalReachability(halfFoldPose, expected); } private void assertForVerticalMove(int from, int expected, int expectedTime, - boolean halfFoldPose, BiConsumer<LetterboxConfiguration, Boolean> move) { + boolean halfFoldPose, BiConsumer<AppCompatConfiguration, Boolean> move) { // We are in the current position - when(mLetterboxConfiguration.getLetterboxPositionForVerticalReachability(halfFoldPose)) + when(mAppCompatConfiguration.getLetterboxPositionForVerticalReachability(halfFoldPose)) .thenReturn(from); - move.accept(mLetterboxConfiguration, halfFoldPose); - verify(mLetterboxConfigurationPersister, + move.accept(mAppCompatConfiguration, halfFoldPose); + verify(mAppCompatConfigurationPersister, times(expectedTime)).setLetterboxPositionForVerticalReachability(halfFoldPose, expected); } @@ -262,20 +262,20 @@ public class LetterboxConfigurationTest { @Test public void test_letterboxPositionWhenReachabilityEnabledIsReset() { // Check that horizontal reachability is set with correct arguments - mLetterboxConfiguration.resetPersistentLetterboxPositionForHorizontalReachability(); - verify(mLetterboxConfigurationPersister).setLetterboxPositionForHorizontalReachability( + mAppCompatConfiguration.resetPersistentLetterboxPositionForHorizontalReachability(); + verify(mAppCompatConfigurationPersister).setLetterboxPositionForHorizontalReachability( false /* forBookMode */, LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER); - verify(mLetterboxConfigurationPersister).setLetterboxPositionForHorizontalReachability( + verify(mAppCompatConfigurationPersister).setLetterboxPositionForHorizontalReachability( true /* forBookMode */, LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT); // Check that vertical reachability is set with correct arguments - mLetterboxConfiguration.resetPersistentLetterboxPositionForVerticalReachability(); - verify(mLetterboxConfigurationPersister).setLetterboxPositionForVerticalReachability( + mAppCompatConfiguration.resetPersistentLetterboxPositionForVerticalReachability(); + verify(mAppCompatConfigurationPersister).setLetterboxPositionForVerticalReachability( false /* forTabletopMode */, LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER); - verify(mLetterboxConfigurationPersister).setLetterboxPositionForVerticalReachability( + verify(mAppCompatConfigurationPersister).setLetterboxPositionForVerticalReachability( true /* forTabletopMode */, LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP); } @@ -283,16 +283,16 @@ public class LetterboxConfigurationTest { @Test public void test_letterboxPositionWhenReachabilityEnabledIsSet() { // Check that horizontal reachability is set with correct arguments - mLetterboxConfiguration.setPersistentLetterboxPositionForHorizontalReachability( + mAppCompatConfiguration.setPersistentLetterboxPositionForHorizontalReachability( false /* forBookMode */, LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT); - verify(mLetterboxConfigurationPersister).setLetterboxPositionForHorizontalReachability( + verify(mAppCompatConfigurationPersister).setLetterboxPositionForHorizontalReachability( false /* forBookMode */, LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT); // Check that vertical reachability is set with correct arguments - mLetterboxConfiguration.setPersistentLetterboxPositionForVerticalReachability( + mAppCompatConfiguration.setPersistentLetterboxPositionForVerticalReachability( false /* forTabletopMode */, LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP); - verify(mLetterboxConfigurationPersister).setLetterboxPositionForVerticalReachability( + verify(mAppCompatConfigurationPersister).setLetterboxPositionForVerticalReachability( false /* forTabletopMode */, LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP); } @@ -300,60 +300,60 @@ public class LetterboxConfigurationTest { @Test public void test_setLetterboxHorizontalPositionMultiplier_validValues() { assertThrows(IllegalArgumentException.class, - () -> mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(-1)); + () -> mAppCompatConfiguration.setLetterboxHorizontalPositionMultiplier(-1)); assertThrows(IllegalArgumentException.class, - () -> mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(2)); + () -> mAppCompatConfiguration.setLetterboxHorizontalPositionMultiplier(2)); // Does not throw an exception for values [0,1]. - mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(0); - mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(0.5f); - mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(1); + mAppCompatConfiguration.setLetterboxHorizontalPositionMultiplier(0); + mAppCompatConfiguration.setLetterboxHorizontalPositionMultiplier(0.5f); + mAppCompatConfiguration.setLetterboxHorizontalPositionMultiplier(1); } @Test public void test_setLetterboxVerticalPositionMultiplier_validValues() { assertThrows(IllegalArgumentException.class, - () -> mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(-1)); + () -> mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier(-1)); assertThrows(IllegalArgumentException.class, - () -> mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(2)); + () -> mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier(2)); // Does not throw an exception for values [0,1]. - mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0); - mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f); - mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(1); + mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier(0); + mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier(0.5f); + mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier(1); } @Test public void test_setLetterboxBookModePositionMultiplier_validValues() { assertThrows(IllegalArgumentException.class, - () -> mLetterboxConfiguration.setLetterboxBookModePositionMultiplier(-1)); + () -> mAppCompatConfiguration.setLetterboxBookModePositionMultiplier(-1)); assertThrows(IllegalArgumentException.class, - () -> mLetterboxConfiguration.setLetterboxBookModePositionMultiplier(2)); + () -> mAppCompatConfiguration.setLetterboxBookModePositionMultiplier(2)); // Does not throw an exception for values [0,1]. - mLetterboxConfiguration.setLetterboxBookModePositionMultiplier(0); - mLetterboxConfiguration.setLetterboxBookModePositionMultiplier(0.5f); - mLetterboxConfiguration.setLetterboxBookModePositionMultiplier(1); + mAppCompatConfiguration.setLetterboxBookModePositionMultiplier(0); + mAppCompatConfiguration.setLetterboxBookModePositionMultiplier(0.5f); + mAppCompatConfiguration.setLetterboxBookModePositionMultiplier(1); } @Test public void test_setLetterboxTabletopModePositionMultiplier_validValues() { assertThrows(IllegalArgumentException.class, - () -> mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(-1)); + () -> mAppCompatConfiguration.setLetterboxTabletopModePositionMultiplier(-1)); assertThrows(IllegalArgumentException.class, - () -> mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(2)); + () -> mAppCompatConfiguration.setLetterboxTabletopModePositionMultiplier(2)); // Does not throw an exception for values [0,1]. - mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(0); - mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(0.5f); - mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(1); + mAppCompatConfiguration.setLetterboxTabletopModePositionMultiplier(0); + mAppCompatConfiguration.setLetterboxTabletopModePositionMultiplier(0.5f); + mAppCompatConfiguration.setLetterboxTabletopModePositionMultiplier(1); } @Test public void test_evaluateThinLetterboxWhenDensityChanges() { final Resources rs = mock(Resources.class); final DisplayMetrics dm = mock(DisplayMetrics.class); - final LetterboxConfigurationPersister lp = mock(LetterboxConfigurationPersister.class); + final AppCompatConfigurationPersister lp = mock(AppCompatConfigurationPersister.class); spyOn(mContext); when(rs.getDisplayMetrics()).thenReturn(dm); when(mContext.getResources()).thenReturn(rs); @@ -361,7 +361,7 @@ public class LetterboxConfigurationTest { .thenReturn(100); when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxHeightDp)) .thenReturn(200); - final LetterboxConfiguration configuration = new LetterboxConfiguration(mContext, lp); + final AppCompatConfiguration configuration = new AppCompatConfiguration(mContext, lp); // Verify the values are the expected ones dm.density = 100; diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java index 35c2ee0b83a1..634453fe008c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java @@ -162,7 +162,7 @@ public class AppCompatOrientationOverridesTest extends WindowTestsBase { * Runs a test scenario providing a Robot. */ void runTestScenario(@NonNull Consumer<OrientationOverridesRobotTest> consumer) { - spyOn(mWm.mLetterboxConfiguration); + spyOn(mWm.mAppCompatConfiguration); final OrientationOverridesRobotTest robot = new OrientationOverridesRobotTest(mWm, mAtm, mSupervisor); consumer.accept(robot); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java index aa520e9cc599..ad34a6b0fc87 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java @@ -514,7 +514,7 @@ public class AppCompatOrientationPolicyTest extends WindowTestsBase { */ void runTestScenario(boolean withActivity, @NonNull Consumer<OrientationPolicyRobotTest> consumer) { - spyOn(mWm.mLetterboxConfiguration); + spyOn(mWm.mAppCompatConfiguration); final OrientationPolicyRobotTest robot = new OrientationPolicyRobotTest(mWm, mAtm, mSupervisor, withActivity); consumer.accept(robot); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java index de16e3888022..92f246be7cdf 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java @@ -31,7 +31,7 @@ abstract class AppCompatRobotBase { @NonNull private final AppCompatActivityRobot mActivityRobot; @NonNull - private final AppCompatLetterboxConfigurationRobot mConfigurationRobot; + private final AppCompatConfigurationRobot mConfigurationRobot; @NonNull private final AppCompatComponentPropRobot mOptPropRobot; @@ -42,7 +42,7 @@ abstract class AppCompatRobotBase { mActivityRobot = new AppCompatActivityRobot(wm, atm, supervisor, displayWidth, displayHeight); mConfigurationRobot = - new AppCompatLetterboxConfigurationRobot(wm.mLetterboxConfiguration); + new AppCompatConfigurationRobot(wm.mAppCompatConfiguration); mOptPropRobot = new AppCompatComponentPropRobot(wm); } @@ -53,12 +53,12 @@ abstract class AppCompatRobotBase { } @NonNull - AppCompatLetterboxConfigurationRobot conf() { + AppCompatConfigurationRobot conf() { return mConfigurationRobot; } @NonNull - void applyOnConf(@NonNull Consumer<AppCompatLetterboxConfigurationRobot> consumer) { + void applyOnConf(@NonNull Consumer<AppCompatConfigurationRobot> consumer) { consumer.accept(mConfigurationRobot); } diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java index 11e6d90a5f50..eaa164127551 100644 --- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java @@ -88,7 +88,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { private static final String CAMERA_ID_2 = "camera-2"; private CameraManager mMockCameraManager; private Handler mMockHandler; - private LetterboxConfiguration mLetterboxConfiguration; + private AppCompatConfiguration mAppCompatConfiguration; private CameraManager.AvailabilityCallback mCameraAvailabilityCallback; private CameraCompatFreeformPolicy mCameraCompatFreeformPolicy; @@ -98,13 +98,13 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { @Before public void setUp() throws Exception { - mLetterboxConfiguration = mDisplayContent.mWmService.mLetterboxConfiguration; - spyOn(mLetterboxConfiguration); - when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled()) + mAppCompatConfiguration = mDisplayContent.mWmService.mAppCompatConfiguration; + spyOn(mAppCompatConfiguration); + when(mAppCompatConfiguration.isCameraCompatTreatmentEnabled()) .thenReturn(true); - when(mLetterboxConfiguration.isCameraCompatRefreshEnabled()) + when(mAppCompatConfiguration.isCameraCompatRefreshEnabled()) .thenReturn(true); - when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()) + when(mAppCompatConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()) .thenReturn(true); mMockCameraManager = mock(CameraManager.class); @@ -228,7 +228,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { @Test public void testOnActivityConfigurationChanging_cycleThroughStopDisabled() throws Exception { - when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()) + when(mAppCompatConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()) .thenReturn(false); configureActivity(SCREEN_ORIENTATION_PORTRAIT); diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java index 1c8dc05c6787..12f5714f91c7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java @@ -60,7 +60,7 @@ public final class CameraStateMonitorTests extends WindowTestsBase { private static final String TEST_PACKAGE_1_LABEL = "testPackage1"; private CameraManager mMockCameraManager; private Handler mMockHandler; - private LetterboxConfiguration mLetterboxConfiguration; + private AppCompatConfiguration mAppCompatConfiguration; private CameraStateMonitor mCameraStateMonitor; private CameraManager.AvailabilityCallback mCameraAvailabilityCallback; @@ -88,13 +88,13 @@ public final class CameraStateMonitorTests extends WindowTestsBase { @Before public void setUp() throws Exception { - mLetterboxConfiguration = mDisplayContent.mWmService.mLetterboxConfiguration; - spyOn(mLetterboxConfiguration); - when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled()) + mAppCompatConfiguration = mDisplayContent.mWmService.mAppCompatConfiguration; + spyOn(mAppCompatConfiguration); + when(mAppCompatConfiguration.isCameraCompatTreatmentEnabled()) .thenReturn(true); - when(mLetterboxConfiguration.isCameraCompatRefreshEnabled()) + when(mAppCompatConfiguration.isCameraCompatRefreshEnabled()) .thenReturn(true); - when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()) + when(mAppCompatConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()) .thenReturn(true); mMockCameraManager = mock(CameraManager.class); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index af0856f6109f..ab0c8d4ed60a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -1874,8 +1874,8 @@ public class DisplayContentTests extends WindowTestsBase { @EnableFlags(com.android.window.flags.Flags.FLAG_RESPECT_NON_TOP_VISIBLE_FIXED_ORIENTATION) @Test public void testRespectNonTopVisibleFixedOrientation() { - spyOn(mWm.mLetterboxConfiguration); - doReturn(false).when(mWm.mLetterboxConfiguration).isTranslucentLetterboxingEnabled(); + spyOn(mWm.mAppCompatConfiguration); + doReturn(false).when(mWm.mAppCompatConfiguration).isTranslucentLetterboxingEnabled(); makeDisplayPortrait(mDisplayContent); final ActivityRecord nonTopVisible = new ActivityBuilder(mAtm) .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) @@ -2604,7 +2604,7 @@ public class DisplayContentTests extends WindowTestsBase { // test misc display overrides assertEquals(ignoreOrientationRequests, testDisplayContent.mSetIgnoreOrientationRequest); assertEquals(fixedOrientationLetterboxRatio, - mWm.mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio(), + mWm.mAppCompatConfiguration.getFixedOrientationLetterboxAspectRatio(), 0 /* delta */); } @@ -2647,7 +2647,7 @@ public class DisplayContentTests extends WindowTestsBase { // test misc display overrides assertEquals(ignoreOrientationRequests, testDisplayContent.mSetIgnoreOrientationRequest); assertEquals(fixedOrientationLetterboxRatio, - mWm.mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio(), + mWm.mAppCompatConfiguration.getFixedOrientationLetterboxAspectRatio(), 0 /* delta */); } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java index d7814ac4da30..e9fcc4048fbc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java @@ -90,7 +90,7 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { private static final String TEST_PACKAGE_1_LABEL = "testPackage1"; private CameraManager mMockCameraManager; private Handler mMockHandler; - private LetterboxConfiguration mLetterboxConfiguration; + private AppCompatConfiguration mAppCompatConfiguration; private ActivityRefresher mActivityRefresher; private DisplayRotationCompatPolicy mDisplayRotationCompatPolicy; @@ -101,13 +101,13 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { @Before public void setUp() throws Exception { - mLetterboxConfiguration = mDisplayContent.mWmService.mLetterboxConfiguration; - spyOn(mLetterboxConfiguration); - when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled()) + mAppCompatConfiguration = mDisplayContent.mWmService.mAppCompatConfiguration; + spyOn(mAppCompatConfiguration); + when(mAppCompatConfiguration.isCameraCompatTreatmentEnabled()) .thenReturn(true); - when(mLetterboxConfiguration.isCameraCompatRefreshEnabled()) + when(mAppCompatConfiguration.isCameraCompatRefreshEnabled()) .thenReturn(true); - when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()) + when(mAppCompatConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()) .thenReturn(true); mMockCameraManager = mock(CameraManager.class); @@ -185,7 +185,7 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { @Test public void testOnScreenRotationAnimationFinished_treatmentNotEnabled_doNotShowToast() { - when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled()) + when(mAppCompatConfiguration.isCameraCompatTreatmentEnabled()) .thenReturn(false); mDisplayRotationCompatPolicy.onScreenRotationAnimationFinished(); @@ -239,7 +239,7 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { @Test public void testTreatmentNotEnabled_noForceRotationOrRefresh() throws Exception { - when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled()) + when(mAppCompatConfiguration.isCameraCompatTreatmentEnabled()) .thenReturn(false); configureActivity(SCREEN_ORIENTATION_PORTRAIT); @@ -253,7 +253,7 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { @Test public void testTreatmentDisabledViaDeviceConfig_noForceRotationOrRefresh() throws Exception { - when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled()) + when(mAppCompatConfiguration.isCameraCompatTreatmentEnabled()) .thenReturn(false); configureActivity(SCREEN_ORIENTATION_PORTRAIT); @@ -480,7 +480,7 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { @Test public void testOnActivityConfigurationChanging_refreshDisabledPerApp_noRefresh() throws Exception { - when(mLetterboxConfiguration.isCameraCompatRefreshEnabled()).thenReturn(false); + when(mAppCompatConfiguration.isCameraCompatRefreshEnabled()).thenReturn(false); configureActivity(SCREEN_ORIENTATION_PORTRAIT); @@ -519,7 +519,7 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { @Test public void testOnActivityConfigurationChanging_cycleThroughStopDisabled() throws Exception { - when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()) + when(mAppCompatConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()) .thenReturn(false); configureActivity(SCREEN_ORIENTATION_PORTRAIT); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java index b1057032eb36..5e8f347c0c6e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java @@ -55,7 +55,7 @@ public class DisplayRotationImmersiveAppCompatPolicyTests extends WindowTestsBas private DisplayRotationImmersiveAppCompatPolicy mPolicy; - private LetterboxConfiguration mMockLetterboxConfiguration; + private AppCompatConfiguration mMockAppCompatConfiguration; private ActivityRecord mMockActivityRecord; private Task mMockTask; private WindowState mMockWindowState; @@ -77,15 +77,15 @@ public class DisplayRotationImmersiveAppCompatPolicyTests extends WindowTestsBas doReturn(mMockActivityRecord).when(mDisplayContent).topRunningActivity(); when(mDisplayContent.getIgnoreOrientationRequest()).thenReturn(true); - mMockLetterboxConfiguration = mock(LetterboxConfiguration.class); - when(mMockLetterboxConfiguration.isDisplayRotationImmersiveAppCompatPolicyEnabled()) + mMockAppCompatConfiguration = mock(AppCompatConfiguration.class); + when(mMockAppCompatConfiguration.isDisplayRotationImmersiveAppCompatPolicyEnabled()) .thenReturn(true); - when(mMockLetterboxConfiguration + when(mMockAppCompatConfiguration .isDisplayRotationImmersiveAppCompatPolicyEnabledAtBuildTime()) .thenReturn(true); mPolicy = DisplayRotationImmersiveAppCompatPolicy.createIfNeeded( - mMockLetterboxConfiguration, createDisplayRotationMock(), + mMockAppCompatConfiguration, createDisplayRotationMock(), mDisplayContent); } @@ -206,7 +206,7 @@ public class DisplayRotationImmersiveAppCompatPolicyTests extends WindowTestsBas @Test public void testRotationChoiceEnforcedOnly_featureFlagDisabled_lockNotEnforced() { - when(mMockLetterboxConfiguration.isDisplayRotationImmersiveAppCompatPolicyEnabled()) + when(mMockAppCompatConfiguration.isDisplayRotationImmersiveAppCompatPolicyEnabled()) .thenReturn(false); assertIsRotationLockEnforcedReturnsFalseForAllRotations(); diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java index c42367e8ae18..d318f0047c08 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -106,15 +106,15 @@ public class LetterboxUiControllerTest extends WindowTestsBase { private Task mTask; private DisplayContent mDisplayContent; private LetterboxUiController mController; - private LetterboxConfiguration mLetterboxConfiguration; + private AppCompatConfiguration mAppCompatConfiguration; private final Rect mLetterboxedPortraitTaskBounds = new Rect(); @Before public void setUp() throws Exception { mActivity = setUpActivityWithComponent(); - mLetterboxConfiguration = mWm.mLetterboxConfiguration; - spyOn(mLetterboxConfiguration); + mAppCompatConfiguration = mWm.mAppCompatConfiguration; + spyOn(mAppCompatConfiguration); mController = new LetterboxUiController(mWm, mActivity); } @@ -238,7 +238,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { /*centerX=*/ 1, /*centerY=*/ 1) ); insets.setRoundedCorners(roundedCorners); - mLetterboxConfiguration.setLetterboxActivityCornersRadius(-1); + mAppCompatConfiguration.setLetterboxActivityCornersRadius(-1); assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow)); } @@ -251,7 +251,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(/*taskbar=*/ null); mainWindow.mInvGlobalScale = invGlobalScale; - mLetterboxConfiguration.setLetterboxActivityCornersRadius(configurationRadius); + mAppCompatConfiguration.setLetterboxActivityCornersRadius(configurationRadius); doReturn(true).when(mActivity).isInLetterboxAnimation(); assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow)); @@ -272,7 +272,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { final int configurationRadius = 15; final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(/*taskbar=*/ null); - mLetterboxConfiguration.setLetterboxActivityCornersRadius(configurationRadius); + mAppCompatConfiguration.setLetterboxActivityCornersRadius(configurationRadius); mainWindow.mInvGlobalScale = -1f; assertEquals(configurationRadius, mController.getRoundedCornersRadius(mainWindow)); @@ -310,7 +310,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { doReturn(true).when(mainWindow).isOnScreen(); doReturn(false).when(mainWindow).isLetterboxedForDisplayCutout(); doReturn(true).when(mainWindow).areAppWindowBoundsLetterboxed(); - doReturn(true).when(mLetterboxConfiguration).isLetterboxActivityCornersRounded(); + doReturn(true).when(mAppCompatConfiguration).isLetterboxActivityCornersRounded(); doReturn(TASKBAR_EXPANDED_HEIGHT).when(resources).getDimensionPixelSize( R.dimen.taskbar_frame_height); @@ -326,7 +326,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE, /* value */ true); - doReturn(false).when(mLetterboxConfiguration).isUserAppAspectRatioFullscreenEnabled(); + doReturn(false).when(mAppCompatConfiguration).isUserAppAspectRatioFullscreenEnabled(); mActivity = setUpActivityWithComponent(); assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() @@ -420,7 +420,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { @Test public void testShouldApplyUserMinAspectRatioOverride_trueProperty_returnsFalse() throws Exception { - doReturn(false).when(mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled(); + doReturn(false).when(mAppCompatConfiguration).isUserAppAspectRatioSettingsEnabled(); mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true); mActivity = setUpActivityWithComponent(); @@ -458,7 +458,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { boolean orientationRequest) { spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()); doReturn(orientationRequest).when( - mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled(); + mAppCompatConfiguration).isUserAppAspectRatioSettingsEnabled(); mDisplayContent.setIgnoreOrientationRequest(true); doReturn(USER_MIN_ASPECT_RATIO_3_2) .when(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()) @@ -471,7 +471,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { private void prepareActivityThatShouldApplyUserFullscreenOverride() { spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()); - doReturn(true).when(mLetterboxConfiguration).isUserAppAspectRatioFullscreenEnabled(); + doReturn(true).when(mAppCompatConfiguration).isUserAppAspectRatioFullscreenEnabled(); mDisplayContent.setIgnoreOrientationRequest(true); doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN) .when(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()) @@ -531,7 +531,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { @Test @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS}) public void testShouldSendFakeFocus_overrideEnabled_returnsTrue() { - doReturn(true).when(mLetterboxConfiguration).isCompatFakeFocusEnabled(); + doReturn(true).when(mAppCompatConfiguration).isCompatFakeFocusEnabled(); mController = new LetterboxUiController(mWm, mActivity); @@ -541,7 +541,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { @Test @DisableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS}) public void testShouldSendFakeFocus_overrideDisabled_returnsFalse() { - doReturn(true).when(mLetterboxConfiguration).isCompatFakeFocusEnabled(); + doReturn(true).when(mAppCompatConfiguration).isCompatFakeFocusEnabled(); mController = new LetterboxUiController(mWm, mActivity); @@ -552,7 +552,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS}) public void testIsCompatFakeFocusEnabled_propertyDisabledAndOverrideEnabled_fakeFocusDisabled() throws Exception { - doReturn(true).when(mLetterboxConfiguration).isCompatFakeFocusEnabled(); + doReturn(true).when(mAppCompatConfiguration).isCompatFakeFocusEnabled(); mockThatProperty(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS, /* value */ false); mController = new LetterboxUiController(mWm, mActivity); @@ -564,7 +564,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { @DisableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS}) public void testIsCompatFakeFocusEnabled_propertyEnabled_noOverride_fakeFocusEnabled() throws Exception { - doReturn(true).when(mLetterboxConfiguration).isCompatFakeFocusEnabled(); + doReturn(true).when(mAppCompatConfiguration).isCompatFakeFocusEnabled(); mockThatProperty(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS, /* value */ true); mController = new LetterboxUiController(mWm, mActivity); @@ -575,7 +575,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { @Test public void testIsCompatFakeFocusEnabled_propertyDisabled_fakeFocusDisabled() throws Exception { - doReturn(true).when(mLetterboxConfiguration).isCompatFakeFocusEnabled(); + doReturn(true).when(mAppCompatConfiguration).isCompatFakeFocusEnabled(); mockThatProperty(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS, /* value */ false); mController = new LetterboxUiController(mWm, mActivity); @@ -586,7 +586,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { @Test public void testIsCompatFakeFocusEnabled_propertyEnabled_fakeFocusEnabled() throws Exception { - doReturn(true).when(mLetterboxConfiguration).isCompatFakeFocusEnabled(); + doReturn(true).when(mAppCompatConfiguration).isCompatFakeFocusEnabled(); mockThatProperty(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS, /* value */ true); mController = new LetterboxUiController(mWm, mActivity); @@ -862,15 +862,15 @@ public class LetterboxUiControllerTest extends WindowTestsBase { @Test public void testgetFixedOrientationLetterboxAspectRatio_splitScreenAspectEnabled() { - doReturn(true).when(mActivity.mWmService.mLetterboxConfiguration) + doReturn(true).when(mActivity.mWmService.mAppCompatConfiguration) .isCameraCompatTreatmentEnabled(); - doReturn(true).when(mActivity.mWmService.mLetterboxConfiguration) + doReturn(true).when(mActivity.mWmService.mAppCompatConfiguration) .isCameraCompatTreatmentEnabledAtBuildTime(); - doReturn(true).when(mActivity.mWmService.mLetterboxConfiguration) + doReturn(true).when(mActivity.mWmService.mAppCompatConfiguration) .isCameraCompatSplitScreenAspectRatioEnabled(); - doReturn(false).when(mActivity.mWmService.mLetterboxConfiguration) + doReturn(false).when(mActivity.mWmService.mAppCompatConfiguration) .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(); - doReturn(1.5f).when(mActivity.mWmService.mLetterboxConfiguration) + doReturn(1.5f).when(mActivity.mWmService.mAppCompatConfiguration) .getFixedOrientationLetterboxAspectRatio(); // Recreate DisplayContent with DisplayRotationCompatPolicy @@ -894,13 +894,13 @@ public class LetterboxUiControllerTest extends WindowTestsBase { @Test public void testIsVerticalThinLetterboxed() { // Vertical thin letterbox disabled - doReturn(-1).when(mActivity.mWmService.mLetterboxConfiguration) + doReturn(-1).when(mActivity.mWmService.mAppCompatConfiguration) .getThinLetterboxHeightPx(); assertFalse(mController.isVerticalThinLetterboxed()); // Define a Task 100x100 final Task task = mock(Task.class); doReturn(new Rect(0, 0, 100, 100)).when(task).getBounds(); - doReturn(10).when(mActivity.mWmService.mLetterboxConfiguration) + doReturn(10).when(mActivity.mWmService.mAppCompatConfiguration) .getThinLetterboxHeightPx(); // Vertical thin letterbox disabled without Task @@ -925,13 +925,13 @@ public class LetterboxUiControllerTest extends WindowTestsBase { @Test public void testIsHorizontalThinLetterboxed() { // Horizontal thin letterbox disabled - doReturn(-1).when(mActivity.mWmService.mLetterboxConfiguration) + doReturn(-1).when(mActivity.mWmService.mAppCompatConfiguration) .getThinLetterboxWidthPx(); assertFalse(mController.isHorizontalThinLetterboxed()); // Define a Task 100x100 final Task task = mock(Task.class); doReturn(new Rect(0, 0, 100, 100)).when(task).getBounds(); - doReturn(10).when(mActivity.mWmService.mLetterboxConfiguration) + doReturn(10).when(mActivity.mWmService.mAppCompatConfiguration) .getThinLetterboxWidthPx(); // Vertical thin letterbox disabled without Task @@ -986,7 +986,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { @Test public void testIsLetterboxEducationEnabled() { mController.isLetterboxEducationEnabled(); - verify(mLetterboxConfiguration).getIsEducationEnabled(); + verify(mAppCompatConfiguration).getIsEducationEnabled(); } private void mockThatProperty(String propertyName, boolean value) throws Exception { diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index afe360430f02..8981f715cf4b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -66,7 +66,7 @@ import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.ActivityRecord.State.STOPPED; import static com.android.server.wm.AppCompatUtils.computeAspectRatio; import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_POSITION_MULTIPLIER_CENTER; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_POSITION_MULTIPLIER_CENTER; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.google.common.truth.Truth.assertThat; @@ -228,7 +228,7 @@ public class SizeCompatTests extends WindowTestsBase { boolean horizontalReachability) { setUpDisplaySizeWithApp(displayWidth, displayHeight); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - final LetterboxConfiguration config = mWm.mLetterboxConfiguration; + final AppCompatConfiguration config = mWm.mAppCompatConfiguration; config.setTranslucentLetterboxingOverrideEnabled(true); config.setLetterboxVerticalPositionMultiplier(0.5f); config.setIsVerticalReachabilityEnabled(true); @@ -353,8 +353,8 @@ public class SizeCompatTests extends WindowTestsBase { .build(); setUpApp(display); display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mWm.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f); - mWm.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true); + mWm.mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier(0.5f); + mWm.mAppCompatConfiguration.setIsVerticalReachabilityEnabled(true); final ActivityRecord activity = getActivityBuilderOnSameTask() .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE) @@ -1082,9 +1082,9 @@ public class SizeCompatTests extends WindowTestsBase { RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); // Simulate the user selecting the fullscreen user aspect ratio override - spyOn(activity.mWmService.mLetterboxConfiguration); + spyOn(activity.mWmService.mAppCompatConfiguration); spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides()); - doReturn(true).when(activity.mWmService.mLetterboxConfiguration) + doReturn(true).when(activity.mWmService.mAppCompatConfiguration) .isUserAppAspectRatioFullscreenEnabled(); doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN) .when(activity.mAppCompatController.getAppCompatAspectRatioOverrides()) @@ -1869,7 +1869,7 @@ public class SizeCompatTests extends WindowTestsBase { // Portrait fixed app with min aspect ratio higher that aspect ratio override for fixed // orientation letterbox. - mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.1f); + mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.1f); mActivity.info.setMinAspectRatio(3); prepareUnresizable(mActivity, /* maxAspect= */ 0, SCREEN_ORIENTATION_PORTRAIT); @@ -1901,7 +1901,7 @@ public class SizeCompatTests extends WindowTestsBase { // Portrait fixed app with max aspect ratio lower that aspect ratio override for fixed // orientation letterbox. - mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(3); + mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(3); prepareUnresizable(mActivity, /* maxAspect= */ 2, SCREEN_ORIENTATION_PORTRAIT); final Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds()); @@ -1931,7 +1931,7 @@ public class SizeCompatTests extends WindowTestsBase { mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); final float fixedOrientationLetterboxAspectRatio = 1.1f; - mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio( + mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio( fixedOrientationLetterboxAspectRatio); prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_PORTRAIT, /* isUnresizable= */ false); @@ -1979,7 +1979,7 @@ public class SizeCompatTests extends WindowTestsBase { // Activity should be letterboxed with an aspect ratio of 1.01. final Rect afterBounds = mActivity.getBounds(); final float actualAspectRatio = 1f * afterBounds.height() / afterBounds.width(); - assertEquals(LetterboxConfiguration.DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW, + assertEquals(AppCompatConfiguration.DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW, actualAspectRatio, DELTA_ASPECT_RATIO_TOLERANCE); assertTrue(mActivity.areBoundsLetterboxed()); } @@ -2027,9 +2027,9 @@ public class SizeCompatTests extends WindowTestsBase { mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); final float fixedOrientationLetterboxAspectRatio = 1.1f; - mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio( + mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio( fixedOrientationLetterboxAspectRatio); - mActivity.mWmService.mLetterboxConfiguration.setDefaultMinAspectRatioForUnresizableApps( + mActivity.mWmService.mAppCompatConfiguration.setDefaultMinAspectRatioForUnresizableApps( 1.5f); prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); @@ -2049,7 +2049,7 @@ public class SizeCompatTests extends WindowTestsBase { // Letterbox logic should use config_letterboxDefaultMinAspectRatioForUnresizableApps over // config_fixedOrientationLetterboxAspectRatio. assertEquals(displayBounds.height(), activityBounds.height()); - final float defaultAspectRatio = mActivity.mWmService.mLetterboxConfiguration + final float defaultAspectRatio = mActivity.mWmService.mAppCompatConfiguration .getDefaultMinAspectRatioForUnresizableApps(); assertEquals(displayBounds.height() / defaultAspectRatio, activityBounds.width(), 0.5); } @@ -2136,10 +2136,10 @@ public class SizeCompatTests extends WindowTestsBase { int screenHeight = 1400; setUpDisplaySizeWithApp(screenWidth, screenHeight); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.mWmService.mLetterboxConfiguration + mActivity.mWmService.mAppCompatConfiguration .setIsSplitScreenAspectRatioForUnresizableAppsEnabled(true); - mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.1f); + mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.1f); prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); @@ -2172,8 +2172,8 @@ public class SizeCompatTests extends WindowTestsBase { final int displayHeight = 1400; setUpDisplaySizeWithApp(displayWidth, displayHeight); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - spyOn(mActivity.mWmService.mLetterboxConfiguration); - doReturn(true).when(mActivity.mWmService.mLetterboxConfiguration) + spyOn(mActivity.mWmService.mAppCompatConfiguration); + doReturn(true).when(mActivity.mWmService.mAppCompatConfiguration) .isUserAppAspectRatioFullscreenEnabled(); // Set user aspect ratio override @@ -2197,8 +2197,8 @@ public class SizeCompatTests extends WindowTestsBase { final int displayHeight = 1600; setUpDisplaySizeWithApp(displayWidth, displayHeight); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - spyOn(mActivity.mWmService.mLetterboxConfiguration); - doReturn(true).when(mActivity.mWmService.mLetterboxConfiguration) + spyOn(mActivity.mWmService.mAppCompatConfiguration); + doReturn(true).when(mActivity.mWmService.mAppCompatConfiguration) .isUserAppAspectRatioFullscreenEnabled(); // Set user aspect ratio override @@ -2394,8 +2394,8 @@ public class SizeCompatTests extends WindowTestsBase { boolean enabled) { final ActivityRecord activity = getActivityBuilderOnSameTask().build(); activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - spyOn(activity.mWmService.mLetterboxConfiguration); - doReturn(enabled).when(activity.mWmService.mLetterboxConfiguration) + spyOn(activity.mWmService.mAppCompatConfiguration); + doReturn(enabled).when(activity.mWmService.mAppCompatConfiguration) .isUserAppAspectRatioSettingsEnabled(); // Set user aspect ratio override final IPackageManager pm = mAtm.getPackageManager(); @@ -2428,7 +2428,7 @@ public class SizeCompatTests extends WindowTestsBase { .build(); // Setup Letterbox Configuration activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - activity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f); + activity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f); // Non-resizable portrait activity prepareUnresizable(activity, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); float expectedAspectRatio = 1f * displayWidth / getExpectedSplitSize(displayHeight); @@ -2449,7 +2449,7 @@ public class SizeCompatTests extends WindowTestsBase { .build(); // Setup Letterbox Configuration activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - activity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f); + activity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f); // Non-resizable portrait activity prepareUnresizable(activity, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); float expectedAspectRatio = 1f * displayHeight / getExpectedSplitSize(displayWidth); @@ -2471,7 +2471,7 @@ public class SizeCompatTests extends WindowTestsBase { .build(); // Setup Letterbox Configuration activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - activity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f); + activity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f); // Non-resizable portrait activity prepareUnresizable(activity, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); float expectedAspectRatio = 1f * displayWidth / getExpectedSplitSize(displayHeight); @@ -2493,7 +2493,7 @@ public class SizeCompatTests extends WindowTestsBase { .build(); // Setup Letterbox Configuration activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - activity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f); + activity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f); // Non-resizable portrait activity prepareUnresizable(activity, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); float expectedAspectRatio = 1f * displayHeight / getExpectedSplitSize(displayWidth); @@ -2581,7 +2581,7 @@ public class SizeCompatTests extends WindowTestsBase { public void testOverrideMinAspectRatioExcludePortraitFullscreen() { setUpDisplaySizeWithApp(2600, 1600); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.33f); + mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.33f); // Create a size compat activity on the same task. final ActivityRecord activity = getActivityBuilderOnSameTask().build(); @@ -2611,7 +2611,7 @@ public class SizeCompatTests extends WindowTestsBase { // In this test, the activity is not in fullscreen, so the override is not applied setUpDisplaySizeWithApp(2600, 1600); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.33f); + mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.33f); // Create a size compat activity on the same task. final ActivityRecord activity = getActivityBuilderOnSameTask().build(); @@ -2677,10 +2677,10 @@ public class SizeCompatTests extends WindowTestsBase { int screenHeight = 1600; setUpDisplaySizeWithApp(screenWidth, screenHeight); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.mWmService.mLetterboxConfiguration + mActivity.mWmService.mAppCompatConfiguration .setIsSplitScreenAspectRatioForUnresizableAppsEnabled(true); - mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.1f); + mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.1f); prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE); @@ -2714,11 +2714,11 @@ public class SizeCompatTests extends WindowTestsBase { int displayHeight = 1600; setUpDisplaySizeWithApp(displayWidth, displayHeight); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mWm.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(2f); + mWm.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(2f); // Enable display aspect ratio to take precedence before // fixedOrientationLetterboxAspectRatio - mWm.mLetterboxConfiguration + mWm.mAppCompatConfiguration .setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(true); // Set up resizable app in portrait @@ -2750,11 +2750,11 @@ public class SizeCompatTests extends WindowTestsBase { int displayHeight = 1400; setUpDisplaySizeWithApp(displayWidth, displayHeight); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mWm.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(2f); + mWm.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(2f); // Enable display aspect ratio to take precedence before // fixedOrientationLetterboxAspectRatio - mWm.mLetterboxConfiguration + mWm.mAppCompatConfiguration .setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(true); // Set up resizable app in landscape @@ -2787,10 +2787,10 @@ public class SizeCompatTests extends WindowTestsBase { setUpDisplaySizeWithApp(displayWidth, displayHeight); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.1f); + mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.1f); // Enable display aspect ratio to take precedence before // fixedOrientationLetterboxAspectRatio - mWm.mLetterboxConfiguration + mWm.mAppCompatConfiguration .setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(true); prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE); @@ -2814,10 +2814,10 @@ public class SizeCompatTests extends WindowTestsBase { setUpDisplaySizeWithApp(displayWidth, displayHeight); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.1f); + mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.1f); // Enable display aspect ratio to take precedence before // fixedOrientationLetterboxAspectRatio - mWm.mLetterboxConfiguration + mWm.mAppCompatConfiguration .setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(true); prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); @@ -3134,14 +3134,14 @@ public class SizeCompatTests extends WindowTestsBase { RESIZE_MODE_UNRESIZEABLE, SCREEN_ORIENTATION_PORTRAIT); activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - spyOn(activity.mWmService.mLetterboxConfiguration); - doReturn(true).when(activity.mWmService.mLetterboxConfiguration) + spyOn(activity.mWmService.mAppCompatConfiguration); + doReturn(true).when(activity.mWmService.mAppCompatConfiguration) .isIgnoreOrientationRequestAllowed(); // Display should not be rotated. assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, activity.mDisplayContent.getOrientation()); - doReturn(false).when(activity.mWmService.mLetterboxConfiguration) + doReturn(false).when(activity.mWmService.mAppCompatConfiguration) .isIgnoreOrientationRequestAllowed(); // Display should be rotated. @@ -3427,7 +3427,7 @@ public class SizeCompatTests extends WindowTestsBase { // Case when the reachability would be enabled otherwise setUpDisplaySizeWithApp(/* dw */ 1000, /* dh */ 2800); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mWm.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true); + mWm.mAppCompatConfiguration.setIsVerticalReachabilityEnabled(true); prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE); mActivity.getWindowConfiguration().setBounds(null); @@ -3442,7 +3442,7 @@ public class SizeCompatTests extends WindowTestsBase { mAtm.mDevEnableNonResizableMultiWindow = true; setUpDisplaySizeWithApp(2800, 1000); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mWm.mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(true); + mWm.mAppCompatConfiguration.setIsHorizontalReachabilityEnabled(true); setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true); final TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm, mActivity.getDisplayContent()); @@ -3465,7 +3465,7 @@ public class SizeCompatTests extends WindowTestsBase { mAtm.mDevEnableNonResizableMultiWindow = true; setUpDisplaySizeWithApp(1000, 2800); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mWm.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true); + mWm.mAppCompatConfiguration.setIsVerticalReachabilityEnabled(true); setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true); final TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm, mActivity.getDisplayContent()); @@ -3487,7 +3487,7 @@ public class SizeCompatTests extends WindowTestsBase { public void testIsVerticalReachabilityEnabled_doesNotMatchParentWidth_false() { setUpDisplaySizeWithApp(1000, 2800); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mWm.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true); + mWm.mAppCompatConfiguration.setIsVerticalReachabilityEnabled(true); setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true); // Unresizable landscape-only activity. @@ -3509,7 +3509,7 @@ public class SizeCompatTests extends WindowTestsBase { public void testIsVerticalReachabilityEnabled_emptyBounds_true() { setUpDisplaySizeWithApp(/* dw */ 1000, /* dh */ 2800); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mWm.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true); + mWm.mAppCompatConfiguration.setIsVerticalReachabilityEnabled(true); setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true); prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE); @@ -3526,7 +3526,7 @@ public class SizeCompatTests extends WindowTestsBase { public void testIsHorizontalReachabilityEnabled_emptyBounds_true() { setUpDisplaySizeWithApp(/* dw */ 2800, /* dh */ 1000); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mWm.mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(true); + mWm.mAppCompatConfiguration.setIsHorizontalReachabilityEnabled(true); setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true); prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); @@ -3543,7 +3543,7 @@ public class SizeCompatTests extends WindowTestsBase { public void testIsHorizontalReachabilityEnabled_portraitDisplayAndApp_true() { // Portrait display setUpDisplaySizeWithApp(1400, 1600); - mActivity.mWmService.mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(true); + mActivity.mWmService.mAppCompatConfiguration.setIsHorizontalReachabilityEnabled(true); setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true); // 16:9f unresizable portrait app @@ -3557,7 +3557,7 @@ public class SizeCompatTests extends WindowTestsBase { public void testIsVerticalReachabilityEnabled_landscapeDisplayAndApp_true() { // Landscape display setUpDisplaySizeWithApp(1600, 1500); - mActivity.mWmService.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true); + mActivity.mWmService.mAppCompatConfiguration.setIsVerticalReachabilityEnabled(true); setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true); // 16:9f unresizable landscape app @@ -3571,7 +3571,7 @@ public class SizeCompatTests extends WindowTestsBase { public void testIsHorizontalReachabilityEnabled_doesNotMatchParentHeight_false() { setUpDisplaySizeWithApp(2800, 1000); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mWm.mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(true); + mWm.mAppCompatConfiguration.setIsHorizontalReachabilityEnabled(true); setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true); // Unresizable portrait-only activity. @@ -3593,7 +3593,7 @@ public class SizeCompatTests extends WindowTestsBase { public void testIsHorizontalReachabilityEnabled_inSizeCompatMode_matchesParentHeight_true() { setUpDisplaySizeWithApp(1800, 2200); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mWm.mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(true); + mWm.mAppCompatConfiguration.setIsHorizontalReachabilityEnabled(true); setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true); // Unresizable portrait-only activity. @@ -3615,7 +3615,7 @@ public class SizeCompatTests extends WindowTestsBase { public void testIsVerticalReachabilityEnabled_inSizeCompatMode_matchesParentWidth_true() { setUpDisplaySizeWithApp(2200, 1800); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mWm.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true); + mWm.mAppCompatConfiguration.setIsVerticalReachabilityEnabled(true); setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true); // Unresizable landscape-only activity. @@ -3698,7 +3698,7 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testLetterboxDetailsForStatusBar_letterboxNotOverlappingStatusBar() { // Align to center so that we don't overlap with the status bar - mAtm.mWindowManager.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f); + mAtm.mWindowManager.mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier(0.5f); final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2800) .setNotch(100) .build(); @@ -3751,7 +3751,7 @@ public class SizeCompatTests extends WindowTestsBase { navSource.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER); navSource.setFrame(new Rect(0, screenHeight - taskbarHeight, screenWidth, screenHeight)); - mActivity.mWmService.mLetterboxConfiguration.setLetterboxActivityCornersRadius(15); + mActivity.mWmService.mAppCompatConfiguration.setLetterboxActivityCornersRadius(15); final WindowState w1 = addWindowToActivity(mActivity); w1.mAboveInsetsState.addSource(navSource); @@ -3793,7 +3793,7 @@ public class SizeCompatTests extends WindowTestsBase { setUpDisplaySizeWithApp(screenWidth, screenHeight); mActivity.mDisplayContent.setIgnoreOrientationRequest(true); - mActivity.mWmService.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(1.0f); + mActivity.mWmService.mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier(1.0f); final InsetsSource navSource = new InsetsSource( InsetsSource.createId(null, 0, navigationBars()), navigationBars()); @@ -3801,7 +3801,7 @@ public class SizeCompatTests extends WindowTestsBase { // Immersive activity has transient navbar navSource.setVisible(!immersive); navSource.setFrame(new Rect(0, screenHeight - taskbarHeight, screenWidth, screenHeight)); - mActivity.mWmService.mLetterboxConfiguration.setLetterboxActivityCornersRadius(15); + mActivity.mWmService.mAppCompatConfiguration.setLetterboxActivityCornersRadius(15); final WindowState w1 = addWindowToActivity(mActivity); w1.mAboveInsetsState.addSource(navSource); @@ -3909,7 +3909,7 @@ public class SizeCompatTests extends WindowTestsBase { spyOn(policy); doReturn(decorInfo).when(policy).getDecorInsetsInfo(ROTATION_90, display.mBaseDisplayHeight, display.mBaseDisplayWidth); - mWm.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f); + mWm.mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier(0.5f); setUpApp(display); prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); @@ -3974,7 +3974,7 @@ public class SizeCompatTests extends WindowTestsBase { setUpDisplaySizeWithApp(2800, 1400); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.mWmService.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier( + mActivity.mWmService.mAppCompatConfiguration.setLetterboxHorizontalPositionMultiplier( letterboxHorizontalPositionMultiplier); prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); assertEquals(fixedOrientationLetterbox, mActivity.getBounds()); @@ -4171,7 +4171,7 @@ public class SizeCompatTests extends WindowTestsBase { public void testApplyAspectRatio_containingRatioAlmostEqualToMaxRatio_boundsUnchanged() { setUpDisplaySizeWithApp(1981, 2576); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mWm.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f); + mWm.mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier(0.5f); final Rect originalBounds = new Rect(mActivity.getBounds()); prepareUnresizable(mActivity, 1.3f, SCREEN_ORIENTATION_UNSPECIFIED); @@ -4206,7 +4206,7 @@ public class SizeCompatTests extends WindowTestsBase { spyOn(policy); doReturn(decorInfo).when(policy).getDecorInsetsInfo(ROTATION_90, display.mBaseDisplayHeight, display.mBaseDisplayWidth); - mWm.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f); + mWm.mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier(0.5f); setUpApp(display); prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE); @@ -4269,7 +4269,7 @@ public class SizeCompatTests extends WindowTestsBase { // Set up a display in portrait with a fixed-orientation LANDSCAPE app setUpDisplaySizeWithApp(1400, 2800); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.mWmService.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier( + mActivity.mWmService.mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier( 1.0f /*letterboxVerticalPositionMultiplier*/); prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE); @@ -4294,7 +4294,7 @@ public class SizeCompatTests extends WindowTestsBase { // Set up a display in portrait with a fixed-orientation LANDSCAPE app. setUpDisplaySizeWithApp(1000, 2000); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.mWmService.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier( + mActivity.mWmService.mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier( 1.0f /*letterboxVerticalPositionMultiplier*/); prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE); @@ -4322,9 +4322,9 @@ public class SizeCompatTests extends WindowTestsBase { public void testGetFixedOrientationLetterboxAspectRatio_tabletop_centered() { // Set up a display in portrait with a fixed-orientation LANDSCAPE app setUpDisplaySizeWithApp(1400, 2800); - mWm.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier( + mWm.mAppCompatConfiguration.setLetterboxHorizontalPositionMultiplier( LETTERBOX_POSITION_MULTIPLIER_CENTER); - mActivity.mWmService.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier( + mActivity.mWmService.mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier( 1.0f /*letterboxVerticalPositionMultiplier*/); prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE); @@ -4360,8 +4360,8 @@ public class SizeCompatTests extends WindowTestsBase { // Set up a display in landscape with a fixed-orientation PORTRAIT app setUpDisplaySizeWithApp(2800, 1400); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mWm.mLetterboxConfiguration.setIsAutomaticReachabilityInBookModeEnabled(true); - mWm.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier( + mWm.mAppCompatConfiguration.setIsAutomaticReachabilityInBookModeEnabled(true); + mWm.mAppCompatConfiguration.setLetterboxHorizontalPositionMultiplier( 1.0f /*letterboxHorizontalPositionMultiplier*/); prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); @@ -4386,8 +4386,8 @@ public class SizeCompatTests extends WindowTestsBase { // Set up a display in landscape with a fixed-orientation PORTRAIT app setUpDisplaySizeWithApp(2800, 1400); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mWm.mLetterboxConfiguration.setIsAutomaticReachabilityInBookModeEnabled(false); - mWm.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(0.5f); + mWm.mAppCompatConfiguration.setIsAutomaticReachabilityInBookModeEnabled(false); + mWm.mAppCompatConfiguration.setLetterboxHorizontalPositionMultiplier(0.5f); prepareUnresizable(mActivity, 1.75f, SCREEN_ORIENTATION_PORTRAIT); Rect letterboxNoFold = new Rect(1000, 0, 1800, 1400); @@ -4469,7 +4469,7 @@ public class SizeCompatTests extends WindowTestsBase { setUpDisplaySizeWithApp(1400, 2800); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.mWmService.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier( + mActivity.mWmService.mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier( letterboxVerticalPositionMultiplier); prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE); @@ -4585,7 +4585,7 @@ public class SizeCompatTests extends WindowTestsBase { public void testIsEligibleForLetterboxEducation_educationNotEnabled_returnsFalse() { setUpDisplaySizeWithApp(2500, 1000); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.mWmService.mLetterboxConfiguration.setIsEducationEnabled(false); + mActivity.mWmService.mAppCompatConfiguration.setIsEducationEnabled(false); prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); @@ -4596,7 +4596,7 @@ public class SizeCompatTests extends WindowTestsBase { public void testIsEligibleForLetterboxEducation_notEligibleForFixedOrientation_returnsFalse() { setUpDisplaySizeWithApp(1000, 2500); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.mWmService.mLetterboxConfiguration.setIsEducationEnabled(true); + mActivity.mWmService.mAppCompatConfiguration.setIsEducationEnabled(true); prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); @@ -4609,7 +4609,7 @@ public class SizeCompatTests extends WindowTestsBase { mAtm.mDevEnableNonResizableMultiWindow = true; setUpDisplaySizeWithApp(1000, 1200); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.mWmService.mLetterboxConfiguration.setIsEducationEnabled(true); + mActivity.mWmService.mAppCompatConfiguration.setIsEducationEnabled(true); final TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm, mActivity.getDisplayContent()); @@ -4630,7 +4630,7 @@ public class SizeCompatTests extends WindowTestsBase { public void testIsEligibleForLetterboxEducation_fixedOrientationLandscape_returnsFalse() { setUpDisplaySizeWithApp(1000, 2500); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.mWmService.mLetterboxConfiguration.setIsEducationEnabled(true); + mActivity.mWmService.mAppCompatConfiguration.setIsEducationEnabled(true); prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE); @@ -4643,7 +4643,7 @@ public class SizeCompatTests extends WindowTestsBase { public void testIsEligibleForLetterboxEducation_hasStartingWindow_returnsFalseUntilRemoved() { setUpDisplaySizeWithApp(2500, 1000); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.mWmService.mLetterboxConfiguration.setIsEducationEnabled(true); + mActivity.mWmService.mAppCompatConfiguration.setIsEducationEnabled(true); prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); mActivity.mStartingData = mock(StartingData.class); @@ -4666,7 +4666,7 @@ public class SizeCompatTests extends WindowTestsBase { public void testIsEligibleForLetterboxEducation_hasStartingWindowAndEducationNotEnabled() { setUpDisplaySizeWithApp(2500, 1000); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.mWmService.mLetterboxConfiguration.setIsEducationEnabled(false); + mActivity.mWmService.mAppCompatConfiguration.setIsEducationEnabled(false); prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); mActivity.mStartingData = mock(StartingData.class); @@ -4689,7 +4689,7 @@ public class SizeCompatTests extends WindowTestsBase { public void testIsEligibleForLetterboxEducation_letterboxedForFixedOrientation_returnsTrue() { setUpDisplaySizeWithApp(2500, 1000); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.mWmService.mLetterboxConfiguration.setIsEducationEnabled(true); + mActivity.mWmService.mAppCompatConfiguration.setIsEducationEnabled(true); prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); @@ -4702,7 +4702,7 @@ public class SizeCompatTests extends WindowTestsBase { public void testIsEligibleForLetterboxEducation_sizeCompatAndEligibleForFixedOrientation() { setUpDisplaySizeWithApp(1000, 2500); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.mWmService.mLetterboxConfiguration.setIsEducationEnabled(true); + mActivity.mWmService.mAppCompatConfiguration.setIsEducationEnabled(true); prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); @@ -4879,7 +4879,7 @@ public class SizeCompatTests extends WindowTestsBase { setUpDisplaySizeWithApp(2800, 1400); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.mWmService.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier( + mActivity.mWmService.mAppCompatConfiguration.setLetterboxHorizontalPositionMultiplier( letterboxHorizontalPositionMultiplier); prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE); assertFitted(); @@ -4896,7 +4896,7 @@ public class SizeCompatTests extends WindowTestsBase { setUpDisplaySizeWithApp(1400, 2800); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.mWmService.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier( + mActivity.mWmService.mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier( letterboxVerticalPositionMultiplier); prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 76690ec1691f..0bf850afb30b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -611,7 +611,7 @@ public class TaskTests extends WindowTestsBase { .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build(); final Task task = rootTask.getBottomMostTask(); final ActivityRecord root = task.getTopNonFinishingActivity(); - spyOn(mWm.mLetterboxConfiguration); + spyOn(mWm.mAppCompatConfiguration); spyOn(root); spyOn(root.mAppCompatController.getAppCompatAspectRatioOverrides()); @@ -655,7 +655,7 @@ public class TaskTests extends WindowTestsBase { .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build(); final Task task = rootTask.getBottomMostTask(); final ActivityRecord root = task.getTopNonFinishingActivity(); - spyOn(mWm.mLetterboxConfiguration); + spyOn(mWm.mAppCompatConfiguration); spyOn(root); doReturn(false).when(root).fillsParent(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java index f07b402c31b6..cbf17c408115 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java @@ -258,7 +258,7 @@ public class TransparentPolicyTest extends WindowTestsBase { robot.transparentActivity((ta) -> { ta.applyOnActivity((a) -> { a.applyToTopActivity((topActivity) -> { - topActivity.mWmService.mLetterboxConfiguration + topActivity.mWmService.mAppCompatConfiguration .setLetterboxHorizontalPositionMultiplier(1.0f); }); a.configureUnresizableTopActivity(SCREEN_ORIENTATION_PORTRAIT); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index 89abe2ff0866..39640fbc9422 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -47,10 +47,10 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR; +import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_WALLPAPER; import static com.google.common.truth.Truth.assertThat; @@ -1390,8 +1390,8 @@ public class WindowManagerServiceTests extends WindowTestsBase { } private boolean setupLetterboxConfigurationWithBackgroundType( - @LetterboxConfiguration.LetterboxBackgroundType int letterboxBackgroundType) { - mWm.mLetterboxConfiguration.setLetterboxBackgroundTypeOverride(letterboxBackgroundType); + @AppCompatConfiguration.LetterboxBackgroundType int letterboxBackgroundType) { + mWm.mAppCompatConfiguration.setLetterboxBackgroundTypeOverride(letterboxBackgroundType); return mWm.isLetterboxBackgroundMultiColored(); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java index 0cb22ad47355..e6648dad4bbe 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java @@ -18,7 +18,6 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; -import static android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED; import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; @@ -87,7 +86,6 @@ public class WindowProcessControllerTests extends WindowTestsBase { ApplicationInfo info = mock(ApplicationInfo.class); info.packageName = "test.package.name"; - doReturn(true).when(info).isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED); mWpc = new WindowProcessController( mAtm, info, null, 0, -1, null, mMockListener); mWpc.setThread(mock(IApplicationThread.class)); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index b512aa852ddb..41f1ac72c56d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -262,34 +262,34 @@ class WindowTestsBase extends SystemServiceTestsBase { // Ensure letterbox aspect ratio is not overridden on any device target. // {@link com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio}, is set // on some device form factors. - mAtm.mWindowManager.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(0); + mAtm.mWindowManager.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(0); // Ensure letterbox horizontal position multiplier is not overridden on any device target. // {@link com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier}, // may be set on some device form factors. - mAtm.mWindowManager.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(0.5f); + mAtm.mWindowManager.mAppCompatConfiguration.setLetterboxHorizontalPositionMultiplier(0.5f); // Ensure letterbox vertical position multiplier is not overridden on any device target. // {@link com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier}, // may be set on some device form factors. - mAtm.mWindowManager.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.0f); + mAtm.mWindowManager.mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier(0.0f); // Ensure letterbox horizontal reachability treatment isn't overridden on any device target. // {@link com.android.internal.R.bool.config_letterboxIsHorizontalReachabilityEnabled}, // may be set on some device form factors. - mAtm.mWindowManager.mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(false); + mAtm.mWindowManager.mAppCompatConfiguration.setIsHorizontalReachabilityEnabled(false); // Ensure letterbox vertical reachability treatment isn't overridden on any device target. // {@link com.android.internal.R.bool.config_letterboxIsVerticalReachabilityEnabled}, // may be set on some device form factors. - mAtm.mWindowManager.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(false); + mAtm.mWindowManager.mAppCompatConfiguration.setIsVerticalReachabilityEnabled(false); // Ensure aspect ratio for unresizable apps isn't overridden on any device target. // {@link com.android.internal.R.bool // .config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled}, may be set on some // device form factors. - mAtm.mWindowManager.mLetterboxConfiguration + mAtm.mWindowManager.mAppCompatConfiguration .setIsSplitScreenAspectRatioForUnresizableAppsEnabled(false); // Ensure aspect ratio for al apps isn't overridden on any device target. // {@link com.android.internal.R.bool // .config_letterboxIsDisplayAspectRatioForFixedOrientationLetterboxEnabled}, may be set on // some device form factors. - mAtm.mWindowManager.mLetterboxConfiguration + mAtm.mWindowManager.mAppCompatConfiguration .setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(false); // Setup WallpaperController crop utils with a simple center-align strategy @@ -331,7 +331,7 @@ class WindowTestsBase extends SystemServiceTestsBase { private void checkDeviceSpecificOverridesNotApplied() { // Check global overrides if (!sGlobalOverridesChecked) { - assertEquals(0, mWm.mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio(), + assertEquals(0, mWm.mAppCompatConfiguration.getFixedOrientationLetterboxAspectRatio(), 0 /* delta */); sGlobalOverridesChecked = true; } diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index bf46154caf3e..010a32274dcd 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -1064,10 +1064,8 @@ public class UsageStatsService extends SystemService implements synchronized (mReportedEvents) { LinkedList<Event> events = mReportedEvents.get(userId); if (events == null) { - // TODO (b/347644400): callers of this API should verify that the userId passed to - // this method exists - there is currently a known case where USER_ALL is passed - // here and it would be added to the queue, never to be flushed correctly. The logic - // below should only remain as a last-resort catch-all fix. + // Callers of this API should verify that the userId passed to this method exists. + // The logic below should only remain as a last-resort catch-all fix. final UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class); if (umi == null || (umi != null && !umi.exists(userId))) { // The userId passed is a non-existent user so don't report the event. @@ -3239,6 +3237,18 @@ public class UsageStatsService extends SystemService implements } @Override + public void reportEventForAllUsers(String packageName, int eventType) { + if (packageName == null) { + Slog.w(TAG, "Event reported without a package name, eventType:" + eventType); + return; + } + + Event event = new Event(eventType, SystemClock.elapsedRealtime()); + event.mPackage = packageName; + reportEventToAllUserId(event); + } + + @Override public void reportConfigurationChange(Configuration config, int userId) { if (config == null) { Slog.w(TAG, "Configuration event reported with a null config"); diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java index 277a508ced57..5ecf5cf0b723 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java @@ -115,6 +115,9 @@ public class AppInfoFactory implements AslMarshallableFactory<AppInfo> { /** Creates a {@link AppInfo} from the human-readable DOM element. */ public AppInfo createFromHrElement(Element appInfoEle, long version) throws MalformedXmlException { + if (appInfoEle == null) { + return null; + } XmlUtils.throwIfExtraneousAttributes( appInfoEle, XmlUtils.getMostRecentVersion(mRecognizedHrAttrs, version)); XmlUtils.throwIfExtraneousChildrenHr( @@ -184,6 +187,9 @@ public class AppInfoFactory implements AslMarshallableFactory<AppInfo> { /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */ public AppInfo createFromOdElement(Element appInfoEle, long version) throws MalformedXmlException { + if (appInfoEle == null) { + return null; + } XmlUtils.throwIfExtraneousChildrenOd( appInfoEle, XmlUtils.getMostRecentVersion(mRecognizedOdEleNames, version)); var requiredOdEles = XmlUtils.getMostRecentVersion(mRequiredOdEles, version); diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java index 14e65e5e5b2b..e3aa50a4cee2 100644 --- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java +++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java @@ -20,8 +20,11 @@ import com.android.asllib.marshallable.AndroidSafetyLabelTest; import com.android.asllib.marshallable.AppInfoTest; import com.android.asllib.marshallable.DataLabelsTest; import com.android.asllib.marshallable.DataTypeEqualityTest; +import com.android.asllib.marshallable.DeveloperInfoTest; import com.android.asllib.marshallable.SafetyLabelsTest; +import com.android.asllib.marshallable.SecurityLabelsTest; import com.android.asllib.marshallable.SystemAppSafetyLabelTest; +import com.android.asllib.marshallable.ThirdPartyVerificationTest; import com.android.asllib.marshallable.TransparencyInfoTest; import org.junit.runner.RunWith; @@ -36,6 +39,9 @@ import org.junit.runners.Suite; DataTypeEqualityTest.class, SafetyLabelsTest.class, SystemAppSafetyLabelTest.class, - TransparencyInfoTest.class + TransparencyInfoTest.class, + DeveloperInfoTest.class, + SecurityLabelsTest.class, + ThirdPartyVerificationTest.class }) public class AllTests {} diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AndroidSafetyLabelTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AndroidSafetyLabelTest.java index 283ccbc44791..6470c060af87 100644 --- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AndroidSafetyLabelTest.java +++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AndroidSafetyLabelTest.java @@ -28,6 +28,7 @@ import org.junit.runners.JUnit4; import org.w3c.dom.Element; import java.nio.file.Paths; +import java.util.List; @RunWith(JUnit4.class) public class AndroidSafetyLabelTest { @@ -37,12 +38,16 @@ public class AndroidSafetyLabelTest { "com/android/asllib/androidsafetylabel/od"; private static final String MISSING_VERSION_FILE_NAME = "missing-version.xml"; - private static final String VALID_EMPTY_FILE_NAME = "valid-empty.xml"; + private static final String VALID_V2_FILE_NAME = "valid-empty.xml"; + private static final String VALID_V1_FILE_NAME = "valid-v1.xml"; private static final String WITH_SAFETY_LABELS_FILE_NAME = "with-safety-labels.xml"; private static final String WITH_SYSTEM_APP_SAFETY_LABEL_FILE_NAME = "with-system-app-safety-label.xml"; private static final String WITH_TRANSPARENCY_INFO_FILE_NAME = "with-transparency-info.xml"; + public static final List<String> REQUIRED_FIELD_NAMES_OD_V2 = + List.of("system_app_safety_label", "transparency_info"); + @Before public void setUp() throws Exception { System.out.println("set up."); @@ -56,12 +61,12 @@ public class AndroidSafetyLabelTest { odToHrExpectException(MISSING_VERSION_FILE_NAME); } - /** Test for android safety label valid empty. */ + /** Test for android safety label valid v2. */ @Test - public void testAndroidSafetyLabelValidEmptyFile() throws Exception { - System.out.println("starting testAndroidSafetyLabelValidEmptyFile."); - testHrToOdAndroidSafetyLabel(VALID_EMPTY_FILE_NAME); - testOdToHrAndroidSafetyLabel(VALID_EMPTY_FILE_NAME); + public void testAndroidSafetyLabelValidV2File() throws Exception { + System.out.println("starting testAndroidSafetyLabelValidV2File."); + testHrToOdAndroidSafetyLabel(VALID_V2_FILE_NAME); + testOdToHrAndroidSafetyLabel(VALID_V2_FILE_NAME); } /** Test for android safety label with safety labels. */ @@ -72,6 +77,34 @@ public class AndroidSafetyLabelTest { testOdToHrAndroidSafetyLabel(WITH_SAFETY_LABELS_FILE_NAME); } + /** Tests missing required fields fails, V2. */ + @Test + public void testMissingRequiredFieldsOdV2() throws Exception { + for (String reqField : REQUIRED_FIELD_NAMES_OD_V2) { + System.out.println("testing missing required field od v2: " + reqField); + var ele = + TestUtils.getElementFromResource( + Paths.get(ANDROID_SAFETY_LABEL_OD_PATH, VALID_V2_FILE_NAME)); + TestUtils.removeOdChildEleWithName(ele, reqField); + assertThrows( + MalformedXmlException.class, + () -> new AndroidSafetyLabelFactory().createFromOdElement(ele)); + } + } + + /** Tests missing optional fields succeeds, V1. */ + @Test + public void testMissingOptionalFieldsOdV1() throws Exception { + for (String reqField : REQUIRED_FIELD_NAMES_OD_V2) { + System.out.println("testing missing optional field od v1: " + reqField); + var ele = + TestUtils.getElementFromResource( + Paths.get(ANDROID_SAFETY_LABEL_OD_PATH, VALID_V1_FILE_NAME)); + TestUtils.removeOdChildEleWithName(ele, reqField); + var unused = new AndroidSafetyLabelFactory().createFromOdElement(ele); + } + } + private void hrToOdExpectException(String fileName) { assertThrows( MalformedXmlException.class, diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java index b557fea9572b..cc58a61760f4 100644 --- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java +++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java @@ -35,8 +35,6 @@ import javax.xml.parsers.ParserConfigurationException; @RunWith(JUnit4.class) public class DataLabelsTest { - private static final long DEFAULT_VERSION = 2L; - private static final String DATA_LABELS_HR_PATH = "com/android/asllib/datalabels/hr"; private static final String DATA_LABELS_OD_PATH = "com/android/asllib/datalabels/od"; diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java new file mode 100644 index 000000000000..a4472b1b78e5 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.asllib.marshallable; + +import static org.junit.Assert.assertThrows; + +import com.android.asllib.testutils.TestUtils; +import com.android.asllib.util.MalformedXmlException; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.w3c.dom.Element; + +import java.nio.file.Paths; +import java.util.List; + +@RunWith(JUnit4.class) +public class DeveloperInfoTest { + private static final String DEVELOPER_INFO_HR_PATH = "com/android/asllib/developerinfo/hr"; + private static final String DEVELOPER_INFO_OD_PATH = "com/android/asllib/developerinfo/od"; + public static final List<String> REQUIRED_FIELD_NAMES = + List.of("address", "countryRegion", "email", "name", "relationship"); + public static final List<String> REQUIRED_FIELD_NAMES_OD = + List.of("address", "country_region", "email", "name", "relationship"); + public static final List<String> OPTIONAL_FIELD_NAMES = List.of("website", "registryId"); + public static final List<String> OPTIONAL_FIELD_NAMES_OD = + List.of("website", "app_developer_registry_id"); + + private static final String ALL_FIELDS_VALID_FILE_NAME = "all-fields-valid.xml"; + + /** Logic for setting up tests (empty if not yet needed). */ + public static void main(String[] params) throws Exception {} + + @Before + public void setUp() throws Exception { + System.out.println("set up."); + } + + /** Test for all fields valid. */ + @Test + public void testAllFieldsValid() throws Exception { + System.out.println("starting testAllFieldsValid."); + testHrToOdDeveloperInfo(ALL_FIELDS_VALID_FILE_NAME); + testOdToHrDeveloperInfo(ALL_FIELDS_VALID_FILE_NAME); + } + + /** Tests missing required fields fails. */ + @Test + public void testMissingRequiredFields() throws Exception { + System.out.println("Starting testMissingRequiredFields"); + for (String reqField : REQUIRED_FIELD_NAMES) { + System.out.println("testing missing required field: " + reqField); + var developerInfoEle = + TestUtils.getElementFromResource( + Paths.get(DEVELOPER_INFO_HR_PATH, ALL_FIELDS_VALID_FILE_NAME)); + developerInfoEle.removeAttribute(reqField); + + assertThrows( + MalformedXmlException.class, + () -> new DeveloperInfoFactory().createFromHrElement(developerInfoEle)); + } + + for (String reqField : REQUIRED_FIELD_NAMES_OD) { + System.out.println("testing missing required field od: " + reqField); + var developerInfoEle = + TestUtils.getElementFromResource( + Paths.get(DEVELOPER_INFO_OD_PATH, ALL_FIELDS_VALID_FILE_NAME)); + TestUtils.removeOdChildEleWithName(developerInfoEle, reqField); + + assertThrows( + MalformedXmlException.class, + () -> new DeveloperInfoFactory().createFromOdElement(developerInfoEle)); + } + } + + /** Tests missing optional fields passes. */ + @Test + public void testMissingOptionalFields() throws Exception { + for (String optField : OPTIONAL_FIELD_NAMES) { + var developerInfoEle = + TestUtils.getElementFromResource( + Paths.get(DEVELOPER_INFO_HR_PATH, ALL_FIELDS_VALID_FILE_NAME)); + developerInfoEle.removeAttribute(optField); + DeveloperInfo developerInfo = + new DeveloperInfoFactory().createFromHrElement(developerInfoEle); + developerInfo.toOdDomElement(TestUtils.document()); + } + + for (String optField : OPTIONAL_FIELD_NAMES_OD) { + var developerInfoEle = + TestUtils.getElementFromResource( + Paths.get(DEVELOPER_INFO_OD_PATH, ALL_FIELDS_VALID_FILE_NAME)); + TestUtils.removeOdChildEleWithName(developerInfoEle, optField); + DeveloperInfo developerInfo = + new DeveloperInfoFactory().createFromOdElement(developerInfoEle); + developerInfo.toHrDomElement(TestUtils.document()); + } + } + + private void testHrToOdDeveloperInfo(String fileName) throws Exception { + var doc = TestUtils.document(); + DeveloperInfo developerInfo = + new DeveloperInfoFactory() + .createFromHrElement( + TestUtils.getElementFromResource( + Paths.get(DEVELOPER_INFO_HR_PATH, fileName))); + Element developerInfoEle = developerInfo.toOdDomElement(doc); + doc.appendChild(developerInfoEle); + TestUtils.testFormatToFormat(doc, Paths.get(DEVELOPER_INFO_OD_PATH, fileName)); + } + + private void testOdToHrDeveloperInfo(String fileName) throws Exception { + var doc = TestUtils.document(); + DeveloperInfo developerInfo = + new DeveloperInfoFactory() + .createFromOdElement( + TestUtils.getElementFromResource( + Paths.get(DEVELOPER_INFO_OD_PATH, fileName))); + Element developerInfoEle = developerInfo.toHrDomElement(doc); + doc.appendChild(developerInfoEle); + TestUtils.testFormatToFormat(doc, Paths.get(DEVELOPER_INFO_HR_PATH, fileName)); + } +} diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java index 7cd510f0ddfc..fc8ff00794ad 100644 --- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java +++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java @@ -26,13 +26,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.w3c.dom.Element; -import org.xml.sax.SAXException; -import java.io.IOException; import java.nio.file.Paths; -import javax.xml.parsers.ParserConfigurationException; - @RunWith(JUnit4.class) public class SafetyLabelsTest { private static final long DEFAULT_VERSION = 2L; @@ -42,6 +38,8 @@ public class SafetyLabelsTest { private static final String VALID_EMPTY_FILE_NAME = "valid-empty.xml"; private static final String WITH_DATA_LABELS_FILE_NAME = "with-data-labels.xml"; + private static final String VALID_V1_FILE_NAME = "valid-v1.xml"; + private static final String UNRECOGNIZED_FIELD_V2_FILE_NAME = "unrecognized-field-v2.xml"; @Before public void setUp() throws Exception { @@ -52,61 +50,59 @@ public class SafetyLabelsTest { @Test public void testSafetyLabelsValidEmptyFile() throws Exception { System.out.println("starting testSafetyLabelsValidEmptyFile."); - testHrToOdSafetyLabels(VALID_EMPTY_FILE_NAME); - testOdToHrSafetyLabels(VALID_EMPTY_FILE_NAME); + testHrToOdSafetyLabels(VALID_EMPTY_FILE_NAME, DEFAULT_VERSION); + testOdToHrSafetyLabels(VALID_EMPTY_FILE_NAME, DEFAULT_VERSION); } /** Test for safety labels with data labels. */ @Test public void testSafetyLabelsWithDataLabels() throws Exception { System.out.println("starting testSafetyLabelsWithDataLabels."); - testHrToOdSafetyLabels(WITH_DATA_LABELS_FILE_NAME); - testOdToHrSafetyLabels(WITH_DATA_LABELS_FILE_NAME); + testHrToOdSafetyLabels(WITH_DATA_LABELS_FILE_NAME, DEFAULT_VERSION); + testOdToHrSafetyLabels(WITH_DATA_LABELS_FILE_NAME, DEFAULT_VERSION); } - private void hrToOdExpectException(String fileName) - throws ParserConfigurationException, IOException, SAXException { - var safetyLabelsEle = - TestUtils.getElementFromResource(Paths.get(SAFETY_LABELS_HR_PATH, fileName)); - assertThrows( - MalformedXmlException.class, - () -> - new SafetyLabelsFactory() - .createFromHrElement(safetyLabelsEle, DEFAULT_VERSION)); + /** Tests valid fields v1. */ + @Test + public void testValidFieldsV1() throws Exception { + var ele = + TestUtils.getElementFromResource( + Paths.get(SAFETY_LABELS_OD_PATH, VALID_V1_FILE_NAME)); + var unused = new SafetyLabelsFactory().createFromOdElement(ele, 1L); } - private void odToHrExpectException(String fileName) - throws ParserConfigurationException, IOException, SAXException { - var safetyLabelsEle = - TestUtils.getElementFromResource(Paths.get(SAFETY_LABELS_OD_PATH, fileName)); + /** Tests unrecognized field v2. */ + @Test + public void testUnrecognizedFieldV2() throws Exception { + var ele = + TestUtils.getElementFromResource( + Paths.get(SAFETY_LABELS_OD_PATH, VALID_V1_FILE_NAME)); assertThrows( MalformedXmlException.class, - () -> - new SafetyLabelsFactory() - .createFromOdElement(safetyLabelsEle, DEFAULT_VERSION)); + () -> new SafetyLabelsFactory().createFromOdElement(ele, 2L)); } - private void testHrToOdSafetyLabels(String fileName) throws Exception { + private void testHrToOdSafetyLabels(String fileName, long version) throws Exception { var doc = TestUtils.document(); SafetyLabels safetyLabels = new SafetyLabelsFactory() .createFromHrElement( TestUtils.getElementFromResource( Paths.get(SAFETY_LABELS_HR_PATH, fileName)), - DEFAULT_VERSION); + version); Element appInfoEle = safetyLabels.toOdDomElement(doc); doc.appendChild(appInfoEle); TestUtils.testFormatToFormat(doc, Paths.get(SAFETY_LABELS_OD_PATH, fileName)); } - private void testOdToHrSafetyLabels(String fileName) throws Exception { + private void testOdToHrSafetyLabels(String fileName, long version) throws Exception { var doc = TestUtils.document(); SafetyLabels safetyLabels = new SafetyLabelsFactory() .createFromOdElement( TestUtils.getElementFromResource( Paths.get(SAFETY_LABELS_OD_PATH, fileName)), - DEFAULT_VERSION); + version); Element appInfoEle = safetyLabels.toHrDomElement(doc); doc.appendChild(appInfoEle); TestUtils.testFormatToFormat(doc, Paths.get(SAFETY_LABELS_HR_PATH, fileName)); diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java new file mode 100644 index 000000000000..9d197a2cf7f5 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.asllib.marshallable; + +import com.android.asllib.testutils.TestUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.w3c.dom.Element; + +import java.nio.file.Paths; +import java.util.List; + +@RunWith(JUnit4.class) +public class SecurityLabelsTest { + private static final String SECURITY_LABELS_HR_PATH = "com/android/asllib/securitylabels/hr"; + private static final String SECURITY_LABELS_OD_PATH = "com/android/asllib/securitylabels/od"; + + public static final List<String> OPTIONAL_FIELD_NAMES = + List.of("isDataDeletable", "isDataEncrypted"); + public static final List<String> OPTIONAL_FIELD_NAMES_OD = + List.of("is_data_deletable", "is_data_encrypted"); + + private static final String ALL_FIELDS_VALID_FILE_NAME = "all-fields-valid.xml"; + + /** Logic for setting up tests (empty if not yet needed). */ + public static void main(String[] params) throws Exception {} + + @Before + public void setUp() throws Exception { + System.out.println("set up."); + } + + /** Test for all fields valid. */ + @Test + public void testAllFieldsValid() throws Exception { + System.out.println("starting testAllFieldsValid."); + testHrToOdSecurityLabels(ALL_FIELDS_VALID_FILE_NAME); + testOdToHrSecurityLabels(ALL_FIELDS_VALID_FILE_NAME); + } + + /** Tests missing optional fields passes. */ + @Test + public void testMissingOptionalFields() throws Exception { + for (String optField : OPTIONAL_FIELD_NAMES) { + var ele = + TestUtils.getElementFromResource( + Paths.get(SECURITY_LABELS_HR_PATH, ALL_FIELDS_VALID_FILE_NAME)); + ele.removeAttribute(optField); + SecurityLabels securityLabels = new SecurityLabelsFactory().createFromHrElement(ele); + securityLabels.toOdDomElement(TestUtils.document()); + } + for (String optField : OPTIONAL_FIELD_NAMES_OD) { + var ele = + TestUtils.getElementFromResource( + Paths.get(SECURITY_LABELS_OD_PATH, ALL_FIELDS_VALID_FILE_NAME)); + TestUtils.removeOdChildEleWithName(ele, optField); + SecurityLabels securityLabels = new SecurityLabelsFactory().createFromOdElement(ele); + securityLabels.toHrDomElement(TestUtils.document()); + } + } + + private void testHrToOdSecurityLabels(String fileName) throws Exception { + var doc = TestUtils.document(); + SecurityLabels securityLabels = + new SecurityLabelsFactory() + .createFromHrElement( + TestUtils.getElementFromResource( + Paths.get(SECURITY_LABELS_HR_PATH, fileName))); + Element ele = securityLabels.toOdDomElement(doc); + doc.appendChild(ele); + TestUtils.testFormatToFormat(doc, Paths.get(SECURITY_LABELS_OD_PATH, fileName)); + } + + private void testOdToHrSecurityLabels(String fileName) throws Exception { + var doc = TestUtils.document(); + SecurityLabels securityLabels = + new SecurityLabelsFactory() + .createFromOdElement( + TestUtils.getElementFromResource( + Paths.get(SECURITY_LABELS_OD_PATH, fileName))); + Element ele = securityLabels.toHrDomElement(doc); + doc.appendChild(ele); + TestUtils.testFormatToFormat(doc, Paths.get(SECURITY_LABELS_HR_PATH, fileName)); + } +} diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java index 9dcc6529969e..04bcd783a1dd 100644 --- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java +++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java @@ -43,6 +43,7 @@ public class SystemAppSafetyLabelTest { "com/android/asllib/systemappsafetylabel/od"; private static final String VALID_FILE_NAME = "valid.xml"; + private static final String VALID_V1_FILE_NAME = "valid-v1.xml"; private static final String MISSING_BOOL_FILE_NAME = "missing-bool.xml"; /** Logic for setting up tests (empty if not yet needed). */ @@ -57,59 +58,81 @@ public class SystemAppSafetyLabelTest { @Test public void testValid() throws Exception { System.out.println("starting testValid."); - testHrToOdSystemAppSafetyLabel(VALID_FILE_NAME); - testOdToHrSystemAppSafetyLabel(VALID_FILE_NAME); + testHrToOdSystemAppSafetyLabel(VALID_FILE_NAME, DEFAULT_VERSION); + testOdToHrSystemAppSafetyLabel(VALID_FILE_NAME, DEFAULT_VERSION); + } + + /** Test for valid v1. */ + @Test + public void testValidV1() throws Exception { + System.out.println("starting testValidV1."); + var doc = TestUtils.document(); + var unused = + new SystemAppSafetyLabelFactory() + .createFromOdElement( + TestUtils.getElementFromResource( + Paths.get( + SYSTEM_APP_SAFETY_LABEL_OD_PATH, + VALID_V1_FILE_NAME)), + 1L); + } + + /** Test for testV1InvalidAsV2. */ + @Test + public void testV1InvalidAsV2() throws Exception { + System.out.println("starting testV1InvalidAsV2."); + odToHrExpectException(VALID_V1_FILE_NAME, 2L); } /** Tests missing bool. */ @Test public void testMissingBool() throws Exception { System.out.println("starting testMissingBool."); - hrToOdExpectException(MISSING_BOOL_FILE_NAME); - odToHrExpectException(MISSING_BOOL_FILE_NAME); + hrToOdExpectException(MISSING_BOOL_FILE_NAME, DEFAULT_VERSION); + odToHrExpectException(MISSING_BOOL_FILE_NAME, DEFAULT_VERSION); } - private void hrToOdExpectException(String fileName) + private void hrToOdExpectException(String fileName, long version) throws ParserConfigurationException, IOException, SAXException { var ele = TestUtils.getElementFromResource( Paths.get(SYSTEM_APP_SAFETY_LABEL_HR_PATH, fileName)); assertThrows( MalformedXmlException.class, - () -> new SystemAppSafetyLabelFactory().createFromHrElement(ele, DEFAULT_VERSION)); + () -> new SystemAppSafetyLabelFactory().createFromHrElement(ele, version)); } - private void odToHrExpectException(String fileName) + private void odToHrExpectException(String fileName, long version) throws ParserConfigurationException, IOException, SAXException { var ele = TestUtils.getElementFromResource( Paths.get(SYSTEM_APP_SAFETY_LABEL_OD_PATH, fileName)); assertThrows( MalformedXmlException.class, - () -> new SystemAppSafetyLabelFactory().createFromOdElement(ele, DEFAULT_VERSION)); + () -> new SystemAppSafetyLabelFactory().createFromOdElement(ele, version)); } - private void testHrToOdSystemAppSafetyLabel(String fileName) throws Exception { + private void testHrToOdSystemAppSafetyLabel(String fileName, long version) throws Exception { var doc = TestUtils.document(); SystemAppSafetyLabel systemAppSafetyLabel = new SystemAppSafetyLabelFactory() .createFromHrElement( TestUtils.getElementFromResource( Paths.get(SYSTEM_APP_SAFETY_LABEL_HR_PATH, fileName)), - DEFAULT_VERSION); + version); Element resultingEle = systemAppSafetyLabel.toOdDomElement(doc); doc.appendChild(resultingEle); TestUtils.testFormatToFormat(doc, Paths.get(SYSTEM_APP_SAFETY_LABEL_OD_PATH, fileName)); } - private void testOdToHrSystemAppSafetyLabel(String fileName) throws Exception { + private void testOdToHrSystemAppSafetyLabel(String fileName, long version) throws Exception { var doc = TestUtils.document(); SystemAppSafetyLabel systemAppSafetyLabel = new SystemAppSafetyLabelFactory() .createFromOdElement( TestUtils.getElementFromResource( Paths.get(SYSTEM_APP_SAFETY_LABEL_OD_PATH, fileName)), - DEFAULT_VERSION); + version); Element resultingEle = systemAppSafetyLabel.toHrDomElement(doc); doc.appendChild(resultingEle); TestUtils.testFormatToFormat(doc, Paths.get(SYSTEM_APP_SAFETY_LABEL_HR_PATH, fileName)); diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/ThirdPartyVerificationTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/ThirdPartyVerificationTest.java new file mode 100644 index 000000000000..ebb2e93af920 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/ThirdPartyVerificationTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.asllib.marshallable; + +import static org.junit.Assert.assertThrows; + +import com.android.asllib.testutils.TestUtils; +import com.android.asllib.util.MalformedXmlException; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.w3c.dom.Element; + +import java.nio.file.Paths; + +@RunWith(JUnit4.class) +public class ThirdPartyVerificationTest { + private static final String THIRD_PARTY_VERIFICATION_HR_PATH = + "com/android/asllib/thirdpartyverification/hr"; + private static final String THIRD_PARTY_VERIFICATION_OD_PATH = + "com/android/asllib/thirdpartyverification/od"; + + private static final String VALID_FILE_NAME = "valid.xml"; + private static final String MISSING_URL_FILE_NAME = "missing-url.xml"; + + /** Logic for setting up tests (empty if not yet needed). */ + public static void main(String[] params) throws Exception {} + + @Before + public void setUp() throws Exception { + System.out.println("set up."); + } + + /** Test for valid. */ + @Test + public void testValid() throws Exception { + System.out.println("starting testValid."); + testHrToOdThirdPartyVerification(VALID_FILE_NAME); + testOdToHrThirdPartyVerification(VALID_FILE_NAME); + } + + /** Tests missing url. */ + @Test + public void testMissingUrl() throws Exception { + System.out.println("starting testMissingUrl."); + hrToOdExpectException(MISSING_URL_FILE_NAME); + odToHrExpectException(MISSING_URL_FILE_NAME); + } + + private void hrToOdExpectException(String fileName) { + assertThrows( + MalformedXmlException.class, + () -> { + new ThirdPartyVerificationFactory() + .createFromHrElement( + TestUtils.getElementFromResource( + Paths.get(THIRD_PARTY_VERIFICATION_HR_PATH, fileName))); + }); + } + + private void odToHrExpectException(String fileName) { + assertThrows( + MalformedXmlException.class, + () -> { + new ThirdPartyVerificationFactory() + .createFromOdElement( + TestUtils.getElementFromResource( + Paths.get(THIRD_PARTY_VERIFICATION_OD_PATH, fileName))); + }); + } + + private void testHrToOdThirdPartyVerification(String fileName) throws Exception { + var doc = TestUtils.document(); + ThirdPartyVerification thirdPartyVerification = + new ThirdPartyVerificationFactory() + .createFromHrElement( + TestUtils.getElementFromResource( + Paths.get(THIRD_PARTY_VERIFICATION_HR_PATH, fileName))); + Element ele = thirdPartyVerification.toOdDomElement(doc); + doc.appendChild(ele); + TestUtils.testFormatToFormat(doc, Paths.get(THIRD_PARTY_VERIFICATION_OD_PATH, fileName)); + } + + private void testOdToHrThirdPartyVerification(String fileName) throws Exception { + var doc = TestUtils.document(); + ThirdPartyVerification thirdPartyVerification = + new ThirdPartyVerificationFactory() + .createFromOdElement( + TestUtils.getElementFromResource( + Paths.get(THIRD_PARTY_VERIFICATION_OD_PATH, fileName))); + Element ele = thirdPartyVerification.toHrDomElement(doc); + doc.appendChild(ele); + TestUtils.testFormatToFormat(doc, Paths.get(THIRD_PARTY_VERIFICATION_HR_PATH, fileName)); + } +} diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java index 6547fb952944..b27d6ddb6243 100644 --- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java +++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java @@ -16,16 +16,23 @@ package com.android.asllib.marshallable; +import static org.junit.Assert.assertThrows; + import com.android.asllib.testutils.TestUtils; +import com.android.asllib.util.MalformedXmlException; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.w3c.dom.Element; +import org.xml.sax.SAXException; +import java.io.IOException; import java.nio.file.Paths; +import javax.xml.parsers.ParserConfigurationException; + @RunWith(JUnit4.class) public class TransparencyInfoTest { private static final long DEFAULT_VERSION = 2L; @@ -35,6 +42,10 @@ public class TransparencyInfoTest { private static final String TRANSPARENCY_INFO_OD_PATH = "com/android/asllib/transparencyinfo/od"; private static final String WITH_APP_INFO_FILE_NAME = "with-app-info.xml"; + private static final String VALID_EMPTY_V1_FILE_NAME = "valid-empty-v1.xml"; + private static final String VALID_DEV_INFO_V1_FILE_NAME = "valid-dev-info-v1.xml"; + private static final String WITH_APP_INFO_AND_DEV_INFO_FILE_NAME = + "with-app-info-v2-and-dev-info-v1.xml"; @Before public void setUp() throws Exception { @@ -45,33 +56,78 @@ public class TransparencyInfoTest { @Test public void testTransparencyInfoWithAppInfo() throws Exception { System.out.println("starting testTransparencyInfoWithAppInfo."); - testHrToOdTransparencyInfo(WITH_APP_INFO_FILE_NAME); - testOdToHrTransparencyInfo(WITH_APP_INFO_FILE_NAME); + testHrToOdTransparencyInfo(WITH_APP_INFO_FILE_NAME, DEFAULT_VERSION); + testOdToHrTransparencyInfo(WITH_APP_INFO_FILE_NAME, DEFAULT_VERSION); + } + + /** Test for testMissingAppInfoFailsInV2. */ + @Test + public void testMissingAppInfoFailsInV2() throws Exception { + System.out.println("starting testMissingAppInfoFailsInV2."); + odToHrExpectException(VALID_EMPTY_V1_FILE_NAME, 2L); + } + + /** Test for testMissingAppInfoPassesInV1. */ + @Test + public void testMissingAppInfoPassesInV1() throws Exception { + System.out.println("starting testMissingAppInfoPassesInV1."); + testParseOdTransparencyInfo(VALID_EMPTY_V1_FILE_NAME, 1L); + } + + /** Test for testDeveloperInfoExistencePassesInV1. */ + @Test + public void testDeveloperInfoExistencePassesInV1() throws Exception { + System.out.println("starting testDeveloperInfoExistencePassesInV1."); + testParseOdTransparencyInfo(VALID_DEV_INFO_V1_FILE_NAME, 1L); } - private void testHrToOdTransparencyInfo(String fileName) throws Exception { + /** Test for testDeveloperInfoExistenceFailsInV2. */ + @Test + public void testDeveloperInfoExistenceFailsInV2() throws Exception { + System.out.println("starting testDeveloperInfoExistenceFailsInV2."); + odToHrExpectException(WITH_APP_INFO_AND_DEV_INFO_FILE_NAME, 2L); + } + + private void testHrToOdTransparencyInfo(String fileName, long version) throws Exception { var doc = TestUtils.document(); TransparencyInfo transparencyInfo = new TransparencyInfoFactory() .createFromHrElement( TestUtils.getElementFromResource( Paths.get(TRANSPARENCY_INFO_HR_PATH, fileName)), - DEFAULT_VERSION); + version); Element resultingEle = transparencyInfo.toOdDomElement(doc); doc.appendChild(resultingEle); TestUtils.testFormatToFormat(doc, Paths.get(TRANSPARENCY_INFO_OD_PATH, fileName)); } - private void testOdToHrTransparencyInfo(String fileName) throws Exception { + private void testParseOdTransparencyInfo(String fileName, long version) throws Exception { + var unused = + new TransparencyInfoFactory() + .createFromOdElement( + TestUtils.getElementFromResource( + Paths.get(TRANSPARENCY_INFO_OD_PATH, fileName)), + version); + } + + private void testOdToHrTransparencyInfo(String fileName, long version) throws Exception { var doc = TestUtils.document(); TransparencyInfo transparencyInfo = new TransparencyInfoFactory() .createFromOdElement( TestUtils.getElementFromResource( Paths.get(TRANSPARENCY_INFO_OD_PATH, fileName)), - DEFAULT_VERSION); + version); Element resultingEle = transparencyInfo.toHrDomElement(doc); doc.appendChild(resultingEle); TestUtils.testFormatToFormat(doc, Paths.get(TRANSPARENCY_INFO_HR_PATH, fileName)); } + + private void odToHrExpectException(String fileName, long version) + throws ParserConfigurationException, IOException, SAXException { + var ele = TestUtils.getElementFromResource(Paths.get(TRANSPARENCY_INFO_OD_PATH, fileName)); + assertThrows( + MalformedXmlException.class, + () -> new TransparencyInfoFactory().createFromOdElement(ele, version)); + } } diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/valid-v1.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/valid-v1.xml new file mode 100644 index 000000000000..7e984e333ceb --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/valid-v1.xml @@ -0,0 +1,8 @@ +<bundle> + <long name="version" value="1"/> + <pbundle_as_map name="system_app_safety_label"> + <string name="url" value="www.example.com"/> + </pbundle_as_map> + <pbundle_as_map name="transparency_info"> + </pbundle_as_map> +</bundle>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/unrecognized-v1.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/unrecognized-v1.xml index 810078e777fb..01fd7180c3a6 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/unrecognized-v1.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/unrecognized-v1.xml @@ -21,5 +21,5 @@ <string name="category" value="Food and drink"/> <string name="email" value="max@maxloh.com"/> <string name="website" value="www.example.com"/> - <string name="unrecognized" value="www.example.com"/> + <boolean name="aps_compliant" value="false"/> </pbundle_as_map> diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/valid-v1.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/valid-v1.xml new file mode 100644 index 000000000000..1384a2f6dd52 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/valid-v1.xml @@ -0,0 +1,9 @@ +<pbundle_as_map name="safety_labels"> + <pbundle_as_map name="security_labels"> + <boolean name="is_data_deletable" value="true" /> + <boolean name="is_data_encrypted" value="false" /> + </pbundle_as_map> + <pbundle_as_map name="third_party_verification"> + <string name="url" value="www.example.com"/> + </pbundle_as_map> +</pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/valid-v1.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/valid-v1.xml new file mode 100644 index 000000000000..f96535b4b49b --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/valid-v1.xml @@ -0,0 +1,3 @@ +<pbundle_as_map name="system_app_safety_label"> + <string name="url" value="www.example.com"/> +</pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/valid-dev-info-v1.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/valid-dev-info-v1.xml new file mode 100644 index 000000000000..d7a4e1a959b7 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/valid-dev-info-v1.xml @@ -0,0 +1,12 @@ + +<pbundle_as_map name="transparency_info"> + <pbundle_as_map name="developer_info"> + <string name="name" value="max"/> + <string name="email" value="max@example.com"/> + <string name="address" value="111 blah lane"/> + <string name="country_region" value="US"/> + <long name="relationship" value="5"/> + <string name="website" value="example.com"/> + <string name="app_developer_registry_id" value="registry_id"/> + </pbundle_as_map> +</pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/valid-empty.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/valid-empty-v1.xml index af574cf92b3a..af574cf92b3a 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/valid-empty.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/valid-empty-v1.xml diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-developer-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-app-info-v2-and-dev-info-v1.xml index b5e64b925ca5..b5e64b925ca5 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-developer-info.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-app-info-v2-and-dev-info-v1.xml diff --git a/wifi/wifi.aconfig b/wifi/wifi.aconfig index c1effe148e5c..f7162f6b7746 100644 --- a/wifi/wifi.aconfig +++ b/wifi/wifi.aconfig @@ -25,3 +25,13 @@ flag { description: "Add API to migrate all values from Legacy Keystore to the new Wifi Blobstore database" bug: "332560152" } + +flag { + name: "hotspot_network_unknown_status_resets_connecting_state" + namespace: "wifi" + description: "Reset the connecting state flags when the hotspot network updates to unknown." + bug: "329670511" + metadata { + purpose: PURPOSE_BUGFIX + } +} |